从零开始搭建STM32与ROS串口通信系统:30分钟实战指南
在机器人开发领域,嵌入式系统与上层控制系统的通信是核心技术难点之一。本文将手把手教你如何用正点原子STM32精英板与ROS Kinetic建立稳定的串口通信链路,即使你从未接触过ROS或嵌入式开发,也能在30分钟内完成第一个通信demo。
1. 硬件准备与环境搭建
1.1 所需硬件清单
- 主控板:正点原子STM32精英板(兼容F103系列)
- 通信模块:CH340G芯片的USB转TTL模块(PL2303等兼容)
- 连接线材:杜邦线若干(建议使用不同颜色区分)
- 开发环境:
- Windows端:Keil MDK-ARM 5.x
- Linux端:Ubuntu 16.04 + ROS Kinetic
注意:购买USB转TTL模块时,务必确认其支持3.3V电平,避免损坏STM32芯片。
1.2 硬件连接示意图
正确的接线方式是通信成功的前提,以下是典型连接方案:
| STM32引脚 | TTL模块接口 | 连接说明 |
|---|---|---|
| PA9(TX) | RX | 交叉连接 |
| PA10(RX) | TX | 交叉连接 |
| GND | GND | 共地连接 |
# Linux下检测设备是否识别成功 ls /dev/ttyUSB* # 预期输出:/dev/ttyUSB0若未出现设备节点,可能需要安装CH340驱动:
sudo apt-get install build-essential sudo apt-get install linux-headers-$(uname -r) sudo modprobe usbserial sudo modprobe ch3412. 软件环境配置
2.1 STM32工程配置
在Keil中新建工程时,关键配置如下:
- 选择正确的芯片型号:STM32F103ZET6
- 启用USART1外设:
- 波特率:115200
- 数据位:8位
- 停止位:1位
- 无校验位
// 串口初始化代码片段 void uart_init(u32 bound){ // GPIO端口设置 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); }2.2 ROS功能包创建
在Ubuntu终端中执行以下命令序列:
# 创建工作空间 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/ catkin_make # 创建功能包 cd src catkin_create_pkg stm32_comm roscpp std_msgs # 安装依赖 sudo apt-get install ros-kinetic-serial3. 通信协议设计
3.1 帧结构定义
一个健壮的通信协议需要包含以下要素:
| 帧头(2B) | 长度(1B) | 数据(NB) | 校验(1B) | 帧尾(2B) |典型实现方案:
// STM32端协议定义 #pragma pack(1) typedef struct { uint8_t header[2]; // 0x55 0xAA uint8_t length; // 数据域长度 int16_t left_vel; // 左轮速度 int16_t right_vel; // 右轮速度 uint8_t ctrl_flag; // 控制标志位 uint8_t checksum; // CRC8校验 uint8_t ender[2]; // 0x0D 0x0A } CommFrame; #pragma pack()3.2 CRC校验实现
// ROS端的CRC8校验函数 unsigned char getCrc8(unsigned char *ptr, unsigned short len) { unsigned char crc = 0; while(len--) { crc ^= *ptr++; for(unsigned char i = 0; i < 8; i++) { if(crc & 0x01) crc = (crc >> 1) ^ 0x8C; else crc >>= 1; } } return crc; }4. 双向通信实现
4.1 STM32数据收发
在STM32中实现中断接收和主动发送:
// 接收中断服务函数 void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uint8_t ch = USART_ReceiveData(USART1); // 协议解析处理... USART_ClearITPendingBit(USART1, USART_IT_RXNE); } } // 数据发送函数 void send_to_ros(int16_t v1, int16_t v2, uint8_t flag) { CommFrame frame; frame.header[0] = 0x55; frame.header[1] = 0xAA; frame.length = 5; // 2+2+1 frame.left_vel = v1; frame.right_vel = v2; frame.ctrl_flag = flag; frame.ender[0] = 0x0D; frame.ender[1] = 0x0A; uint8_t *p = (uint8_t*)&frame; frame.checksum = getCrc8(p+2, frame.length+1); // 计算校验 for(int i=0; i<sizeof(CommFrame); i++) { while(!(USART1->SR & USART_SR_TXE)); USART_SendData(USART1, p[i]); } }4.2 ROS节点实现
创建serial_node.cpp实现与STM32的通信:
#include <ros/ros.h> #include <serial/serial.h> serial::Serial ser; void callback(const std_msgs::String::ConstPtr& msg) { ROS_INFO("Sending: %s", msg->data.c_str()); ser.write(msg->data); } int main(int argc, char** argv) { ros::init(argc, argv, "serial_node"); ros::NodeHandle nh; try { ser.setPort("/dev/ttyUSB0"); ser.setBaudrate(115200); serial::Timeout to = serial::Timeout::simpleTimeout(1000); ser.setTimeout(to); ser.open(); } catch (serial::IOException& e) { ROS_ERROR("Unable to open port"); return -1; } ros::Subscriber sub = nh.subscribe("stm32_command", 1000, callback); ros::Publisher pub = nh.advertise<std_msgs::String>("stm32_feedback", 1000); while(ros::ok()) { if(ser.available()) { std_msgs::String result; result.data = ser.read(ser.available()); pub.publish(result); } ros::spinOnce(); } }5. 调试技巧与常见问题
5.1 权限问题解决方案
每次重新插拔USB设备后都需要重新设置权限:
# 永久解决方案(创建udev规则) echo 'KERNEL=="ttyUSB*", MODE="0666"' | sudo tee /etc/udev/rules.d/50-stm32.rules sudo service udev restart5.2 通信故障排查步骤
物理层检查
- 确认TX-RX交叉连接
- 测量USB转TTL模块供电电压(3.3V)
端口检测
# 查看端口信息 dmesg | grep tty # 测试端口通信 cat /dev/ttyUSB0协议分析
- 使用逻辑分析仪抓取波形
- 在STM32端添加LED指示灯辅助调试
5.3 性能优化建议
- 增加超时重传机制
- 实现数据分包处理
- 添加心跳包检测连接状态
- 使用DMA传输减轻CPU负担
在完成所有步骤后,你应该能在ROS中看到来自STM32的实时数据流,同时可以通过ROS话题向STM32发送控制指令。这种通信框架可扩展应用于机器人运动控制、传感器数据采集等多种场景。