基于ROS 2与STM32的机器人底层驱动及键盘控制节点设计
架构概述与模块定位
在传统的机器人软件栈中,高层控制算法与底层硬件驱动通常被严格解耦。然而,针对采用轻量级字符流协议的 STM32 底层固件,我们优化了系统架构,引入了高度集成的驱动网关模式。本文探讨的 keyboard_control_node 节点不仅负责处理人机交互指令,还兼顾高频传感器数据的采集与解析,是连接 ROS 2 上位机与微控制器的核心枢纽。
系统整体上下文架构如下图所示:

多线程并发与IO调度
为确保键盘输入的即时响应与串口数据的不丢失,该节点内部设计了双线程异步协作机制:
- 后台事件监听线程:基于
pynput库构建。该线程以非阻塞模式捕获按键的按下与释放事件。按下时映射为对应的运动指令并更新共享状态;释放时则触发安全机制(Deadman Switch),立即下发停止信号以防止失控。 - 主循环调度线程:依赖
rclpy的定时器机制,以 20Hz(50ms)的频率轮询。下行方向负责将共享状态中的指令转化为 ASCII 字符写入串口;上行方向则持续读取串口缓冲区,按行分割并解析传感器数据帧。
上下行通信协议设计
系统采用非对称的混合通信策略:下行控制指令追求极致的低延迟与简洁,而上行传感器数据则侧重于结构化与易解析性。串口波特率统一配置为 115200 bps。
下行控制指令集 (Host to MCU)
控制端采用单字节字符协议。STM32 固件端配置了 500ms 的指令超时检测,因此上位机需保证至少 2Hz 的心跳发送频率。
| 运动意图 | 触发按键 | 十六进制载荷 | 底层执行动作 | 运动学特征 |
|---|---|---|---|---|
| 直线前进 | W | 0x57 | 四轮同向正转 | 线速度>0,角速度=0 |
| 直线后退 | S | 0x53 | 四轮同向反转 | 线速度<0,角速度=0 |
| 原地左旋 | A | 0x41 | 左侧反转/右侧正转 | 线速度=0,角速度>0 |
| 原地右旋 | D | 0x44 | 左侧正转/右侧反转 | 线速度=0,角速度<0 |
| 紧急制动 | Space | 0x20 | 四轮PWM清零 | 速度强制归零 |
上行数据帧格式 (MCU to Host)
传感器数据以 ASCII CSV 格式回传,使用换行符 \n 作为帧尾。
1. 轮式里程计数据
数据格式:/four_wheel_encoder,{delta_fl},{delta_fr},{delta_rl},{delta_rr},{tick}
以下为重构后的数据解析逻辑示例:
def parse_encoder_frame(raw_bytes):
# 将字节流解码并去除首尾空白字符
text_data = raw_bytes.decode('utf-8').strip()
segments = text_data.split(',')
# 校验帧头与数据长度
if segments[0] == '/four_wheel_encoder' and len(segments) == 6:
# 提取四个轮子的脉冲增量及时间戳
pulse_fl = int(segments[1])
pulse_fr = int(segments[2])
pulse_rl = int(segments[3])
pulse_rr = int(segments[4])
time_tick = int(segments[5])
return {
'fl': pulse_fl, 'fr': pulse_fr,
'rl': pulse_rl, 'rr': pulse_rr,
'timestamp': time_tick
}
return None
注:脉冲增量代表两次采样周期(约20ms)内的编码器计数变化。
2. 惯性测量单元 (IMU) 数据
数据格式:/imu_data,{ax},{ay},{az},{gx},{gy},{gz},{temp},{tick}
回传值为 MPU6050 的原始 ADC 读数,上位机需结合当前配置的加速度计量程与陀螺仪量程,将其转换为标准物理单位($m/s^2$ 与 $rad/s$)。
运动学正解与状态发布
为满足后续 SLAM 建图的需求,节点需在内部完成底盘运动学正解,将离散的编码器脉冲积分转化为连续的全局位姿。
差速底盘里程计推算
假设系统物理参数如下:单圈脉冲数结合轮径与减速比计算出的比例系数 $K_{scale} \approx 1571.0$ 脉冲/米,左右轮距 $W = 0.17m$。
推算流程如下:
- 计算单侧位移:左侧位移 $\Delta d_L = \frac{\Delta_{fl} + \Delta_{rl}}{2} \times K_{scale}^{-1}$,右侧位移 $\Delta d_R = \frac{\Delta_{fr} + \Delta_{rr}}{2} \times K_{scale}^{-1}$。
- 计算中心位移与航向角变化:中心弧长 $\Delta s = \frac{\Delta d_R + \Delta d_L}{2}$,航向角增量 $\Delta \theta = \frac{\Delta d_R - \Delta d_L}{W}$。
- 位姿积分更新:
$x_k = x_{k-1} + \Delta s \cdot \cos(\theta_{k-1} + \frac{\Delta \theta}{2})$
$y_k = y_{k-1} + \Delta s \cdot \sin(\theta_{k-1} + \frac{\Delta \theta}{2})$
$\theta_k = \theta_{k-1} + \Delta \theta$
ROS 2 接口封装
解算后的状态需通过标准接口向系统广播:
- 里程计话题:发布
nav_msgs/msg/Odometry消息至/odom话题,父坐标系设为odom,子坐标系设为base_link。 - 坐标变换树:利用
tf2_ros.TransformBroadcaster持续广播odom到base_link的动态 TF 变换,这是 Cartographer 等建图算法正常工作的先决条件。
系统部署与故障排查
环境配置与启动
在运行节点前,需确保当前用户对串口设备具有读写权限:
# 开放串口设备读写权限
sudo chmod a+rw /dev/ttyUSB0
# 启动键盘控制与底层驱动节点
ros2 run stm32_keyboard_control keyboard_control_node
启动后,可通过以下命令验证 TF 树是否正确构建:
ros2 run tf2_ros tf2_echo odom base_link
若机器人受控但无 TF 输出,表明上行数据解析或里程计解算模块存在缺陷,需重点检查相关代码逻辑。
典型异常分析
- 底盘运动出现周期性卡顿:通常是因为主循环定时器的触发频率过低(低于 2Hz),导致 STM32 固件触发 500ms 超时保护而自动刹车。建议将定时器周期严格控制在 50ms (20Hz)。
- TF 坐标树断开或建图失败:多因
/odom话题未成功发布,或TransformBroadcaster实例化失败引起。需检查节点初始化流程及里程计积分算法是否抛出异常。