diff --git a/.gitignore b/.gitignore
index e69de29..5079921 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1,47 @@
+# ROS 2 / Colcon
+build/
+install/
+log/
+
+# CMake
+CMakeCache.txt
+CMakeFiles/
+cmake_install.cmake
+Makefile
+CTestTestfile.cmake
+Testing/
+
+# C++ 编译输出
+*.o
+*.obj
+*.so
+*.a
+*.dll
+*.exe
+*.out
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+
+# 系统文件
+.DS_Store
+Thumbs.db
+
+# 临时文件
+*.tmp
+*.temp
+*.log
+
+# 保留 compile_commands.json 给 clangd 使用
+# (不忽略 build/compile_commands.json,通过 CMake 导出到项目根目录)
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index aa46b6d..2a31b6f 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,8 +1,28 @@
{
- "clangd.arguments": [
+ // Clangd 配置
+ "clangd.path": "/usr/bin/clangd",
+ "clangd.arguments": [
+ "--background-index",
"--compile-commands-dir=${workspaceFolder}/build",
- "--completion-style=detailed",
- "--query-driver=/usr/bin/clang",
- "--header-insertion=never"
+ "--header-insertion=iwyu",
+ "--completion-style=bundled",
+ "--pch-storage=memory",
+ "--cross-file-rename"
],
+ // C++ 配置
+ "C_Cpp.intelliSenseEngine": "disabled", // 使用 clangd 替代默认引擎
+ "C_Cpp.autocomplete": "disabled",
+ "C_Cpp.errorSquiggles": "disabled",
+ // 文件关联
+ "files.associations": {
+ "*.h": "c",
+ "*.hpp": "cpp",
+ "*.cpp": "cpp"
+ },
+ // 编辑器配置
+ "editor.formatOnSave": true,
+ "editor.tabSize": 4,
+ "editor.insertSpaces": true,
+ // ROS 2 配置
+ "ros.distro": "humble"
}
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 92af353..473f354 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,3 +1,4 @@
+
cmake_minimum_required(VERSION 3.8)
project(amadeus_26)
@@ -5,14 +6,14 @@ project(amadeus_26)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
+# 导出 compile_commands.json 给 clangd 使用
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
# 查找依赖
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
-# 查找 serial 库 (用于 UART 节点)
-find_package(serial REQUIRED)
-
# 添加 SDK 库路径
set(TRANSMITTER_SDK_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib/transmitter_sdk)
link_directories(${TRANSMITTER_SDK_PATH}/lib/linux/x64)
@@ -23,37 +24,16 @@ include_directories(
${TRANSMITTER_SDK_PATH}/inc
)
-# ==================== CAN 节点 ====================
-add_executable(transmitter_test_node src/transmitter_test_node.cpp)
-
-set_target_properties(transmitter_test_node PROPERTIES
- BUILD_WITH_INSTALL_RPATH FALSE
- LINK_FLAGS "-Wl,-rpath,'${TRANSMITTER_SDK_PATH}/lib/linux/x64'"
-)
-
-ament_target_dependencies(transmitter_test_node
- rclcpp
- std_msgs
-)
-
-target_link_libraries(transmitter_test_node
- dm_device
- usb-1.0
- pthread
-)
-
# ==================== UART 节点 ====================
add_executable(uart_transmitter_node src/uart_transmitter_node.cpp)
ament_target_dependencies(uart_transmitter_node
rclcpp
std_msgs
- serial
)
# ==================== 安装目标 ====================
install(TARGETS
- transmitter_test_node
uart_transmitter_node
DESTINATION lib/${PROJECT_NAME}
)
diff --git a/README.md b/README.md
index 3c56262..674e417 100644
--- a/README.md
+++ b/README.md
@@ -10,8 +10,8 @@
- MV-SUA133GC-T 工业相机 (USB)
- NUC10i7 微型计算机 (主机)
-- FDI Systems DETA10 IMU 模块 (USB - CP210x 串口芯片 - UART) `/dev/ttyUSB0`
-- 达妙 USB2CAN-DUAL USB 转 CAN 模块 (USB - 模块 - CAN*2) `/dev/ttyACM0`
+- FDI Systems DETA10 IMU 模块 (USB - CP210x 串口芯片 - UART) `/dev/ttyUSB1`
+- CH340 USB 转 UART 模块 (USB - 模块 - UART) `/dev/ttyUSB0`
### 软件
@@ -19,12 +19,11 @@
- OpenCV 4.13 with Contrib
- Eigen 3
- MVSDK (工业相机 SDK)
-- dm_device (USB 转 CAN 模块 SDK)
- 待补充...
## 目标
-感谢同济 SuperPower 战队!自瞄算法参考同济的 sp_vision_26 。项目目标为使 2026 年采用的
+感谢同济 SuperPower 战队!自瞄算法参考同济的 sp_vision_26 。
## 软件架构
@@ -40,14 +39,14 @@ ROS2 基本架构为节点。以每个节点的发布、解析、接收数据为
### 收发节点
- 功能:主机对外通信的封装节点。物理上读取收发模块
-- 连接:USB2CAN,协商 CAN 通信速率 1000kbps,`/dev/ttyACM0`
- IMU_CP210x,协商 UART 通信速率 921600,`/dev/ttyUSB0`
-- 接收:USB2CAN -> 裁判系统数据
+- 连接:CH340,协商 UART 通信速率 115200,`/dev/ttyUSB0`
+ IMU_CP210x,协商 UART 通信速率 921600,`/dev/ttyUSB1`
+- 接收:CH340.RX -> 裁判系统数据
- 订阅:中央节点 -> 控制指令
-- 发送:USB2CAN.CAN2 -> 控制指令
+- 发送:CH340.TX -> 控制指令
- 发布:IMU 数据(地磁角、六轴角加速度、加速度、陀螺仪)
裁判系统数据(血量、比赛当前状态)
- USB2CAN 、IMU 连接 OK 标志
+ CH340 、IMU 连接 OK 标志
### 中央节点
diff --git a/camera_sdk/lib/linux/arm64/libMVSDK.so b/camera_sdk/lib/linux/arm64/libMVSDK.so
deleted file mode 100644
index 402ea21..0000000
Binary files a/camera_sdk/lib/linux/arm64/libMVSDK.so and /dev/null differ
diff --git a/camera_sdk/lib/linux/x64/libMVSDK.so b/camera_sdk/lib/linux/x64/libMVSDK.so
deleted file mode 100644
index 625adcb..0000000
Binary files a/camera_sdk/lib/linux/x64/libMVSDK.so and /dev/null differ
diff --git a/docs/Transmit/ctrl_command.md b/docs/Transmit/ctrl_command.md
deleted file mode 100644
index b80bec6..0000000
--- a/docs/Transmit/ctrl_command.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# 控制指令的发送说明
-
-在主机外接收的控制板中的控制协议是:
-
-| 帧头1 | 帧头2 | 平动左右 | 平动前后 | 云台偏航 | 云台俯仰 | 拨弹轮 | 左拨杆 | 右拨杆 | CRC8 | 帧尾 |
-| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| 1 | 1 | 2 | 2 | 2 | 2 | 2 | 0.5 | 0.5 | 1 | 1 |
-
-
-两个 ID 分别是云台和底盘的控制板,在处于进点/回家状态时不应开启小陀螺,发射弹丸需要**打开摩擦轮 同时 进弹**,因此只有在处于攻击状态时开启摩擦轮,此时可以将进弹视为扳机。
\ No newline at end of file
diff --git a/docs/Transmit/ctrl_frame.md b/docs/Transmit/ctrl_frame.md
new file mode 100644
index 0000000..3628804
--- /dev/null
+++ b/docs/Transmit/ctrl_frame.md
@@ -0,0 +1,39 @@
+# SRCP 通信协议帧结构
+
+## 帧格式
+
+| 字段 | 内容 | 长度 | 说明 |
+|------|------|------|------|
+| SOF | 0xBB 0x77 | 2 bytes | 帧头 |
+| x_move | -660~660 | 2 bytes | 平动左右 (小端序) |
+| y_move | -660~660 | 2 bytes | 平动前后 (小端序) |
+| yaw | -660~660 | 2 bytes | 云台偏航 (小端序) |
+| pitch | -660~660 | 2 bytes | 云台俯仰 (小端序) |
+| feed | -660~660 | 2 bytes | 拨弹轮 (小端序) |
+| key | 0~15 | 1 byte | 按键 |
+| crc8 | CRC8-MAXIM | 1 byte | 校验 |
+| EOF | 0xEE | 1 byte | 帧尾 |
+
+## 帧长度
+
+```
+SOF(2) + x_move(2) + y_move(2) + yaw(2) + pitch(2) + feed(2) + key(1) + crc8(1) + EOF(1) = 15 bytes
+```
+
+**FRAME_LENGTH = 15**
+
+## 字节序
+
+- 所有 int16_t 类型字段(x_move, y_move, yaw, pitch, feed)使用 **小端序**
+
+## 代码示例
+
+```cpp
+constexpr int FRAME_LENGTH = 15; // 总帧长度
+
+frame[0] = 0xBB; // SOF
+frame[1] = 0x77; // SOF
+frame[2] = x_move & 0xFF; // 低字节
+frame[3] = (x_move >> 8) & 0xFF; // 高字节
+// ... 其他字段
+frame[14] = 0xEE; // EOF
\ No newline at end of file
diff --git a/docs/Transmit/usb_device_setup.md b/docs/Transmit/usb_device_setup.md
new file mode 100644
index 0000000..a96ac44
--- /dev/null
+++ b/docs/Transmit/usb_device_setup.md
@@ -0,0 +1,87 @@
+# USB 串口设备权限设置指南
+
+## 文件说明
+
+| 文件 | 说明 |
+|------|------|
+| `99-usb-serial.rules` | udev 规则文件,固定设备名称和权限 |
+| `setup_usb_permissions.sh` | 自动化设置脚本 |
+
+## 操作步骤
+
+### 方法一:使用自动化脚本(推荐)
+
+```bash
+cd ~/code/amadeus_26
+./setup_usb_permissions.sh
+```
+
+### 方法二:手动设置
+
+1. **识别设备 VID/PID**
+
+```bash
+# 查看 CH340 设备信息
+udevadm info -a -n /dev/ttyUSB0 | grep -E "idVendor|idProduct"
+
+# 查看 IMU 设备信息
+udevadm info -a -n /dev/ttyUSB1 | grep -E "idVendor|idProduct"
+```
+
+2. **安装 udev 规则**
+
+```bash
+sudo cp 99-usb-serial.rules /etc/udev/rules.d/
+sudo udevadm control --reload-rules
+sudo udevadm trigger
+```
+
+3. **添加用户权限**
+
+```bash
+sudo usermod -aG dialout $USER
+```
+
+4. **生效**
+
+- 重新插拔 USB 设备,或重启系统
+- 注销并重新登录,使权限生效
+
+## 设备映射
+
+设置完成后,设备将以固定名称出现:
+
+| 设备 | 固定名称 | 原名称 |
+|------|----------|--------|
+| CH340 发送模块 | `/dev/ttyCH340` | `/dev/ttyUSB0` |
+| IMU 模块 | `/dev/ttyIMU` | `/dev/ttyUSB1` |
+
+## 验证
+
+```bash
+# 检查设备链接
+ls -la /dev/ttyCH340 /dev/ttyIMU
+
+# 检查权限
+ls -la /dev/ttyUSB*
+```
+
+## 使用
+
+修改 launch 文件中的默认设备路径:
+
+```python
+# launch/uart_transmitter.launch.py
+serial_port_arg = DeclareLaunchArgument(
+ 'serial_port',
+ default_value='/dev/ttyCH340', # 使用固定名称
+ description='CH340 串口设备路径'
+)
+```
+
+运行节点:
+
+```bash
+ros2 launch amadeus_26 uart_transmitter.launch.py
+# 或指定 IMU 设备
+ros2 launch amadeus_26 uart_transmitter.launch.py serial_port:=/dev/ttyIMU
\ No newline at end of file
diff --git a/launch/uart_transmitter.launch.py b/launch/uart_transmitter.launch.py
index 9a63af6..316ca7d 100644
--- a/launch/uart_transmitter.launch.py
+++ b/launch/uart_transmitter.launch.py
@@ -8,19 +8,19 @@ def generate_launch_description():
# 声明启动参数
serial_port_arg = DeclareLaunchArgument(
'serial_port',
- default_value='/dev/ttyUSB0',
+ default_value='/dev/ttyCH340',
description='CH340 串口设备路径'
)
baudrate_arg = DeclareLaunchArgument(
'baudrate',
- default_value='921600',
+ default_value='115200',
description='串口波特率'
)
send_frequency_arg = DeclareLaunchArgument(
'send_frequency',
- default_value='50.0',
+ default_value='10.0',
description='发送频率 (Hz)'
)
diff --git a/camera_sdk/inc/CameraApi.h b/lib/camera_sdk/inc/CameraApi.h
similarity index 100%
rename from camera_sdk/inc/CameraApi.h
rename to lib/camera_sdk/inc/CameraApi.h
diff --git a/camera_sdk/inc/CameraDefine.h b/lib/camera_sdk/inc/CameraDefine.h
similarity index 100%
rename from camera_sdk/inc/CameraDefine.h
rename to lib/camera_sdk/inc/CameraDefine.h
diff --git a/camera_sdk/inc/CameraStatus.h b/lib/camera_sdk/inc/CameraStatus.h
similarity index 100%
rename from camera_sdk/inc/CameraStatus.h
rename to lib/camera_sdk/inc/CameraStatus.h
diff --git a/package.xml b/package.xml
new file mode 100644
index 0000000..bc57f4d
--- /dev/null
+++ b/package.xml
@@ -0,0 +1,18 @@
+
+
+
+ amadeus_26
+ 0.0.1
+ Amadeus 26 transmission and control package
+ maintainer
+ MIT
+
+ ament_cmake
+
+ rclcpp
+ std_msgs
+
+
+ ament_cmake
+
+
\ No newline at end of file
diff --git a/src/uart_transmitter_node.cpp b/src/uart_transmitter_node.cpp
index cfc630d..1fc5931 100644
--- a/src/uart_transmitter_node.cpp
+++ b/src/uart_transmitter_node.cpp
@@ -1,309 +1,357 @@
/**
* @file uart_transmitter_node.cpp
* @brief UART 串口收发模块测试节点 (CH340)
- *
- * 使用 CH340 USB 转串口模块发送控制指令
- * 波特率:921600 (与裁判系统一致)
+ *
+ * 默认波特率:115200
* 协议帧格式:
- * | 帧头1 | 帧头2 | 平动左右 | 平动前后 | 云台偏航 | 云台俯仰 | 拨弹轮 | 拨杆 | CRC8 | 帧尾 |
- * | 0xAA | 0x55 | 2 bytes | 2 bytes | 2 bytes | 2 bytes | 2 bytes|1 byte| 1byte| 0xFF |
+ * | 0xBB | 0x77 | 平动左右 | 平动前后 | 云台偏航 | 云台俯仰 | 拨弹轮 | 拨杆 |
+ * CRC8 | 帧尾 | | 0xAA | 0x55 | 2 bytes | 2 bytes | 2 bytes | 2 bytes | 2
+ * bytes|1 byte| 1byte| 0xEE |
*/
+#include
+#include
+#include
#include
#include
-#include
+#include
#include
-#include
+#include
#include
// 帧定义
-constexpr uint8_t FRAME_HEADER_1 = 0xAA;
-constexpr uint8_t FRAME_HEADER_2 = 0x55;
-constexpr uint8_t FRAME_TAIL = 0xFF;
-constexpr int FRAME_LENGTH = 14; // 总帧长度
+constexpr uint8_t FRAME_HEADER_1 = 0xBB;
+constexpr uint8_t FRAME_HEADER_2 = 0x77;
+constexpr uint8_t FRAME_TAIL = 0xEE;
+constexpr int FRAME_LENGTH = 15;
// 默认串口设备
-constexpr const char* DEFAULT_SERIAL_PORT = "/dev/ttyUSB0";
+constexpr const char *DEFAULT_SERIAL_PORT = "/dev/ttyCH340";
constexpr int DEFAULT_BAUDRATE = 115200;
-class UartTransmitterNode : public rclcpp::Node
-{
+class UartTransmitterNode : public rclcpp::Node {
public:
- UartTransmitterNode() : Node("uart_transmitter_node")
- {
- // 声明参数
- this->declare_parameter("serial_port", DEFAULT_SERIAL_PORT);
- this->declare_parameter("baudrate", DEFAULT_BAUDRATE);
- this->declare_parameter("send_frequency", 50.0); // Hz
-
- // 控制参数
- this->declare_parameter("x_move", 0); // 平动左右 [-660, 660]
- this->declare_parameter("y_move", 0); // 平动前后 [-660, 660]
- this->declare_parameter("yaw", 0); // 云台偏航 [-660, 660]
- this->declare_parameter("pitch", 0); // 云台俯仰 [-660, 660]
- this->declare_parameter("feed", 0); // 拨弹轮 [-660, 660]
- this->declare_parameter("left_switch", 0); // 左拨杆 [0, 15]
- this->declare_parameter("right_switch", 0); // 右拨杆 [0, 15]
+ UartTransmitterNode() : Node("uart_transmitter_node") {
+ // 声明参数
+ this->declare_parameter("serial_port", DEFAULT_SERIAL_PORT);
+ this->declare_parameter("baudrate", DEFAULT_BAUDRATE);
+ this->declare_parameter("send_frequency", 50.0); // Hz
- // 获取参数
- serial_port_ = this->get_parameter("serial_port").as_string();
- baudrate_ = this->get_parameter("baudrate").as_int();
- send_frequency_ = this->get_parameter("send_frequency").as_double();
+ // 控制参数
+ this->declare_parameter("x_move", 0); // 平动左右 [-660, 660]
+ this->declare_parameter("y_move", 0); // 平动前后 [-660, 660]
+ this->declare_parameter("yaw", 0); // 云台偏航 [-660, 660]
+ this->declare_parameter("pitch", 0); // 云台俯仰 [-660, 660]
+ this->declare_parameter("feed", 0); // 拨弹轮 [-660, 660]
+ this->declare_parameter("left_switch", 0); // 左拨杆 [1, 3]
+ this->declare_parameter("right_switch", 0); // 右拨杆 [1, 3]
- RCLCPP_INFO(this->get_logger(), "=================================");
- RCLCPP_INFO(this->get_logger(), "UART 收发模块测试节点启动");
- RCLCPP_INFO(this->get_logger(), "串口: %s", serial_port_.c_str());
- RCLCPP_INFO(this->get_logger(), "波特率: %d", baudrate_);
- RCLCPP_INFO(this->get_logger(), "发送频率: %.1f Hz", send_frequency_);
- RCLCPP_INFO(this->get_logger(), "=================================");
+ // 获取参数
+ serial_port_ = this->get_parameter("serial_port").as_string();
+ baudrate_ = this->get_parameter("baudrate").as_int();
+ send_frequency_ = this->get_parameter("send_frequency").as_double();
- // 初始化串口
- if (!initSerial()) {
- RCLCPP_ERROR(this->get_logger(), "串口初始化失败,节点退出");
- rclcpp::shutdown();
- return;
- }
+ RCLCPP_INFO(this->get_logger(), "---------------------------------");
+ RCLCPP_INFO(this->get_logger(), "UART 收发节点启动");
+ RCLCPP_INFO(this->get_logger(), "串口: %s", serial_port_.c_str());
+ RCLCPP_INFO(this->get_logger(), "波特率: %d", baudrate_);
+ RCLCPP_INFO(this->get_logger(), "发送频率: %.1f Hz", send_frequency_);
+ RCLCPP_INFO(this->get_logger(), "---------------------------------");
- // 创建发布者
- connection_status_pub_ = this->create_publisher("transmitter/connection_status", 10);
-
- // 创建定时器 - 发送数据
- auto send_period = std::chrono::duration(1.0 / send_frequency_);
- send_timer_ = this->create_wall_timer(
- std::chrono::duration_cast(send_period),
- std::bind(&UartTransmitterNode::sendControlFrame, this));
-
- // 创建定时器 - 发布连接状态
- status_timer_ = this->create_wall_timer(
- std::chrono::seconds(1),
- std::bind(&UartTransmitterNode::publishStatus, this));
-
- // 创建接收线程
- receive_thread_ = std::thread(&UartTransmitterNode::receiveLoop, this);
-
- RCLCPP_INFO(this->get_logger(), "UART 节点初始化完成");
+ // 初始化串口
+ if (!initSerial()) {
+ RCLCPP_ERROR(this->get_logger(), "串口初始化失败,节点退出");
+ rclcpp::shutdown();
+ return;
}
- ~UartTransmitterNode()
- {
- if (receive_thread_.joinable()) {
- running_ = false;
- receive_thread_.join();
- }
- if (serial_.isOpen()) {
- serial_.close();
- }
- RCLCPP_INFO(this->get_logger(), "串口已关闭");
+ // 创建发布者
+ connection_status_pub_ = this->create_publisher(
+ "transmitter/connection_status", 10);
+
+ // 创建定时器 - 发送数据
+ auto send_period = std::chrono::duration(1.0 / send_frequency_);
+ send_timer_ = this->create_wall_timer(
+ std::chrono::duration_cast(send_period),
+ std::bind(&UartTransmitterNode::sendControlFrame, this));
+
+ // 创建定时器 - 发布连接状态
+ status_timer_ = this->create_wall_timer(
+ std::chrono::seconds(1),
+ std::bind(&UartTransmitterNode::publishStatus, this));
+
+ // 创建接收线程
+ receive_thread_ = std::thread(&UartTransmitterNode::receiveLoop, this);
+
+ RCLCPP_INFO(this->get_logger(), "UART 节点初始化完成");
+ }
+
+ ~UartTransmitterNode() {
+ running_ = false;
+ if (receive_thread_.joinable()) {
+ receive_thread_.join();
}
+ if (serial_fd_ >= 0) {
+ close(serial_fd_);
+ }
+ RCLCPP_INFO(this->get_logger(), "串口已关闭");
+ }
private:
- bool initSerial()
- {
- try {
- RCLCPP_INFO(this->get_logger(), "正在打开串口 %s...", serial_port_.c_str());
-
- serial_.setPort(serial_port_);
- serial_.setBaudrate(baudrate_);
- serial::Timeout timeout = serial::Timeout::simpleTimeout(1000);
- serial_.setTimeout(timeout);
- serial_.setBytesize(serial::eightbits);
- serial_.setParity(serial::parity_none);
- serial_.setStopbits(serial::stopbits_one);
-
- serial_.open();
-
- if (serial_.isOpen()) {
- RCLCPP_INFO(this->get_logger(), "串口打开成功");
- is_connected_ = true;
- return true;
- } else {
- RCLCPP_ERROR(this->get_logger(), "串口打开失败");
- return false;
- }
- } catch (const std::exception& e) {
- RCLCPP_ERROR(this->get_logger(), "串口异常: %s", e.what());
- return false;
- }
+ bool initSerial() {
+ RCLCPP_INFO(this->get_logger(), "正在打开串口 %s...", serial_port_.c_str());
+
+ // 打开串口
+ serial_fd_ = open(serial_port_.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
+ if (serial_fd_ < 0) {
+ RCLCPP_ERROR(this->get_logger(), "无法打开串口 %s: %s",
+ serial_port_.c_str(), strerror(errno));
+ return false;
}
- void sendControlFrame()
- {
- if (!serial_.isOpen()) {
- RCLCPP_WARN_THROTTLE(this->get_logger(), *this->get_clock(), 5000, "串口未打开");
- return;
- }
+ // 配置串口
+ struct termios tty;
+ memset(&tty, 0, sizeof(tty));
- // 获取最新的控制参数
- int16_t x_move = static_cast(this->get_parameter("x_move").as_int());
- int16_t y_move = static_cast(this->get_parameter("y_move").as_int());
- int16_t yaw = static_cast(this->get_parameter("yaw").as_int());
- int16_t pitch = static_cast(this->get_parameter("pitch").as_int());
- int16_t feed = static_cast(this->get_parameter("feed").as_int());
- uint8_t left_switch = static_cast(this->get_parameter("left_switch").as_int()) & 0x0F;
- uint8_t right_switch = static_cast(this->get_parameter("right_switch").as_int()) & 0x0F;
-
- // 构建数据帧
- std::vector frame;
- frame.reserve(FRAME_LENGTH);
-
- // 帧头
- frame.push_back(FRAME_HEADER_1);
- frame.push_back(FRAME_HEADER_2);
-
- // 平动左右 (2 bytes, int16, 大端序)
- frame.push_back((x_move >> 8) & 0xFF);
- frame.push_back(x_move & 0xFF);
-
- // 平动前后 (2 bytes)
- frame.push_back((y_move >> 8) & 0xFF);
- frame.push_back(y_move & 0xFF);
-
- // 云台偏航 (2 bytes)
- frame.push_back((yaw >> 8) & 0xFF);
- frame.push_back(yaw & 0xFF);
-
- // 云台俯仰 (2 bytes)
- frame.push_back((pitch >> 8) & 0xFF);
- frame.push_back(pitch & 0xFF);
-
- // 拨弹轮 (2 bytes)
- frame.push_back((feed >> 8) & 0xFF);
- frame.push_back(feed & 0xFF);
-
- // 拨杆 (1 byte: 高4位左拨杆,低4位右拨杆)
- uint8_t switches = (left_switch << 4) | right_switch;
- frame.push_back(switches);
-
- // CRC8 (除帧头外的所有数据)
- uint8_t crc = calculateCRC8(frame.data() + 2, frame.size() - 2);
- frame.push_back(crc);
-
- // 帧尾
- frame.push_back(FRAME_TAIL);
-
- // 发送数据
- try {
- size_t written = serial_.write(frame.data(), frame.size());
- if (written != frame.size()) {
- RCLCPP_WARN(this->get_logger(), "发送数据不完整: %zu/%d", written, FRAME_LENGTH);
- }
- } catch (const std::exception& e) {
- RCLCPP_ERROR(this->get_logger(), "发送失败: %s", e.what());
- is_connected_ = false;
- }
+ if (tcgetattr(serial_fd_, &tty) != 0) {
+ RCLCPP_ERROR(this->get_logger(), "tcgetattr 错误: %s", strerror(errno));
+ close(serial_fd_);
+ serial_fd_ = -1;
+ return false;
}
- uint8_t calculateCRC8(const uint8_t* data, size_t len)
- {
- uint8_t crc = 0xFF; // 初始值
- for (size_t i = 0; i < len; i++) {
- crc ^= data[i];
- for (int j = 0; j < 8; j++) {
- if (crc & 0x80) {
- crc = (crc << 1) ^ 0x31; // CRC8-MAXIM 多项式
+ // 设置波特率
+ speed_t baud = convertBaudrate(baudrate_);
+ cfsetospeed(&tty, baud);
+ cfsetispeed(&tty, baud);
+
+ // 8N1
+ tty.c_cflag &= ~PARENB; // 无校验
+ tty.c_cflag &= ~CSTOPB; // 1位停止位
+ tty.c_cflag &= ~CSIZE;
+ tty.c_cflag |= CS8; // 8位数据
+ tty.c_cflag |= CREAD | CLOCAL; // 启用接收,忽略控制线
+
+ // 禁用硬件流控
+ tty.c_cflag &= ~CRTSCTS;
+
+ // 原始模式
+ tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
+ tty.c_iflag &= ~(IXON | IXOFF | IXANY);
+ tty.c_oflag &= ~OPOST;
+
+ // 设置超时
+ tty.c_cc[VMIN] = 0;
+ tty.c_cc[VTIME] = 1; // 100ms 超时
+
+ if (tcsetattr(serial_fd_, TCSANOW, &tty) != 0) {
+ RCLCPP_ERROR(this->get_logger(), "tcsetattr 错误: %s", strerror(errno));
+ close(serial_fd_);
+ serial_fd_ = -1;
+ return false;
+ }
+
+ // 清空缓冲区
+ tcflush(serial_fd_, TCIOFLUSH);
+
+ RCLCPP_INFO(this->get_logger(), "串口打开成功");
+ is_connected_ = true;
+ return true;
+ }
+
+ speed_t convertBaudrate(int baudrate) {
+ switch (baudrate) {
+ case 9600:
+ return B9600;
+ case 19200:
+ return B19200;
+ case 38400:
+ return B38400;
+ case 57600:
+ return B57600;
+ case 115200:
+ return B115200;
+ case 230400:
+ return B230400;
+ case 460800:
+ return B460800;
+ case 921600:
+ return B921600;
+ default:
+ RCLCPP_WARN(this->get_logger(), "不支持的波特率 %d,使用 115200",
+ baudrate);
+ return B115200;
+ }
+ }
+
+ void sendControlFrame() {
+ if (serial_fd_ < 0) {
+ RCLCPP_WARN_THROTTLE(this->get_logger(), *this->get_clock(), 5000,
+ "串口未打开");
+ return;
+ }
+
+ // 获取控制参数
+ int16_t x_move =
+ static_cast(this->get_parameter("x_move").as_int());
+ int16_t y_move =
+ static_cast(this->get_parameter("y_move").as_int());
+ int16_t yaw = static_cast(this->get_parameter("yaw").as_int());
+ int16_t pitch = static_cast(this->get_parameter("pitch").as_int());
+ int16_t feed = static_cast(this->get_parameter("feed").as_int());
+ uint8_t left_switch =
+ static_cast(this->get_parameter("left_switch").as_int()) &
+ 0x0F;
+ uint8_t right_switch =
+ static_cast(this->get_parameter("right_switch").as_int()) &
+ 0x0F;
+
+ // 构建数据帧
+ uint8_t frame[FRAME_LENGTH];
+ int idx = 0;
+
+ // 帧头
+ frame[idx++] = FRAME_HEADER_1;
+ frame[idx++] = FRAME_HEADER_2;
+
+ // 平动左右 (2 bytes, int16, 小端序)
+ frame[idx++] = x_move & 0xFF;
+ frame[idx++] = (x_move >> 8) & 0xFF;
+
+ // 平动前后 (2 bytes, 小端序)
+ frame[idx++] = y_move & 0xFF;
+ frame[idx++] = (y_move >> 8) & 0xFF;
+
+ // 云台偏航 (2 bytes, 小端序)
+ frame[idx++] = yaw & 0xFF;
+ frame[idx++] = (yaw >> 8) & 0xFF;
+
+ // 云台俯仰 (2 bytes, 小端序)
+ frame[idx++] = pitch & 0xFF;
+ frame[idx++] = (pitch >> 8) & 0xFF;
+
+ // 拨弹轮 (2 bytes, 小端序)
+ frame[idx++] = feed & 0xFF;
+ frame[idx++] = (feed >> 8) & 0xFF;
+
+ // 拨杆 (1 byte: 高4位左拨杆,低4位右拨杆)
+ frame[idx++] = (left_switch << 4) | right_switch;
+
+ // CRC8 (除帧头和帧尾外的所有数据,不包括CRC本身)
+ // 当前idx=13, 数据长度=11 (从frame[2]到frame[12])
+ frame[idx++] = calculateCRC8(frame + 2, 11);
+
+ // 帧尾
+ frame[idx++] = FRAME_TAIL;
+
+ // 发送数据
+ ssize_t written = write(serial_fd_, frame, FRAME_LENGTH);
+ if (written != FRAME_LENGTH) {
+ RCLCPP_WARN(this->get_logger(), "发送数据不完整: %zd/%d", written,
+ FRAME_LENGTH);
+ }
+ }
+
+ uint8_t calculateCRC8(const uint8_t *data, size_t len) {
+ uint8_t crc = 0xFF; // 初始值
+ for (size_t i = 0; i < len; i++) {
+ crc ^= data[i];
+ for (int j = 0; j < 8; j++) {
+ if (crc & 0x80) {
+ crc = (crc << 1) ^ 0x31; // CRC8-MAXIM 多项式
+ } else {
+ crc <<= 1;
+ }
+ }
+ }
+ return crc;
+ }
+
+ void receiveLoop() {
+ uint8_t buffer[256];
+ std::vector frame_buffer;
+ frame_buffer.reserve(FRAME_LENGTH);
+
+ while (running_ && rclcpp::ok()) {
+ if (serial_fd_ < 0) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ continue;
+ }
+
+ // 读取数据
+ ssize_t n = read(serial_fd_, buffer, sizeof(buffer));
+ if (n > 0) {
+ for (ssize_t i = 0; i < n; i++) {
+ frame_buffer.push_back(buffer[i]);
+
+ // 查找帧头
+ if (frame_buffer.size() >= 2 && frame_buffer[0] == FRAME_HEADER_1 &&
+ frame_buffer[1] == FRAME_HEADER_2) {
+
+ // 等待完整帧
+ if (frame_buffer.size() >= FRAME_LENGTH) {
+ // 检查帧尾
+ if (frame_buffer[FRAME_LENGTH - 1] == FRAME_TAIL) {
+ // 验证 CRC
+ uint8_t rx_crc = frame_buffer[FRAME_LENGTH - 2];
+ uint8_t calc_crc =
+ calculateCRC8(frame_buffer.data() + 2, FRAME_LENGTH - 4);
+
+ if (rx_crc == calc_crc) {
+ RCLCPP_INFO(this->get_logger(), "收到有效帧,CRC 验证通过");
} else {
- crc <<= 1;
+ RCLCPP_WARN(this->get_logger(),
+ "CRC 错误: 接收=%02X, 计算=%02X", rx_crc,
+ calc_crc);
}
+ }
+
+ // 清空缓冲区,准备下一帧
+ frame_buffer.clear();
}
+ } else if (frame_buffer.size() > FRAME_LENGTH) {
+ // 缓冲区溢出,清空
+ frame_buffer.clear();
+ }
}
- return crc;
+ } else if (n < 0 && errno != EAGAIN) {
+ RCLCPP_ERROR(this->get_logger(), "读取错误: %s", strerror(errno));
+ }
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
+ }
- void receiveLoop()
- {
- std::vector buffer;
- buffer.reserve(FRAME_LENGTH);
+ void publishStatus() {
+ auto msg = std_msgs::msg::Bool();
+ msg.data = is_connected_ && (serial_fd_ >= 0);
+ connection_status_pub_->publish(msg);
+ }
- while (running_ && rclcpp::ok()) {
- if (!serial_.isOpen()) {
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- continue;
- }
+ // 成员变量
+ std::string serial_port_;
+ int baudrate_;
+ double send_frequency_;
- try {
- // 读取可用数据
- size_t available = serial_.available();
- if (available > 0) {
- std::vector data = serial_.read(available);
-
- // 处理接收到的数据(简单的帧解析)
- for (uint8_t byte : data) {
- buffer.push_back(byte);
-
- // 查找帧头
- if (buffer.size() >= 2 &&
- buffer[0] == FRAME_HEADER_1 &&
- buffer[1] == FRAME_HEADER_2) {
-
- // 等待完整帧
- if (buffer.size() >= FRAME_LENGTH) {
- // 检查帧尾
- if (buffer[FRAME_LENGTH - 1] == FRAME_TAIL) {
- // 验证 CRC
- uint8_t rx_crc = buffer[FRAME_LENGTH - 2];
- uint8_t calc_crc = calculateCRC8(
- buffer.data() + 2, FRAME_LENGTH - 4);
-
- if (rx_crc == calc_crc) {
- RCLCPP_INFO(this->get_logger(),
- "收到有效帧,CRC 验证通过");
- } else {
- RCLCPP_WARN(this->get_logger(),
- "CRC 错误: 接收=%02X, 计算=%02X",
- rx_crc, calc_crc);
- }
- }
-
- // 清空缓冲区,准备下一帧
- buffer.clear();
- }
- } else if (buffer.size() > FRAME_LENGTH) {
- // 缓冲区溢出,清空
- buffer.clear();
- }
- }
- }
- } catch (const std::exception& e) {
- RCLCPP_ERROR(this->get_logger(), "接收异常: %s", e.what());
- is_connected_ = false;
- }
+ int serial_fd_ = -1;
+ bool is_connected_ = false;
+ bool running_ = true;
+ std::thread receive_thread_;
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
- }
- }
-
- void publishStatus()
- {
- auto msg = std_msgs::msg::Bool();
- msg.data = is_connected_ && serial_.isOpen();
- connection_status_pub_->publish(msg);
- }
-
- // 成员变量
- std::string serial_port_;
- int baudrate_;
- double send_frequency_;
-
- serial::Serial serial_;
- bool is_connected_ = false;
- bool running_ = true;
- std::thread receive_thread_;
-
- rclcpp::Publisher::SharedPtr connection_status_pub_;
- rclcpp::TimerBase::SharedPtr send_timer_;
- rclcpp::TimerBase::SharedPtr status_timer_;
+ rclcpp::Publisher::SharedPtr connection_status_pub_;
+ rclcpp::TimerBase::SharedPtr send_timer_;
+ rclcpp::TimerBase::SharedPtr status_timer_;
};
-int main(int argc, char* argv[])
-{
- rclcpp::init(argc, argv);
-
- auto node = std::make_shared();
-
- if (rclcpp::ok()) {
- rclcpp::spin(node);
- }
-
- rclcpp::shutdown();
- return 0;
+int main(int argc, char *argv[]) {
+ rclcpp::init(argc, argv);
+
+ auto node = std::make_shared();
+
+ if (rclcpp::ok()) {
+ rclcpp::spin(node);
+ }
+
+ rclcpp::shutdown();
+ return 0;
}
\ No newline at end of file
diff --git a/tool/99-usb-serial.rules b/tool/99-usb-serial.rules
new file mode 100644
index 0000000..a798e46
--- /dev/null
+++ b/tool/99-usb-serial.rules
@@ -0,0 +1,5 @@
+# CH340 串口模块 (ttyUSB0 -> ttyCH340)
+SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="ttyCH340", MODE="0666", GROUP="dialout"
+
+# IMU 串口模块 (ttyUSB1 -> ttyIMU)
+SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="ttyIMU", MODE="0666", GROUP="dialout"
\ No newline at end of file
diff --git a/tool/setup_usb_permissions.sh b/tool/setup_usb_permissions.sh
new file mode 100755
index 0000000..fdac62e
--- /dev/null
+++ b/tool/setup_usb_permissions.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+# USB 串口设备权限和固定名称设置脚本
+
+set -e
+
+echo "=================================="
+echo "USB 串口设备权限设置"
+echo "=================================="
+
+# 1. 检查当前设备
+echo ""
+echo "当前连接的 USB 串口设备:"
+ls -la /dev/ttyUSB* 2>/dev/null || echo "未发现 ttyUSB 设备"
+
+echo ""
+echo "设备详细信息:"
+for dev in /dev/ttyUSB*; do
+ if [ -e "$dev" ]; then
+ echo "--- $dev ---"
+ udevadm info -a -n "$dev" | grep -E "(idVendor|idProduct|serial)" | head -5
+ fi
+done
+
+# 2. 安装规则文件
+echo ""
+echo "安装 udev 规则"
+sudo cp 99-usb-serial.rules /etc/udev/rules.d/
+
+# 3. 重新加载 udev 规则
+echo "重新加载 udev 规则"
+sudo udevadm control --reload-rules
+sudo udevadm trigger
+
+# 4. 添加用户到 dialout 组
+echo "将当前用户添加到 dialout 组"
+sudo usermod -aG dialout $USER
+
+echo "设置完成,请重新插拔设备"
\ No newline at end of file