1. 简言由于传统 ROS 2 系统通常运行在 Linux 主机上(例如Ubuntu), 但是 MCU 资源有限, 无法直接运行完整的 ROS 2,因此 micro-ROS 采用了 Client-Agent 架构。其中micro-ROS Client运行在 MCU 端micro-ROS Agent运行在 PC 端两者通过UART、USB、Ethernet等方式通信MCU 端只负责运行轻量级 ROS 2 Client而复杂的 DDS 通信由 PC 端 Agent 完成。这样既保留了 ROS 2 的通信能力又满足 MCU 的资源限制。2. 在PC端搭建好通信架构2.1. 环境准备本次示例的开发环境为项目配置操作系统UbuntuROS 2版本JazzyMCURenesas RA6M4通信方式UARTAgent运行环境Ubuntu如果不确定自己的ROS2版本可以在终端使用以下命令来确定echo $ROS_DISTRO最后会输出形如jazzy由于不同版本之间的软件包、接口以及 micro-ROS 版本需要保持匹配。因此在后续安装 micro-ROS 组件时需要选择与当前 ROS 2 版本对应的分支。2.2. 先安装工作空间所需要的基础工具安装pip安装过一次就可以了后续新建工作空间不需要再运行sudo apt install -y python3-pip安装编译工具colcon安装过一次就可以了后续新建工作空间不需要再运行sudo apt install python3-colcon-common-extensions安装rosdep工具安装过一次就可以了后续新建工作空间不需要再运行sudo apt install python3-rosdep进行初始化安装过一次就可以了后续新建工作空间不需要再运行sudo rosdep init2.3. 加载 ROS 2 环境由于 Linux 终端默认只会在$PATH变量列出的目录里查找命令。自己之前所下载的ROS 2可能不在 $PATH里。 因此让当前终端知道 ROS 2 装在哪里从而能正常使用ros2命令和相关工具在终端运行source /opt/ros/jazzy/setup.bash它会设置ROS package 搜索路径Python package 路径CMake 查找路径ROS 相关环境变量。这样就可以正常使用 有关ROS 2的命令了2.4. 创建 micro-ROS 工作空间micro-ROS 在 PC 端同样采用 ROS 2 workspace 管理方式。创建独立工作空间mkdir -p ~/microros_ws/src cd ~/microros_ws目录结构microros_ws ├── src ├── build ├── install └── log其中目录作用src存放 ROS 2/micro-ROS 源代码build编译产生的中间文件install编译后的可用 packagelog构建日志这一结构与普通 ROS 2 workspace 一致。2.5. 获取 micro-ROS 构建工具micro-ROS 官方提供 micro_ros_setup 用于创建、配置和构建 micro-ROS 工程。下面以我自己的Jazzy版本为例从其官方获取构建工具对于不同版本将下面的“jazzy”换成自己的版本即可git clone -b jazzy \ https://github.com/micro-ROS/micro_ros_setup.git \ src/micro_ros_setup2.6. 安装依赖并编译 micro-ROS 工具2.6.1. 更新 rosdep 索引每次新建工作工具都要运行rosdep update2.6.2. 安装工作空间依赖每次新建工作工具都要运行rosdep install --from-paths src --ignore-src -y2.6.3. 进行编译colcon build2.6.4. 加载当前的 workspacesource install/local_setup.bash2.7. 创建并编译 micro-ROS Agent当我们安装好 micro_ros_setup 后就可以用该工具构建我们最终需要的 micro_ros_agent 了2.7.1. 创作 Agent 工作空间首先获取micro-ROS Agent源码Micro XRCE-DDS Agent相关 ROS 2 依赖ros2 run micro_ros_setup create_agent_ws.sh然后编译ros2 run micro_ros_setup build_agent.sh编译完最终会生成 micro_ros_agent 的可执行文件2.7.2. 验证 micro-ROS Agent 是否安装成功检查 ROS 2 是否能够找到 Agentros2 pkg executables micro_ros_agent当输出 micro_ros_agent micro_ros_agent 的时候说明你已经安装成功了。接下来测试以下Agent是否可以正常启动ros2 run micro_ros_agent micro_ros_agent --help如果输出内容为 Usage: micro_ros_agent udp4|udp6|tcp4|tcp6|serial...证明Agent可以正常启动3. 在 RA6M4 端构建 micro-ROS Client3.1. 获取 Renesas micro-ROS 组件micro-ROS 官方针对 Renesas e² studio 提供了对应组件 micro_ros_renesas2estudio_component该组件包含micro-ROS Client源码静态库生成脚本Renesas平台适配代码Transport相关代码。首先你需要进入对应的RA6M4工程目录cd 你的工程文件目录之后你需要在该目录下拉取你电脑上ROS 2版本对应的分支以我的jazzy为例git clone -b jazzy \ https://github.com/micro-ROS/micro_ros_renesas2estudio_component.git其中jazzy需要按照自己的版本修改3.2. 确认 MCU 编译参数micro-ROS Client 生成的libmicroros.a最终会和你的应用代码一起被链接器合并成同一个 ELF 固件。这意味着两边的机器码必须遵循相同的函数调用规则ABI。如果两边的编译参数不一致例如你的工程使用-mfloat-abihard浮点参数通过 FPU 寄存器传递libmicroros.a 使用-mfloat-abisoft浮点参数通过通用寄存器传递那么你的代码调用 micro-ROS 函数时参数会被放进不同的寄存器导致函数读到错误的值。这种问题在编译和链接阶段可能不会报错但运行时会表现为数据异常非常难以排查。因此在生成libmicroros.a之前必须先确认当前 RA6M4 工程实际使用的编译参数并在生成静态库时传入完全相同的参数。查看方式如下打开工程中的cmake/GeneratedCfg.cmake找到RASC_CMAKE_C_FLAGS其中包含形如-mcpucortex-m33-mthumb-mfpufpv5-sp-d16-mfloat-abihard上述这些都是我个人的相关信息每个人和每个人的可能不一样3.3. 生成 micro-ROS Client 静态库首先进入 micro_ros_renesas2estudio_component/library_generation 路径cd micro_ros_renesas2estudio_component/library_generation在该目录下执行./library_generation.sh \ -mcpucortex-m33 -mthumb -mfloat-abihard -mfpufpv5-sp-d16该命令用于生成 libmicroros.a 静态库。生成完成后返回工程目录查看生成结果如何cd ../..查看静态库ls -lh \ micro_ros_renesas2estudio_component/libmicroros/libmicroros.a如果生成成功的话运行完刚才那条命令后会得到类似以下的反馈3.4. 将 micro-ROS Client 集成到 RA6M4 工程在成功交叉编译出 libmicroros.a 静态库之后接下来的关键步骤是修改 RA6M4 工程的 CMakeLists.txt将 micro-ROS 的头文件、静态库以及我们自己编写的移植源码整合在一起。整个集成过程总体分为好几个小步打开你工程的 CMakeLists.txt在其末尾依次追加以下配置3.4.1. 保留瑞萨 FSP 自动生成的配置在添加任何 micro-ROS 的代码之前首先需要确保 CMakeLists.txt 中包含了瑞萨自动生成的 CMake 脚本# # Renesas generated sources # # 它负责引入 FSP 生成的底层驱动并创建了 ${PROJECT_NAME}.elf 目标 include(${CMAKE_CURRENT_LIST_DIR}/cmake/GeneratedSrc.cmake)3.4.2. 定义 micro-ROS 根目录变量为了避免后续反复书写冗长的路径我们先定义一个变量如 MICROROS_ROOT 指向 micro-ROS 库的所在目录# # micro-ROS integration # # 1. 定义 micro-ROS 库的根目录路径 set(MICROROS_ROOT ${CMAKE_CURRENT_LIST_DIR}/micro_ros_renesas2estudio_component/libmicroros )3.4.3. 添加头文件路径 (Include Directories)编译器需要知道 micro-ROS 的各种数据结构和 API 声明在哪里。我们将 include 目录暴露给工程的主目标注意嵌入式工程的目标名通常带有 .elf 后缀# 2. 告诉编译器 micro-ROS 的头文件在哪里 target_include_directories(${PROJECT_NAME}.elf PRIVATE ${MICROROS_ROOT}/include )3.4.4. 导入并链接静态库 (Import Link Library)这是最关键的一步。我们不直接链接文件路径而是先用 add_library 声明一个名为 microros 的外部静态库然后将其路径绑定到这个名字上最后让主工程链接这个名字# 3. 导入预编译的 micro-ROS 静态库 add_library(microros STATIC IMPORTED) set_target_properties(microros PROPERTIES IMPORTED_LOCATION ${MICROROS_ROOT}/libmicroros.a ) # 将导入的 microros 库链接到 RA6M4 固件中 target_link_libraries(${PROJECT_NAME}.elf PRIVATE microros )3.4.5. 添加用户源码 (Sources)最后将我们自己编写的 micro-ROS 节点逻辑例如 test.c 以及可能需要的底层接口移植文件加入编译列表# 4. 添加用户编写的 micro-ROS 源码文件 target_sources(${PROJECT_NAME}.elf PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/test.c # 如果后续有自定义的串口/网络收发文件也在这里继续添加 )上述的 test.c 是我建在 src 文件路径下用来测试我的配置是否成功了如果成功了那么编译就没问题反之如果编译有问题说明配置有问题。读者如果不想测试的话可以忽略这个文件。3.4.6. 上述所有需要添加的代码include(${CMAKE_CURRENT_LIST_DIR}/Config.cmake) include(${CMAKE_CURRENT_LIST_DIR}/cmake/GeneratedCfg.cmake) # If required user can amend RASC generated flags here, # e.g. list(REMOVE_ITEM RASC_CMAKE_C_FLAGS -Wall) # 上述是工程自己生成的内容需要添加的内容是下面的所有内容 # # micro-ROS integration # # micro-ROS generated library directory set(MICROROS_ROOT ${CMAKE_CURRENT_LIST_DIR}/micro_ros_renesas2estudio_component/libmicroros ) # micro-ROS headers target_include_directories(${PROJECT_NAME}.elf PRIVATE ${MICROROS_ROOT}/include ) # Import prebuilt micro-ROS static library add_library(microros STATIC IMPORTED) set_target_properties(microros PROPERTIES IMPORTED_LOCATION ${MICROROS_ROOT}/libmicroros.a ) # Link micro-ROS into RA6M4 firmware target_link_libraries(${PROJECT_NAME}.elf PRIVATE microros ) # User micro-ROS source files target_sources(${PROJECT_NAME}.elf PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/test.c )4.RA6M4 底层接口适配为了兼容不同架构的MCUmicro-ROS 在底层剥离了与具体硬件相关的逻辑对于数据收发Transport、内存分配Allocator、时间获取Clock等强依赖底层外设的操作官方仅提供了统一的接口声明。这种设计在确保上层协议栈通用性的同时也将具体的底层适配工作交由开发者完成。因此为了使 micro-ROS 在 Renesas RA6M4 上正常运行我们必须遵循官方的接口约束利用 RA6M4 的 FSP 驱动库编写对应的执行逻辑从而完成将软件接口映射到物理硬件外设的底层移植工作。下面以“数据收发”为例子简略说明适配过程展示出来的代码仅为要点部分具体完整的RA6M4接口适配代码在文章末尾4.1.1. 数据收发 (Transport) 接口的注册机制在进行底层移植时首要任务是明确需要实现哪些具体的函数。查阅 micro-ROS 官方的移植指南Porting Guide可知由于 MCU 裸机或 RTOS 环境通常缺乏标准操作系统如 Linux的 POSIX 驱动层micro-ROS 专门提供了自定义传输层Custom Transport机制。开发者必须调用核心 APIrmw_uros_set_custom_transport将特定硬件的收发逻辑注册到中间件RMW层。通过分析该函数的原型定义其采用了典型的函数指针回调机制要求开发者传入四个核心底层操作的具体实现Open初始化通信硬件如配置串口波特率、使能外设。Close关闭通信硬件如释放串口资源。Write将上层协议栈打包好的数据流通过物理硬件发送。Read从物理硬件读取底层数据流并交由上层解析。在 RA6M4 工程中的接口绑定在 RA6M4 的主程序入口通常为hal_entry()中需调用该函数完成底层通信链路的注册。具体代码如下void hal_entry(void) { // 注册自定义的 Transport 层接口 rmw_uros_set_custom_transport( true, // framing: 是否启用数据包分帧串口通信通常设为 true (void *) NULL, // args: 传递给底层函数的自定义参数此处设为 NULL renesas_e2_transport_open, // 绑定自定义的 Open 函数 renesas_e2_transport_close, // 绑定自定义的 Close 函数 renesas_e2_transport_write, // 绑定自定义的 Write 函数 renesas_e2_transport_read // 绑定自定义的 Read 函数 ); // ... 后续的其他初始化代码 ... }micro-ROS 框架为了屏蔽底层硬件差异将通信层的具体实现进行了抽象。官方通过rmw_uros_set_custom_transport接口仅定义了底层通信所需的 Open、Close、Write、Read 四个函数指针规范并未提供具体代码。这种机制虽然保证了协议栈的跨平台通用性但要求开发者必须针对具体的物理链路提供对应的函数实体。在完成上述接口注册后下一步的核心工作便是利用 RA6M4 的 FSP 驱动库严格按照这四个函数的签名规范编写具体的串口初始化与数据收发逻辑从而彻底打通数据链路。4.1.2. 编写具体的函数内容在完成上述接口注册后下一步的核心工作便是利用 RA6M4 的 FSP 驱动库严格按照这四个函数的签名规范编写具体的串口初始化与数据收发逻辑从而彻底打通数据链路。bool renesas_e2_transport_open(struct uxrCustomTransport * transport) { (void) transport; return uros_uart_open(); } bool renesas_e2_transport_close(struct uxrCustomTransport * transport) { (void) transport; return uros_uart_close(); } // 暂时仅展示这两函数的内容bool uros_uart_open(void) { fsp_err_t err g_uart0.p_api-open(g_uart0.p_ctrl, g_uart0.p_cfg); return (FSP_SUCCESS err) || (FSP_ERR_ALREADY_OPEN err); } bool uros_uart_close(void) { fsp_err_t err g_uart0.p_api-close(g_uart0.p_ctrl); return (FSP_SUCCESS err) || (FSP_ERR_NOT_OPEN err); }具体完整的适配代码在该压缩包内请需要的读者自取RA6M4底层接口适配.zip