8.4 KiB
8.4 KiB
RM Serial Driver 使用文档
功能特性
✅ 稳定可靠的串口通信
- 自动重连机制,断线后持续尝试重连
- 完善的异常处理,不会因串口异常而崩溃
- CRC16 校验保证数据完整性
✅ 灵活的协议设计
- 支持多种数据包类型(MCU、裁判系统、AI)
- 易于扩展新的数据包类型
- 详细的协议修改指南
✅ 实时统计监控
- 接收/发送数据包计数
- CRC 错误统计
- 连接状态监控
✅ 高性能
- 独立接收线程,不阻塞主程序
- 支持多实例,可同时管理多个串口
快速开始
1. 安装依赖
# 安装 serial 库
git clone https://github.com/ZhaoXiangBox/serial.git
cd serial
mkdir build && cd build
cmake ..
make
sudo make install
2. 编译
cd ~/MOVE_AI
colcon build --packages-select rm_serial_driver
source install/setup.bash
3. 配置参数
编辑 config/serial_config.yaml:
/rm_serial_driver:
ros__parameters:
device_name: /dev/ttyACM0 # 串口设备
baud_rate: 115200 # 波特率
reconnect_interval_ms: 1000 # 重连间隔(毫秒)
enable_statistics: true # 启用统计信息
topic_prefix: serial # 话题前缀
4. 启动驱动
单个串口
ros2 run rm_serial_driver rm_serial_driver --ros-args \
-p device_name:=/dev/ttyACM0 \
-p baud_rate:=115200 \
-p topic_prefix:=serial
多个串口(使用 launch 文件)
ros2 launch rm_serial_driver rm_serial_driver.launch.py
5. 查看话题
# 列出所有话题
ros2 topic list
# 查看 MCU 数据
ros2 topic echo /serial/data_mcu
# 查看裁判系统数据
ros2 topic echo /serial/data_ref
# 发送 AI 控制命令
ros2 topic pub /serial/data_ai rm_msgs/msg/DataAI "{...}"
话题接口
订阅的话题
| 话题名 | 消息类型 | 说明 |
|---|---|---|
{prefix}/data_ai |
rm_msgs/msg/DataAI |
AI 控制命令,发送给单片机 |
发布的话题
| 话题名 | 消息类型 | 说明 |
|---|---|---|
{prefix}/data_mcu |
rm_msgs/msg/DataMCU |
单片机姿态数据(四元数、欧拉角) |
{prefix}/data_ref |
rm_msgs/msg/DataRef |
裁判系统数据(血量、比赛进度) |
参数说明
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
device_name |
string | /dev/ttyACM0 |
串口设备路径 |
baud_rate |
int | 115200 |
波特率 |
reconnect_interval_ms |
int | 1000 |
重连间隔(毫秒) |
enable_statistics |
bool | true |
是否启用统计信息 |
topic_prefix |
string | serial |
ROS 话题前缀 |
协议格式
数据包结构
所有数据包都遵循以下格式:
+----+----------+-------+
| ID | Data | CRC |
+----+----------+-------+
| 1B | N | 2B |
- ID: 1 字节,数据包类型标识
- Data: N 字节,实际数据内容
- CRC: 2 字节,CRC16-CCITT 校验码
数据包类型
1. MCU 数据包 (ID: 0xC4)
从单片机接收的姿态数据:
struct DataMCU {
struct {
float q0, q1, q2, q3; // 四元数
} quat;
struct {
float yaw, pit, rol; // 欧拉角(度)
} gimbal;
uint8_t notice; // 控制命令
};
2. 裁判系统数据包 (ID: 0xA8)
从裁判系统接收的比赛数据:
struct DataReferee {
uint16_t remain_hp; // 剩余血量
uint8_t game_progress : 4; // 比赛进度
uint16_t stage_remain_time; // 剩余时间(秒)
};
3. AI 控制数据包
发送给单片机的控制命令:
struct DataAI {
struct {
float yaw, pit, rol; // 欧拉角增量
} delta_eulr;
struct {
float vx, vy, wz; // 位置增量
} delta_pos;
uint8_t notice; // 控制命令
};
使用示例
Python 示例
接收 MCU 数据
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from rm_msgs.msg import DataMCU
class MCUListener(Node):
def __init__(self):
super().__init__('mcu_listener')
self.subscription = self.create_subscription(
DataMCU,
'serial/data_mcu',
self.mcu_callback,
10)
def mcu_callback(self, msg):
self.get_logger().info(
f'姿态: yaw={msg.gimbal.yaw:.2f}, '
f'pitch={msg.gimbal.pit:.2f}, '
f'roll={msg.gimbal.rol:.2f}')
def main():
rclpy.init()
node = MCUListener()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
发送 AI 控制命令
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from rm_msgs.msg import DataAI
class AIController(Node):
def __init__(self):
super().__init__('ai_controller')
self.publisher = self.create_publisher(DataAI, 'serial/data_ai', 10)
self.timer = self.create_timer(0.1, self.send_command)
def send_command(self):
msg = DataAI()
msg.delta_eulr.yaw = 0.1
msg.delta_eulr.pit = 0.0
msg.delta_eulr.rol = 0.0
msg.delta_pos.vx = 1.0
msg.delta_pos.vy = 0.0
msg.delta_pos.wz = 0.0
msg.notice = 1
self.publisher.publish(msg)
def main():
rclpy.init()
node = AIController()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
C++ 示例
#include <rclcpp/rclcpp.hpp>
#include "rm_msgs/msg/data_mcu.hpp"
#include "rm_msgs/msg/data_ai.hpp"
class RMController : public rclcpp::Node
{
public:
RMController() : Node("rm_controller")
{
// 订阅 MCU 数据
mcu_sub_ = create_subscription<rm_msgs::msg::DataMCU>(
"serial/data_mcu", 10,
std::bind(&RMController::mcu_callback, this, std::placeholders::_1));
// 发布 AI 控制命令
ai_pub_ = create_publisher<rm_msgs::msg::DataAI>("serial/data_ai", 10);
// 定时发送控制命令
timer_ = create_wall_timer(
std::chrono::milliseconds(100),
std::bind(&RMController::send_command, this));
}
private:
void mcu_callback(const rm_msgs::msg::DataMCU::SharedPtr msg)
{
RCLCPP_INFO(get_logger(), "姿态: yaw=%.2f, pitch=%.2f, roll=%.2f",
msg->gimbal.yaw, msg->gimbal.pit, msg->gimbal.rol);
}
void send_command()
{
auto msg = rm_msgs::msg::DataAI();
msg.delta_eulr.yaw = 0.1;
msg.delta_pos.vx = 1.0;
msg.notice = 1;
ai_pub_->publish(msg);
}
rclcpp::Subscription<rm_msgs::msg::DataMCU>::SharedPtr mcu_sub_;
rclcpp::Publisher<rm_msgs::msg::DataAI>::SharedPtr ai_pub_;
rclcpp::TimerBase::SharedPtr timer_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<RMController>());
rclcpp::shutdown();
return 0;
}
故障排查
串口无法打开
# 检查串口设备
ls -l /dev/ttyUSB* /dev/ttyACM*
# 添加用户到 dialout 组
sudo usermod -aG dialout $USER
# 注销后重新登录
# 检查串口权限
sudo chmod 666 /dev/ttyACM0
查看详细日志
ros2 run rm_serial_driver rm_serial_driver --ros-args --log-level debug
测试串口通信
# 使用 minicom
sudo apt install minicom
minicom -D /dev/ttyACM0 -b 115200
# 或使用 screen
screen /dev/ttyACM0 115200
常见错误
1. "串口打开失败"
- 检查设备路径是否正确
- 检查串口权限
- 确认设备已连接
2. "CRC 校验失败"
- 确认单片机和上位机使用相同的 CRC 算法
- 检查数据包结构是否匹配
- 验证字节序是否一致
3. "串口意外关闭"
- 检查 USB 线缆连接
- 查看系统日志:
dmesg | tail - 确认单片机程序正常运行
性能优化
1. 调整接收缓冲区
在 receive_data() 中:
// 增大缓冲区限制
if (serial_buffer_.size() > 8192) // 默认 4096
{
serial_buffer_.clear();
}
2. 调整统计间隔
// 减少统计频率
statistics_timer_ = this->create_wall_timer(
std::chrono::seconds(10), // 默认 5 秒
std::bind(&RMSerialDriver::statistics_timer_callback, this));
3. 禁用调试日志
生产环境中使用 INFO 级别:
ros2 run rm_serial_driver rm_serial_driver --ros-args --log-level info
更多文档
许可证
Apache License 2.0