MOVE_AI/src/rm_serial_driver/docs/USER_GUIDE.md
2026-03-04 17:50:55 +08:00

8.4 KiB
Raw Blame History

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