ROS2 生态里最让人头疼的环节,从来不是写节点逻辑,而是怎么让那些跑在 MCU 上的传感器、执行器真正融入 DDS 的通信网络。STM32F407 这颗芯片在工业控制和机器人底层驱动里出镜率极高,但它的资源跑不动完整的 ROS2 客户端库,过去大家要么用串口自定义协议硬扛,要么干脆把 MCU 当成一个哑设备。micro_ros 的出现改变了这个局面——它把 ROS2 的通信能力裁剪到能在几十 KB 内存里运行,让 F407 这种级别的芯片也能直接发布话题、订阅指令、甚至提供 Service 服务。这篇内容面向的是已经有一定 STM32 开发基础、想把手里的板子接入 ROS2 网络的嵌入式工程师,我会从传输层选型一路讲到实际跑通话题通信的完整过程,把踩过的坑和验证过的配置都摊开来说。
1. 为什么 F407 接入 ROS2 值得认真做传输层选型
1.1 micro_ros 在 F407 上的资源账本
STM32F407 的典型配置是 192KB SRAM、1MB Flash,主频 168MHz,带 FPU 和 DSP 指令集。这个资源放在裸机开发里相当宽裕,但 micro_ros 的默认配置会吃掉不少内存。我实测过几个关键数字:micro_ros 的 rcl 层加上 rmw_microxrcedds 中间件,在只启用一个话题发布者的情况下,静态内存占用大约在 12KB 到 18KB 之间,具体取决于消息类型的大小和 XRCE-DDS 的会话缓冲区配置。如果再加上一个订阅者和一个 Service 服务端,内存会逼近 30KB。这还没算上用户自己的应用逻辑和协议栈。
所以第一件事就是算清楚你的内存预算。F407 的 192KB SRAM 分成了 112KB + 16KB + 64KB 三个区域,其中 64KB 是 CCM RAM,只能通过 CPU 内核总线访问,DMA 用不了。micro_ros 的缓冲区如果放在 CCM 里,网络外设的 DMA 就没法直接搬运数据,这一点在配置链接脚本的时候必须注意。我的做法是把 micro_ros 的堆和会话缓冲区放在主 SRAM 区,把用户自己的大数据缓冲区放到 CCM 里,各取所需。
另一个容易被忽略的是 Flash 占用。micro_ros 的静态库加上 XRCE-DDS 的代码,编译出来大概在 80KB 到 120KB 之间,取决于你启用了多少种消息类型。F407 的 1MB Flash 完全够用,但如果你用的是 F407VET6 这种 512KB 的版本,就要留意别把消息类型开得太杂。
1.2 三种传输方式的真实对比
micro_ros 支持多种传输层,在 F407 上最常用的是串口(UART)、UDP over Ethernet 和 CAN。这三种方式我都在实际项目里用过,各自的适用场景差别很大。
| 传输方式 | 典型延迟 | 带宽 | 硬件复杂度 | 适用场景 |
|---|---|---|---|---|
| UART | 1-5ms | 115200-921600bps | 极低 | 点对点、调试、低频率传感器 |
| UDP over Ethernet | 0.5-2ms | 10/100Mbps | 中(需 PHY) | 多节点、高频数据、图像传输 |
| CAN | 2-10ms | 1Mbps | 中(需收发器) | 工业现场、多节点总线、抗干扰 |
串口方式最简单,一根 USB-TTL 就能把 F407 和跑 ROS2 的上位机连起来。但串口的带宽是硬伤,115200bps 下每秒最多传十几KB的有效载荷,稍微大一点的消息类型就会堵。而且串口是点对点的,一个 F407 只能连一个上位机,多节点场景下需要多个串口或者用 USB Hub 扩展。
以太网方式是我最推荐的。F407 自带 MAC 控制器,配合 RMII 接口的 PHY 芯片(比如 LAN8720 或 DP83848)就能跑 100Mbps。UDP 传输的延迟低、带宽大,而且天然支持多节点——每个 F407 分配一个 IP,上位机通过同一个网段就能和所有节点通信。代价是硬件上要多画一个 PHY 电路,RMII 的时钟线要走等长,50MHz 的 REF_CLK 对布线有一定要求。
CAN 方式适合工业现场。F407 有两个 CAN 控制器,配合 6N137 或者 ADuM1201 隔离收发器就能组网。CAN 的抗干扰能力强,总线长度可以到几十米甚至上百米,但带宽只有 1Mbps,而且 micro_ros 的 CAN 传输层实现相对复杂,需要自己处理帧的分片和重组。
1.3 选型决策的实际依据
我的建议是:如果你只是做单节点验证或者低频传感器采集,串口足够;如果你要做多节点协同或者有视频、点云这类大数据流,直接上以太网;如果现场环境电磁干扰严重、节点分散在几十米范围内,选 CAN。
这里有个实际经验:很多人一开始图省事用串口,结果项目做到一半发现带宽不够,再换以太网就要重新画板、改代码。所以如果你对项目的最终形态有预期,传输层选型要提前想清楚。我现在的习惯是,只要板子上有以太网 PHY,就优先用以太网,串口只留作调试和固件升级用。
2. 从零搭建 micro_ros 的交叉编译环境
2.1 工具链的版本匹配问题
micro_ros 的编译依赖 ROS2 的构建系统,所以第一步是在 Ubuntu 上装好 ROS2。我用的版本是 Humble,对应 Ubuntu 22.04。这里有个坑:micro_ros 的各个版本和 ROS2 发行版有严格的对应关系,Humble 对应的是 micro_ros 的 humble 分支,如果你混用了 Foxy 的 micro_ros 和 Humble 的 ROS2,编译能过但运行时会出各种奇怪的通信问题。
安装 ROS2 Humble 的步骤网上很多,这里只强调几个关键点。首先是 locale 设置,如果locale命令输出的不是en_US.UTF-8,ros2 的命令行工具会报编码错误。其次是源的选择,用官方源还是国内镜像源会影响下载速度,但不要混用。最后是rosdep的初始化,这一步如果卡住,后面编译 micro_ros 时会找不到依赖。
# 检查 locale locale # 如果不是 en_US.UTF-8,执行: sudo apt update && sudo apt install locales sudo locale-gen en_US en_US.UTF-8 sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 export LANG=en_US.UTF-8装完 ROS2 之后,还需要安装 micro_ros 的构建工具micro_ros_setup。这个工具提供了一系列脚本,可以自动下载 micro_ros 的源码、配置工具链、编译静态库。
# 创建工作空间 mkdir -p ~/microros_ws/src cd ~/microros_ws # 下载 micro_ros_setup git clone -b humble https://github.com/micro-ROS/micro_ros_setup.git src/micro_ros_setup # 安装依赖 rosdep update rosdep install --from-paths src --ignore-src -y # 编译 colcon build source install/local_setup.bash2.2 交叉编译工具链的配置
F407 是 ARM Cortex-M4 内核,需要 arm-none-eabi 工具链。Ubuntu 的 apt 源里有gcc-arm-none-eabi,但版本可能比较老。我建议从 ARM 官网下载最新的工具链,解压后把bin目录加到 PATH 里。
# 下载工具链(以 10.3-2021.10 为例) wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/10.3-2021.10/gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 tar -xjf gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 export PATH=$PATH:$(pwd)/gcc-arm-none-eabi-10.3-2021.10/bin # 验证 arm-none-eabi-gcc --version接下来用micro_ros_setup创建 F407 的固件工作空间。这里需要指定工具链文件,micro_ros 提供了stm32f4的参考配置,但实际项目中往往需要根据自己的板子调整。
# 创建固件工作空间 ros2 run micro_ros_setup create_firmware_ws.sh # 配置工具链 ros2 run micro_ros_setup configure_firmware.sh # 编译 ros2 run micro_ros_setup build_firmware.shconfigure_firmware.sh会交互式地询问传输方式、消息类型等配置。如果你用的是自定义板子,需要修改firmware/mcu_ws/下的colcon.meta文件,调整各个包的编译选项。
2.3 自定义消息类型的处理
micro_ros 默认只编译了一部分标准消息类型,如果你要用自定义的.msg文件,需要把它们放到firmware/mcu_ws/下,并在colcon.meta里启用对应的包。这里有个细节:自定义消息的依赖关系必须完整,如果 A.msg 里引用了 B.msg,两个都要放进去,否则编译时会报找不到类型。
我遇到过一个典型问题:自定义消息里用了std_msgs/Header,但colcon.meta里没有启用std_msgs,编译能过但链接时会报未定义符号。解决办法是在colcon.meta里把std_msgs的rosidl_generator_c和rosidl_typesupport_microxrcedds_c都打开。
{ "names": { "std_msgs": { "cmake-args": [ "-DBUILD_TESTING=OFF", "-DROSIDL_GENERATOR_C_BUILD_TESTING=OFF" ] }, "my_custom_msgs": { "cmake-args": [ "-DBUILD_TESTING=OFF" ] } } }编译完成后,firmware/mcu_ws/install/下会生成静态库文件,包括libmicroros.a和各个消息类型的库。把这些库和头文件拷贝到你的 STM32 工程里,就可以在裸机代码里调用 micro_ros 的 API 了。
3. STM32CubeMX 里的底层外设配置要点
3.1 以太网外设的 RMII 配置
如果你选的是以太网传输,CubeMX 里的 ETH 配置有几个关键点。首先是 PHY 地址,LAN8720 的默认地址是 0,DP83848 是 1,这个在HAL_ETH_Init里要设对。其次是 RMII 的 REF_CLK,F407 的 PA1 引脚可以输出 50MHz 时钟给 PHY,也可以由 PHY 提供时钟给 MCU。两种方式在 CubeMX 里的配置不同:如果 MCU 输出时钟,PA1 要配置为RCC_MCO1并选择 HSE 或 PLL 作为时钟源;如果 PHY 提供时钟,PA1 要配置为ETH_RMII_REF_CLK输入。
我实际用 LAN8720 的时候,遇到过 REF_CLK 配置错误导致 PHY 无法通信的问题。现象是HAL_ETH_Init返回HAL_ERROR,用示波器量 PA1 没有 50MHz 信号。后来发现是 CubeMX 里 PA1 的复用功能选错了,应该选ETH_RMII_REF_CLK而不是RCC_MCO1。这个细节在 CubeMX 的图形界面里不太直观,需要点开 PA1 的引脚配置仔细看。
MDIO 和 MDC 是管理接口,用来读写 PHY 的寄存器。MDC 的时钟不能超过 2.5MHz,CubeMX 里可以配置分频系数。我一般设成 HCLK/42,168MHz 下大约是 4MHz,稍微超了一点但实测没问题。如果 PHY 对 MDC 时钟敏感,可以再降一档。
3.2 串口和 DMA 的配合
串口传输方式下,DMA 是必须的。micro_ros 的串口传输层会频繁地读写数据,如果用中断方式,CPU 会被大量的小数据包打断,影响实时性。我一般给 UART 配两个 DMA 通道:一个用于 TX,一个用于 RX,都设成循环模式。
CubeMX 里配置 UART DMA 的时候,要注意 DMA 请求的映射关系。F407 的 USART1_TX 对应 DMA2 Stream7 Channel4,USART1_RX 对应 DMA2 Stream5 Channel4。这个映射在参考手册的 DMA 请求映射表里有,CubeMX 会自动处理,但如果你手动改过 DMA 配置,要确认一下没搞错。
还有一个细节:micro_ros 的串口传输层需要知道 UART 的句柄和 DMA 句柄。在microros_transports.h里,你需要实现几个回调函数,把 HAL 的 UART 操作和 micro_ros 的传输接口对接起来。
bool cubemx_transport_open(struct uxrCustomTransport * transport) { // 启动 UART DMA 接收 HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE); return true; } bool cubemx_transport_write(struct uxrCustomTransport * transport, const uint8_t * buf, size_t len, uint8_t * err) { HAL_UART_Transmit_DMA(&huart1, (uint8_t *)buf, len); return true; }3.3 时钟树和中断优先级
F407 的时钟树配置直接影响 micro_ros 的定时器精度。ROS2 的话题通信依赖时间戳,如果 MCU 的时钟不准,上位机收到的消息时间戳会漂移。我一般把 HSE 设成 8MHz 外部晶振,PLL 倍频到 168MHz,这样 SysTick 的精度足够。
中断优先级方面,以太网的 ETH 中断和串口的 DMA 中断要设得比 SysTick 高,否则通信数据可能会丢。但也不能设得太高,否则会影响用户任务的执行。我的经验是:ETH 中断设成优先级 5,UART DMA 设成 6,SysTick 设成 15(最低),用户任务用 FreeRTOS 的话,任务优先级根据实际需求分配。
这里有个容易踩的坑:如果你用了 FreeRTOS,micro_ros 的默认配置可能和 FreeRTOS 的内存管理冲突。micro_ros 有自己的内存分配器,如果和 FreeRTOS 的heap_4同时使用,可能会出现内存碎片。解决办法是在colcon.meta里把 micro_ros 的内存分配器设成malloc,让 FreeRTOS 统一管理堆。
4. 跑通第一个话题通信的完整过程
4.1 编写 micro_ros 节点代码
在 STM32 工程里创建一个app.c文件,实现一个简单的发布者节点。这个节点每隔 100ms 发布一条std_msgs/Int32消息,内容是一个递增的计数器。
#include <rcl/rcl.h> #include <rclc/rclc.h> #include <rclc/executor.h> #include <std_msgs/msg/int32.h> rcl_publisher_t publisher; std_msgs__msg__Int32 msg; rclc_executor_t executor; rclc_support_t support; rcl_allocator_t allocator; rcl_node_t node; rcl_timer_t timer; void timer_callback(rcl_timer_t * timer, int64_t last_call_time) { (void) last_call_time; if (timer != NULL) { msg.data++; rcl_publish(&publisher, &msg, NULL); } } void appMain(void * argument) { allocator = rcl_get_default_allocator(); // 初始化支持结构 rclc_support_init(&support, 0, NULL, &allocator); // 创建节点 rclc_node_init_default(&node, "stm32f407_node", "", &support); // 创建发布者 rclc_publisher_init_default( &publisher, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32), "stm32_counter"); // 创建定时器 rclc_timer_init_default( &timer, &support, RCL_MS_TO_NS(100), timer_callback); // 创建执行器 rclc_executor_init(&executor, &support.context, 1, &allocator); rclc_executor_add_timer(&executor, &timer); msg.data = 0; // 主循环 while (1) { rclc_executor_spin_some(&executor, RCL_MS_TO_NS(100)); // 这里可以加入用户自己的任务 } }这段代码的结构和 ROS2 的 C++ 节点很像,只是 API 换成了 C 版本。rclc_executor_spin_some是非阻塞的,每次调用处理一次执行器里的事件,然后返回。这样用户可以在主循环里插入自己的逻辑,不会因为 spin 阻塞而影响其他任务。
4.2 上位机的 agent 配置
micro_ros 的通信需要一个 agent 在上位机运行,负责把 XRCE-DDS 协议转换成标准的 DDS 通信。agent 的安装很简单:
# 安装 micro_ros_agent sudo apt install ros-humble-micro-ros-agent # 串口方式启动 ros2 run micro_ros_agent micro_ros_agent serial --dev /dev/ttyUSB0 -b 115200 # 以太网方式启动 ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888串口方式下,--dev指定设备节点,-b指定波特率。以太网方式下,--port指定监听的 UDP 端口,默认是 8888。agent 启动后,会等待 MCU 的连接请求。
这里有个实际经验:agent 的启动顺序很重要。如果 MCU 先上电、agent 后启动,MCU 会一直重连直到 agent 就绪。但如果 agent 先启动、MCU 后上电,MCU 第一次连接就能成功。我一般先启动 agent,再给 MCU 上电,这样调试信息更清晰。
4.3 验证通信是否正常
agent 启动后,另开一个终端,用ros2 topic list查看话题列表。如果一切正常,应该能看到/stm32_counter这个话题。然后用ros2 topic echo /stm32_counter查看消息内容,应该能看到递增的整数。
# 查看话题列表 ros2 topic list # 输出: # /parameter_events # /rosout # /stm32_counter # 查看消息内容 ros2 topic echo /stm32_counter # 输出: # data: 1 # --- # data: 2 # ---如果ros2 topic list看不到话题,先检查 agent 的日志输出。agent 会打印连接状态和错误信息,常见的错误包括:波特率不匹配、串口设备权限不足、UDP 端口被占用等。串口权限问题可以用sudo usermod -aG dialout $USER解决,然后重新登录。
以太网方式下,如果 agent 收不到数据,先ping一下 MCU 的 IP,确认网络层是通的。然后检查防火墙有没有挡住 UDP 8888 端口。我遇到过 Ubuntu 的 ufw 默认规则挡住了 UDP 包,sudo ufw allow 8888/udp就好了。
5. 实际项目中绕不开的几个坑
5.1 内存溢出导致的随机崩溃
micro_ros 的内存分配失败不会立刻崩溃,而是会在某个不确定的时刻触发 HardFault。这种问题最难查,因为崩溃点往往和真正的内存耗尽点隔了很远。我的排查方法是:在colcon.meta里打开 micro_ros 的内存调试选项,让它在分配失败时打印日志。
{ "names": { "rmw_microxrcedds": { "cmake-args": [ "-DRMW_MICROXRCEDDS_DEBUG=ON" ] } } }打开调试后,如果内存分配失败,串口会打印类似Failed to allocate memory的日志。这时候就要检查你的缓冲区配置了。micro_ros 的几个关键缓冲区大小在microros_allocators.h里定义,包括XRCE_BUFFER_SIZE、XRCE_HISTORY_SIZE等。默认值偏小,如果消息类型比较大或者发布频率高,需要适当调大。
我的一般配置是:XRCE_BUFFER_SIZE设成 2048,XRCE_HISTORY_SIZE设成 4,XRCE_MAX_TOPICS设成 8。这样在 F407 上跑两三个话题的发布订阅没问题。如果还要加 Service,XRCE_MAX_SERVICES也要相应调大。
5.2 时间同步的精度问题
ROS2 的消息时间戳默认来自 MCU 的 SysTick,但 SysTick 的精度受晶振影响。如果 MCU 用的是内部 RC 振荡器,时间戳漂移会很明显,上位机做数据融合时会出问题。解决办法是外接晶振,并且在 micro_ros 里启用时间同步。
micro_ros 支持从 agent 同步时间,需要在rclc_support_init之后调用rmw_uros_sync_session。这个函数会向 agent 请求当前时间,然后计算 MCU 时间和 agent 时间的偏移量。之后发布的消息时间戳会自动加上这个偏移量。
// 同步时间,超时 1000ms rmw_uros_sync_session(1000);实测下来,同步一次之后,时间戳的误差可以控制在几毫秒以内。如果 MCU 的晶振精度高,同步一次可以管很久;如果晶振精度差,建议每隔几分钟重新同步一次。
5.3 以太网 PHY 的硬件调试
以太网方式下,PHY 的硬件调试是最容易卡住的地方。我遇到过几种典型问题:PHY 的复位引脚没接对、RMII 的差分线走线太长导致信号质量差、PHY 的地址配置错误。
排查 PHY 问题的第一步是量时钟。用示波器看 REF_CLK 引脚有没有 50MHz 信号,如果没有,检查 MCU 的时钟配置和 PHY 的时钟输入选择。第二步是量 MDC 和 MDIO,看 MCU 有没有在读写 PHY 寄存器。如果 MDC 没有波形,说明 ETH 外设没有正确初始化。
如果时钟和 MDIO 都正常,但HAL_ETH_Init还是失败,可以用HAL_ETH_ReadPHYRegister读一下 PHY 的 ID 寄存器。LAN8720 的 ID 是 0x0007C0F1,DP83848 是 0x20005C90。如果读出来的 ID 不对,说明 PHY 地址配错了或者 PHY 没工作。
还有一个隐蔽的问题:RMII 的 REF_CLK 如果由 PHY 提供,MCU 的 PA1 要配置成输入模式,并且要在 CubeMX 里把 ETH 的时钟源设成ETH_RMII_REF_CLK。如果设成了RCC_MCO1,MCU 会试图输出时钟,和 PHY 的输出冲突,导致 PHY 不工作。
6. 从单节点到多节点:扩展时的架构考虑
6.1 多节点通信的 IP 规划
以太网方式下,每个 F407 节点需要一个独立的 IP。如果直接用 DHCP,节点多了之后 IP 不固定,上位机的配置会很麻烦。我的做法是给每个节点分配静态 IP,在代码里写死,或者通过拨码开关选择预设的 IP 配置。
IP 规划要考虑网段和子网掩码。如果节点数量少,用一个 C 类网段就够了,比如 192.168.1.0/24,MCU 节点从 192.168.1.100 开始分配,上位机用 192.168.1.10。如果节点数量多,可以划分子网,但 micro_ros 的 agent 默认只监听一个网段,跨网段需要配置路由。
还有一个细节:micro_ros 的 UDP 传输层默认使用 8888 端口,如果多个 agent 在同一台机器上运行,端口会冲突。解决办法是给每个 agent 指定不同的端口,MCU 端也要相应修改目标端口。
6.2 话题命名和 QoS 配置
多节点场景下,话题命名要有统一的规范。我一般用/robot_name/sensor_name的格式,比如/arm/joint1_position、/base/ultrasonic。这样在 rviz2 或者 ros2 topic list 里看起来清晰,也方便用通配符过滤。
QoS 配置是另一个容易忽略的点。micro_ros 默认使用rmw_qos_profile_default,可靠性是RELIABLE,历史深度是 10。如果 MCU 的内存有限,可以把历史深度降到 1 或者 2,减少缓冲区占用。如果数据可以容忍丢包,把可靠性改成BEST_EFFORT也能省内存。
// 自定义 QoS 配置 rmw_qos_profile_t qos_profile = rmw_qos_profile_default; qos_profile.reliability = RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT; qos_profile.depth = 2; rclc_publisher_init( &publisher, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32), "stm32_counter", &qos_profile);6.3 固件升级的考虑
项目部署之后,固件升级是个绕不开的问题。F407 支持通过 UART 或者 USB 进行 ISP 升级,也支持通过以太网进行 IAP 升级。如果节点安装在难以触及的位置,以太网 IAP 是最方便的。
实现以太网 IAP 的思路是:在 Flash 里划分两个区域,一个放 bootloader,一个放应用程序。bootloader 负责接收上位机发来的固件包,写入应用程序区,然后跳转执行。micro_ros 的 agent 可以配合一个自定义的 ROS2 节点,把固件包通过话题发给 MCU。
这里有个实际经验:IAP 升级过程中要确保通信不中断,所以 bootloader 里的网络协议栈要尽量简单,不要依赖 micro_ros。我一般用裸机的 lwIP 加上一个简单的 UDP 协议来传输固件,升级完成后再跳转到 micro_ros 应用。
7. 调试工具和日常维护的实用技巧
7.1 用 ros2 topic hz 和 bw 做性能评估
ros2 topic hz可以查看话题的发布频率,ros2 topic bw可以查看带宽占用。这两个命令在调试通信性能时非常有用。比如你设定了 100ms 的定时器,但ros2 topic hz显示只有 5Hz,说明有消息丢失或者 MCU 的处理速度跟不上。
# 查看发布频率 ros2 topic hz /stm32_counter # 查看带宽 ros2 topic bw /stm32_counter如果频率不稳定,先检查 MCU 的定时器配置。micro_ros 的定时器精度受 SysTick 影响,如果 SysTick 被其他中断打断,定时器回调会延迟。解决办法是把 SysTick 的优先级设到最低,让通信中断优先处理。
7.2 串口调试的日志输出
micro_ros 支持通过串口输出调试日志,但如果你用串口做通信传输,日志和通信数据会混在一起。解决办法是用一个独立的串口做日志输出,或者用 SWO(Serial Wire Output)输出日志。SWO 需要 ST-Link 支持,在 CubeMX 里启用TRACESWO引脚,然后在代码里用ITM_SendChar输出日志。
我一般用 SWO 输出 micro_ros 的内部日志,用 UART 做通信。这样调试信息不会干扰通信数据,而且 SWO 的速度比 UART 快,适合输出大量日志。
7.3 长期运行的稳定性观察
micro_ros 在 F407 上长期运行,最常见的问题是内存泄漏和连接断开。内存泄漏通常是因为消息分配后没有释放,或者执行器的回调里创建了新的对象但没有销毁。连接断开可能是因为网络抖动或者 agent 重启。
我的做法是在主循环里加一个心跳检测,定期检查rmw_uros_ping_agent的返回值。如果连续几次 ping 不通,就重新初始化 micro_ros 的连接。
// 心跳检测 if (rmw_uros_ping_agent(100, 3) != RMW_RET_OK) { // 重新初始化 rclc_executor_fini(&executor); rcl_node_fini(&node); rclc_support_fini(&support); // 重新初始化... }这个机制在实际项目中救过我好几次。有一次 agent 因为系统更新重启了,MCU 端没有重连,导致数据中断了几个小时。加了心跳检测之后,agent 恢复后 MCU 会自动重连,不需要人工干预。
7.4 关于消息类型选择的经验
最后说一个关于消息类型的经验。micro_ros 支持的消息类型越多,编译出来的固件越大,内存占用也越高。我建议只启用实际用到的消息类型,不要图省事把整个std_msgs和sensor_msgs都打开。
如果标准消息类型不够用,可以自定义精简的消息类型。比如你只需要传一个浮点数组,用std_msgs/Float32MultiArray会带上很多额外的字段,不如自己定义一个MyFloatArray.msg,只包含float32[] data。这样消息的序列化开销小,内存占用也低。
我在一个机械臂项目里,把关节角度、速度、电流三个数据合并成一个自定义消息,比用三个标准消息分别发布节省了将近 40% 的带宽。这个优化在节点数量多的时候效果很明显。
7.5 关于硬件选型的一点补充
F407 的以太网 PHY 选型上,LAN8720 和 DP83848 是最常见的两个。LAN8720 便宜、外围电路简单,但它的 REF_CLK 输出精度一般,长时间运行可能会有频偏。DP83848 贵一些,但时钟精度高、抗干扰能力强,工业项目里更推荐。
如果板子空间允许,建议给 PHY 单独用一个 25MHz 晶振,而不是用 MCU 的 MCO 输出。独立晶振的抖动更小,以太网的误码率更低。我对比过两种方案,用独立晶振的时候,ros2 topic bw显示的带宽更稳定,丢包率也更低。
STM32F407 的 CAN 控制器有两个,CAN1 是主控制器,CAN2 是从控制器,两者共享 512 字节的 SRAM。如果两个 CAN 同时使用,滤波器的配置要注意,CAN2 的滤波器编号从 14 开始,不能和 CAN1 的冲突。这个细节在参考手册里有说明,但很容易被忽略。