【瑞萨 MicroROS 评测】micro‑ROS 外设实践外设实践①:ROS2 控制 RA6M4 板载 LED
前言
上一篇完成micro‑ROS底层移植,实现MCU与Agent握手通信。本篇基于已搭建好的节点框架,实现ROS2订阅者,订阅LED控制话题,上位机下发消息,MCU完成板载LED开关控制。
1 硬件资源
- 板载LED:GPIO输出引脚
- 通信:USB‑CDC,micro‑ROS与Agent通信
2 功能设计
- 订阅话题:
/led_control,消息类型std_msgs/msg/Bool - True:点亮LED
- False:熄灭LED
3 订阅者代码实现
#include<rclc/rclc.h>#include<std_msgs/msg/bool.h>rcl_subscription_tled_sub;std_msgs__msg__Bool led_msg;// 订阅回调函数voidled_callback(constvoid*msgin){conststd_msgs__msg__Bool*msg=(conststd_msgs__msg__Bool*)msgin;if(msg->data){R_GPIO_PinWrite(LED_PIN,BSP_IO_LEVEL_LOW);}else{R_GPIO_PinWrite(LED_PIN,BSP_IO_LEVEL_HIGH);}}// 订阅者初始化voidmicroros_led_sub_init(rclc_node_t*node,rclc_executor_t*executor){RCCHECK(rclc_subscription_init_default(&led_sub,node,ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs,msg,Bool),"led_control"));RCCHECK(rclc_executor_add_subscription(executor,&led_sub,&led_msg,&led_callback,ON_NEW_DATA));}4 主逻辑调用
节点初始化完成后调用microros_led_sub_init(),主循环持续执行rclc_executor_spin_some()。
while(1){rclc_executor_spin_some(&executor,RCL_MS_TO_NS(10));}5 整机测试流程
- J‑Link下载固件,板子上电;
- Ubuntu启动micro‑ROS Agent
ros2 run micro_ros_agent micro_ros_agent serial--dev/dev/ttyACM0- 新开终端,发布话题控制LED
ros2 topic pub /led_control std_msgs/msg/Bool"{data: true}"ros2 topic pub /led_control std_msgs/msg/Bool"{data: false}"- 观察开发板LED跟随指令亮灭。
执行 pub true,板子 LED 点亮
pub false 熄灭
6 实操踩坑记录
问题1 发布话题,LED无响应
现象:上位机正常发布消息,回调函数不触发
调试思路:executor、订阅初始化、话题名称
根因:订阅未加入executor;话题名字大小写不匹配
解决方案:确认rclc_executor_add_subscription执行成功,严格核对话题名称。
问题2 运行一段时间偶发HardFault
现象:空闲一段时间后随机死机
调试思路:栈大小,回调函数内部处理逻辑
根因:回调函数栈溢出,回调中执行耗时阻塞操作
解决方案:调大任务栈,回调内部只做IO赋值,禁止延时、循环阻塞。
问题3 Agent握手成功,ROS2找不到话题
现象:ros2 topic list看不到订阅话题
调试思路:micro‑ROS资源配置colcon.meta
根因:最大订阅数量配置过小
解决方案:修改RMW_UXRCE_MAX_SUBSCRIPTIONS,重新编译libmicroros.a。
7 小结
完成ROS2订阅者开发,实现上位机远程控制MCU板载LED。
【瑞萨 MicroROS 评测】micro‑ROS 外设实践②:板载按键中断采集并发布 ROS2 Topic
前言
实现了ROS2订阅控制板载LED。使用外部中断捕获板载按键,按键触发后向上位机发布Bool类型Topic,实现MCU硬件事件上报ROS2。
1 硬件资源
- 板载按键:GPIO外部下降沿中断
- 通信:USB‑CDC micro‑ROS
注意:中断服务函数内禁止调用micro‑ROS API,仅置标志位,在主循环处理发布。
2 功能设计
- 发布话题:
/key_event,消息类型std_msgs/msg/Bool - 按键按下:发布data:true
- 按键松开:发布data:false
- 中断仅设置事件标志,消息发布放在主循环executor上下文执行。
3 代码实现
#include<rclc/rclc.h>#include<std_msgs/msg/bool.h>rcl_publisher_tkey_pub;std_msgs__msg__Bool key_msg;volatilebool key_press_flag=false;//按键外部中断回调voidkey_exti_callback(void){key_press_flag=true;}//发布者初始化voidmicroros_key_pub_init(rclc_node_t*node){RCCHECK(rclc_publisher_init_default(&key_pub,node,ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs,msg,Bool),"key_event"));}//按键状态检测与消息发送,放在主循环调用voidkey_process(void){if(key_press_flag){key_press_flag=false;key_msg.data=true;rcl_publish(&key_pub,&key_msg,NULL);}}4 主循环逻辑
while(1){key_process();rclc_executor_spin_some(&executor,RCL_MS_TO_NS(10));}5 整机测试流程
- J‑Link下载固件;
- Ubuntu启动micro‑ROS Agent
ros2 run micro_ros_agent micro_ros_agent serial--dev/dev/ttyACM0- 新开终端监听话题
ros2 topicecho/key_event- 按下板载按键,终端收到按键事件消息。
中断设置
6 实操踩坑记录
问题1 按键按下,没有消息发布
现象:中断可以进入标志位置1,但没有ROS消息输出
调试思路:中断标志清除、主循环调用逻辑、publisher初始化
根因:中断标志未清零;publisher未正确初始化
解决方案:FSP中断回调中清除中断标志,确认microros_key_pub_init成功执行。
问题2 频繁按键直接HardFault死机
现象:快速反复按键,固件崩溃
调试思路:中断服务函数内容、micro‑ROS API调用位置
根因:在中断内部直接调用rcl_publish,micro‑ROS非中断安全
解决方案:中断中只设置volatile标志,发布操作放到主循环。
问题3 消息丢包,部分按键事件收不到
现象:部分按键按下上位机无输出
调试思路:USB‑CDC缓冲区,micro‑ROS发布速率
根因:短时间大量消息挤压,传输缓冲区溢出
解决方案:增加按键软件消抖,限制消息发布频率。
7 小结
完成外部中断按键采集,MCU硬件事件上报ROS2话题
【瑞萨 MicroROS 评测】micro‑ROS 外设实践③:MCU 系统状态监控节点
前言
前面完成LED订阅控制、按键事件上报。本篇搭建系统状态监控节点,周期性上报MCU运行时长、USB‑CDC缓冲区溢出计数、Agent心跳连接状态,实现对micro‑ROS链路健康监测。
1 功能设计
- 发布话题:
/mcu/status,消息类型diagnostic_msgs/msg/DiagnosticStatus - 上报内容:
- MCU运行时间(ms)
- USB‑CDC接收缓冲区溢出次数
- Agent心跳状态(在线/离线)
- 发布周期:1Hz,1秒上报一次状态。
2 全局变量定义
#include<rclc/rclc.h>#include<diagnostic_msgs/msg/diagnostic_status.h>rcl_publisher_tstatus_pub;diagnostic_msgs__msg__DiagnosticStatus status_msg;uint32_tmcu_run_ms=0;uint32_tusb_cdc_overflow_cnt=0;bool agent_online=false;rcl_time_point_value_tlast_agent_time;3 发布者初始化
voidmicroros_status_pub_init(rclc_node_t*node){RCCHECK(rclc_publisher_init_default(&status_pub,node,ROSIDL_GET_MSG_TYPE_SUPPORT(diagnostic_msgs,msg,DiagnosticStatus),"/mcu/status"));}4 状态更新与发布函数
voidmcu_status_update(void){//获取系统毫秒计时mcu_run_ms=R_Delay_GetMilliseconds();//心跳判断,超过1.5s没有通信判定Agent离线rcl_time_point_value_tnow;rcl_clock_get_now(rcl_get_default_clock(),&now);agent_online=((now-last_agent_time)<RCL_MS_TO_NS(1500));status_msg.level=agent_online?0:1;status_msg.message=agent_online?"Agent connected":"Agent disconnected";status_msg.hardware_id.data="RA6M4";status_msg.hardware_id.size=5;//填充键值对diagnostic_msgs__msg__KeyValue kv_run,kv_overflow;kv_run.key.data="run_time_ms";kv_run.value.data=itoa(mcu_run_ms,kv_run.value.data,10);kv_overflow.key.data="usb_overflow";kv_overflow.value.data=itoa(usb_cdc_overflow_cnt,kv_overflow.value.data,10);status_msg.values.data[0]=kv_run;status_msg.values.data[1]=kv_overflow;status_msg.values.size=2;rcl_publish(&status_pub,&status_msg,NULL);}5 主循环逻辑
uint32_tstatus_timer=0;while(1){key_process();//1Hz周期上报状态if(R_Delay_GetMilliseconds()-status_timer>=1000){status_timer=R_Delay_GetMilliseconds();mcu_status_update();}rclc_executor_spin_some(&executor,RCL_MS_TO_NS(10));//通信有数据更新心跳时间if(transport_rx_activity_flag){transport_rx_activity_flag=false;rcl_clock_get_now(rcl_get_default_clock(),&last_agent_time);}}在USB‑CDC接收中断中,当发生缓冲区溢出时对
usb_cdc_overflow_cnt++;收到有效数据包置位transport_rx_activity_flag。
6 整机测试流程
- J‑Link下载固件
- 启动micro‑ROS Agent
ros2 run micro_ros_agent micro_ros_agent serial--dev/dev/ttyACM0- 终端监听状态话题
ros2 topicecho/mcu/status- 断开Agent,可以观察状态切换为离线;重启Agent自动恢复在线。
输出结果
state: 1
msg: “mcu online”
state: 1
msg: “mcu online”
7 实操踩坑记录
问题1 DiagnosticStatus消息发布乱码
现象:收到消息字符串乱码
调试思路:ros2字符串内存管理
根因:字符串临时栈内存,消息发送完成内存失效
解决方案:使用全局static字符数组存储字符串内容。
问题2 Agent断开后状态不会切换离线
现象:关闭Agent,状态一直显示connected
调试思路:心跳时间戳更新逻辑
根因:没有清除接收活动标记,时间戳不刷新
解决方案:仅当Transport收到有效数据包才刷新last_agent_time。
问题3 高负载下USB溢出计数持续上涨
现象:高速收发时usb_overflow持续累加
调试思路:USB‑CDC接收缓冲区大小
根因:USB中断处理不及时,接收FIFO溢出
解决方案:调大USB‑CDC接收缓冲区,降低micro‑ROS消息发布频率。
8 小结
完成MCU系统监控节点,实现运行时间、USB溢出计数、Agent在线心跳检测。
【瑞萨 MicroROS 评测】micro‑ROS 外设实践④:ROS2 下发字符串控制 OLED 显示 + 本地 Agent 离线诊断
前言
上一篇完成MCU系统状态监控节点。本篇实现字符串订阅功能,上位机下发字符串,OLED屏幕实时显示;增加离线诊断逻辑,Agent断开连接时,OLED本地打印故障信息,脱离ROS2也可查看状态。
1 硬件资源
- OLED:I2C SSD1306 0.96寸
- 通信:USB‑CDC micro‑ROS
- 本地离线模式:Agent离线,OLED直接展示MCU内部状态
2 功能设计
- 订阅话题:
/oled/display,消息类型std_msgs/msg/String - 上位机下发字符串,OLED刷新显示文本内容
- Agent离线状态下,OLED自动切换本地诊断界面:运行时长、USB溢出计数、链路状态
3 代码实现
#include<rclc/rclc.h>#include<std_msgs/msg/string.h>rcl_subscription_toled_sub;std_msgs__msg__String oled_msg;//ROS字符串订阅回调voidoled_callback(constvoid*msgin){conststd_msgs__msg__String*msg=(conststd_msgs__msg__String*)msgin;oled_clear();oled_show_string(0,0,msg->data.data);}//订阅初始化voidmicroros_oled_sub_init(rclc_node_t*node,rclc_executor_t*executor){RCCHECK(rclc_subscription_init_default(&oled_sub,node,ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs,msg,String),"/oled/display"));RCCHECK(rclc_executor_add_subscription(executor,&oled_sub,&oled_msg,&oled_callback,ON_NEW_DATA));}//离线诊断页面刷新voidoled_offline_diagnostic(uint32_trun_ms,uint32_toverflow_cnt){oled_clear();oled_show_string(0,0,"Agent OFFLINE");charbuf[32];sprintf(buf,"Run:%d ms",run_ms);oled_show_string(0,16,buf);sprintf(buf,"USB_O:%d",overflow_cnt);oled_show_string(0,32,buf);}4 主循环逻辑
while(1){key_process();if(R_Delay_GetMilliseconds()-status_timer>=1000){status_timer=R_Delay_GetMilliseconds();mcu_status_update();}rclc_executor_spin_some(&executor,RCL_MS_TO_NS(10));if(transport_rx_activity_flag){transport_rx_activity_flag=false;rcl_clock_get_now(rcl_get_default_clock(),&last_agent_time);}//心跳判断rcl_time_point_value_tnow;rcl_clock_get_now(rcl_get_default_clock(),&now);agent_online=((now-last_agent_time)<RCL_MS_TO_NS(1500));//离线自动切诊断界面if(!agent_online){oled_offline_diagnostic(mcu_run_ms,usb_cdc_overflow_cnt);}}5 整机测试流程
- J‑Link下载固件,OLED接I2C引脚
- Ubuntu启动Agent
ros2 run micro_ros_agent micro_ros_agent serial--dev/dev/ttyACM0- 下发字符串命令
ros2 topic pub /oled/display std_msgs/msg/String"{data:\"RA6M4 Micro‑ROS Test\"}"- OLED屏幕显示下发文本;关闭Agent,屏幕自动切换离线诊断页面。
6 实操踩坑记录
问题1 长字符串下发后固件死机
现象:发送较长字符串HardFault
调试思路:std_msgs/String内存分配
根因:String消息未分配足够缓冲区,内存越界
解决方案:修改colcon.meta调大字符串最大长度,给msg.data分配全局buffer。
问题2 Agent断开,OLED画面闪烁
现象:离线诊断页面每秒反复刷新闪烁
调试思路:页面刷新频率控制
根因:主循环无条件每秒重复刷屏
解决方案:增加状态标记,状态发生改变才执行oled刷新。
问题3 部分中文乱码
现象:下发中文OLED输出乱码
调试思路:OLED字库
根因:SSD1306无内置中文字库
解决方案:仅使用ASCII字符,如需中文需要点阵字库。
7 小结
完成ROS字符串下发控制OLED显示,实现Agent离线本地诊断显示。
【瑞萨 MicroROS 评测】进阶挑战 :FreeRTOS+micro‑ROS
前言
前面全部基于裸机环境完成micro‑ROS开发。迁移至FreeRTOS操作系统,把micro‑ROS executor放到独立任务运行,隔离IO、按键、显示业务,解决裸机主循环阻塞带来的通信卡顿问题。
1 环境说明
- MCU:RA6M4
- RTOS:FreeRTOS(FSP自带)
- 通信:USB‑CDC transport
- micro‑ROS运行于独立RTOS任务,优先级中等
2 系统架构设计
- microros_task:专门运行
rclc_executor_spin_some,处理订阅、发布、XRCE‑DDS协议,栈空间调大; - ui_task:OLED显示、离线诊断刷新;
- key_task:按键状态处理;
禁止在中断、其他任务直接调用micro‑ROS API,所有rclc接口统一在microros_task内部执行。
3 RTOS任务创建代码
#include"FreeRTOS.h"#include"task.h"#defineMICROROS_TASK_STACK_SIZE(4096)#defineUI_TASK_STACK_SIZE(1024)staticTaskHandle_t microros_task_handle;staticTaskHandle_t ui_task_handle;voidmicroros_task(void*pvParameters){(void)pvParameters;//micro‑ROS初始化放在本任务内app_microros_init();microros_led_sub_init(&node,&executor);microros_key_pub_init(&node);microros_status_pub_init(&node);microros_oled_sub_init(&node,&executor);for(;;){rclc_executor_spin_some(&executor,RCL_MS_TO_NS(10));vTaskDelay(pdMS_TO_TICKS(10));}}voidui_task(void*pvParameters){(void)pvParameters;for(;;){//OLED刷新、离线诊断逻辑if(!agent_online){oled_offline_diagnostic(mcu_run_ms,usb_cdc_overflow_cnt);}vTaskDelay(pdMS_TO_TICKS(500));}}//在main中创建任务voidrtos_app_create_tasks(void){xTaskCreate(microros_task,"microros",MICROROS_TASK_STACK_SIZE,NULL,2,µros_task_handle);xTaskCreate(ui_task,"ui",UI_TASK_STACK_SIZE,NULL,1,&ui_task_handle);}4 资源与配置关键点
- FreeRTOS栈:micro‑ROS任务栈至少4096字节,栈过小直接HardFault;
- 优先级:micro‑ROS任务优先级高于UI任务,保证通信实时性;
- Heap:FreeRTOS heap5,堆空间增大,供micro‑ROS分配;
- Transport USB‑CDC中断保持不变,中断仅置标志,不调用rcl接口;
- 多任务共享变量(
agent_online、mcu_run_ms等)访问加临界区保护。
//共享变量读取示例uint32_tget_mcu_run_ms(void){uint32_tval;taskENTER_CRITICAL();val=mcu_run_ms;taskEXIT_CRITICAL();returnval;}5 整机测试流程
- FSP开启FreeRTOS,生成工程;
- J‑Link下载固件;
- Ubuntu启动Agent
ros2 run micro_ros_agent micro_ros_agent serial--dev/dev/ttyACM0- 执行各项ROS话题测试:LED控制、按键上报、OLED字符串下发、状态监控;
- 长时间压测,观察是否出现死锁、死机、断连。
6 实操踩坑记录
问题1 一跑micro‑ROS任务直接HardFault
现象:进入任务立刻硬件错误
调试思路:任务栈大小,FPU上下文保存
根因:栈空间不足;FreeRTOS未开启FPU浮点上下文保存
解决方案:调大任务栈,FSP中FreeRTOS勾选Use FPU。
问题2 随机发生死锁,通信完全停止
现象:Agent握手成功,一段时间后不再响应任何话题
调试思路:多任务共享资源,中断与RTOS冲突
根因:多任务无保护读写micro‑ROS句柄、全局状态变量
解决方案:micro‑ROS全部API只在microros_task执行,共享变量用临界区保护。
问题3 高负载下USB‑CDC接收丢包变多
现象:RTOS调度切换频繁,USB中断延迟
调试思路:USB中断优先级
根因:USB中断优先级过低,被其他中断抢占
解决方案:提升USB外设中断优先级,保证USB中断及时响应。
7 小结
完成FreeRTOS下micro‑ROS部署,使用独立任务运行executor,业务解耦,解决裸机循环阻塞带来通信卡顿。完整覆盖RA6M4平台micro‑ROS从环境搭建、底层移植、外设实践、USB‑CDC通信到RTOS移植全流程。
一些相关演示视频按键控制话题输出
mcu/status 在线周期性输出