尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【瑞萨Micro-ROS评测】+基于 RA6M4 的 micro-ROS 自定义传输:CAN 2.0 + ISO-TP 实现

【瑞萨Micro-ROS评测】+基于 RA6M4 的 micro-ROS 自定义传输:CAN 2.0 + ISO-TP 实现 1. 概述micro-ROS 支持自定义传输层允许开发者将 ROS 2 节点运行在资源受限的嵌入式设备上。由于标准 CAN 2.0 总线单帧最多只能传输 8 字节无法直接承载较大的 ROS 消息因此需要引入 ISO-TPISO 15765-2协议进行拆包与组包。本文将详细介绍如何在 Renesas RA6M4 MCU 上实现基于 CAN 2.0 与 ISO-TP 的 micro-ROS 自定义传输包括 Agent 的编译修改、传输层代码实现以及硬件配置。2. 环境准备与 Agent 编译2.1 获取 Micro-XRCE-DDS-Agent 源码# 创建工作空间 mkdir -p ~/microros_ws/src cd ~/microros_ws/src 克隆 Micro-XRCE-DDS-Agentmicro-ROS Agent 的底层实现 git clone -b humble https://github.com/eProsima/Micro-XRCE-DDS-Agent.git cd Micro-XRCE-DDS-Agent 安装依赖 sudo apt update sudo apt install python3-rosdep python3-colcon-common-extensions rosdep update rosdep install --from-paths . --ignore-src -y2.2 修改 Agent 支持自定义传输Micro-XRCE-DDS-Agent 默认支持串口、UDP、TCP 等传输方式。要添加基于 CAN ISO-TP 的自定义传输本方案采用在 Agent 仓库根目录创建custom_agent_can_isotp.cpp的方式并依赖isotp-c/子模块实现 ISO-TP 协议。最终目录结构如下Micro-XRCE-DDS-Agent/ ├── CMakeLists.txt ├── custom_agent_can_isotp.cpp ├── isotp-c/ ├── include/ ├── src/ └── build/步骤 1添加 isotp-c 子模块在 Agent 仓库根目录下添加lishen2/isotp-c作为子模块cd ~/microros_ws/src/Micro-XRCE-DDS-Agent git submodule add https://github.com/lishen2/isotp-c.git isotp-c步骤 2创建 custom_agent_can_isotp.cpp在 Agent 仓库根目录创建custom_agent_can_isotp.cpp实现基于 CAN ISO-TP 的自定义传输。该文件直接使用isotp-c子模块提供的协议栈并通过uxrCustomTransport接口对接 micro-ROS Agent// custom_agent_can_isotp.cpp #include uxr/agent/transport/custom/CustomAgent.hpp #include uxr/agent/logger/Logger.hpp #include uxr/agent/middleware/Middleware.hpp #include iostream #include iomanip #include cstdio #include string #include cstring #include ctime #include unistd.h #include sys/socket.h #include net/if.h #include sys/ioctl.h #include linux/can.h #include linux/can/raw.h #include poll.h extern C { #include isotp.h } using namespace eprosima::uxr; static int g_sock -1; #define ISOTP_TX_ID 0x2 #define ISOTP_RX_ID 0x1 #define ISOTP_BUFFER_SIZE 512 static IsoTpLink g_link; static uint8_t g_isotp_send_buf[ISOTP_BUFFER_SIZE]; static uint8_t g_isotp_recv_buf[ISOTP_BUFFER_SIZE]; // 辅助打印十六进制数据 static void print_hex(const uint8_t* data, size_t len) { std::cout ; for (size_t i 0; i len; i) { std::cout std::hex std::setw(2) std::setfill(0) (int)data[i] ; if ((i1) % 16 0) std::cout std::endl ; } std::cout std::dec std::endl; } int isotp_user_send_can(const uint32_t arbitration_id, const uint8_t *data, const uint8_t size) { if (g_sock 0 || size 8) return -1; struct can_frame frame; memset(frame, 0, sizeof(frame)); frame.can_id arbitration_id 0x7FF; frame.can_dlc size; memcpy(frame.data, data, size); printf([ISOTP] TX CAN id0x%03X dlc%u data, arbitration_id 0x7FF, size); for (uint8_t i 0; i size; i) printf(%02x , data[i]); printf(\n); fflush(stdout); ssize_t nbytes write(g_sock, frame, sizeof(frame)); return (nbytes sizeof(frame)) ? ISOTP_RET_OK : -1; } uint32_t isotp_user_get_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, ts); return (uint32_t)(ts.tv_sec * 1000 ts.tv_nsec / 1000000); } void isotp_user_debug(const char *message, ...) { (void)message; } static bool custom_init() { std::cout [DEBUG] custom_init() called std::endl; return true; } static bool custom_fini() { std::cout [DEBUG] custom_fini() called std::endl; return true; } static ssize_t custom_recv(CustomEndPoint *endpoint, uint8_t *buffer, size_t buffer_length, int timeout, TransportRc transport_rc) { (void)timeout; (void)endpoint; while (true) { struct can_frame frame; struct pollfd fds; fds.fd g_sock; fds.events POLLIN; int ret poll(amp;amp;fds, 1, 100); if (ret amp;gt; 0 amp;amp;amp;amp; (fds.revents amp;amp; POLLIN)) { ssize_t nbytes read(g_sock, amp;amp;frame, sizeof(frame)); if (nbytes sizeof(frame)) { printf([ISOTP] RX CAN id0x%03X dlc%u data, frame.can_id amp;amp; 0x7FF, frame.can_dlc); for (int i 0; i amp;lt; frame.can_dlc; i) printf(%02x , frame.data[i]); printf(\n); fflush(stdout); if ((frame.can_id amp;amp; 0x7FF) ISOTP_RX_ID) { isotp_on_can_message(amp;amp;g_link, frame.data, frame.can_dlc); } } } isotp_poll(amp;amp;g_link); uint16_t out_size 0; int rc isotp_receive(amp;amp;g_link, buffer, (uint16_t)buffer_length, amp;amp;out_size); if (rc ISOTP_RET_OK amp;amp;amp;amp; out_size amp;gt; 0) { transport_rc TransportRc::ok; std::cout amp;lt;amp;lt; [DEBUG] custom_recv: received complete message, len amp;lt;amp;lt; out_size amp;lt;amp;lt; std::endl; print_hex(buffer, out_size); return out_size; } } } static ssize_t custom_send(const CustomEndPoint *endpoint, uint8_t buffer, size_t message_length, TransportRc transport_rc) { (void)endpoint; std::cout [DEBUG] custom_send called, message_length message_length std::endl; print_hex(buffer, message_length); / 若上一次多帧发送仍在进行(CF 由接收线程 isotp_poll 异步发出), isotp_send 会返回 INPROGRESS(-2)。等待其完成后重试, 避免被误判为 server error 而反复 fini/init。 */ int ret isotp_send(amp;g_link, buffer, (uint16_t)message_length); if (ret ISOTP_RET_INPROGRESS) { int waited 0; while (g_link.send_status ISOTP_SEND_STATUS_INPROGRESS amp;amp; waited lt; 200) { usleep(1000); waited; } std::cout lt;lt; [DEBUG] custom_send: waited lt;lt; waited lt;lt; ms for previous TX, retry lt;lt; std::endl; ret isotp_send(amp;g_link, buffer, (uint16_t)message_length); } if (ret ISOTP_RET_OK) { transport_rc TransportRc::ok; std::cout lt;lt; [DEBUG] custom_send: isotp_send succeeded lt;lt; std::endl; return message_length; } else { transport_rc TransportRc::server_error; std::cout lt;lt; [DEBUG] custom_send: isotp_send failed with ret lt;lt; ret lt;lt; std::endl; return -1; } } int main(int argc, char **argv) { (void)argc; (void)argv; const char *ifname can0; if ((g_sock socket(PF_CAN, SOCK_RAW, CAN_RAW)) lt; 0) { perror(socket); return 1; } struct ifreq ifr; strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); ifr.ifr_name[IFNAMSIZ - 1] \0; if (ioctl(g_sock, SIOCGIFINDEX, amp;ifr) lt; 0) { perror(ioctl); close(g_sock); return 1; } struct sockaddr_can addr; addr.can_family AF_CAN; addr.can_ifindex ifr.ifr_ifindex; if (bind(g_sock, (struct sockaddr *)amp;addr, sizeof(addr)) lt; 0) { perror(bind); close(g_sock); return 1; } isotp_init_link(amp;g_link, ISOTP_TX_ID, g_isotp_send_buf, sizeof(g_isotp_send_buf), g_isotp_recv_buf, sizeof(g_isotp_recv_buf)); auto init_func std::functionlt;bool()gt;(custom_init); auto fini_func std::functionlt;bool()gt;(custom_fini); auto send_func std::functionlt;ssize_t(const CustomEndPoint *, uint8_t *, size_t, TransportRc amp;)gt;(custom_send); auto recv_func std::functionlt;ssize_t(CustomEndPoint *, uint8_t *, size_t, int, TransportRc amp;)gt;(custom_recv); CustomEndPoint *endpoint new CustomEndPoint(); CustomAgent agent( CAN_ISOTP, endpoint, Middleware::Kind::FASTDDS, false, init_func, fini_func, send_func, recv_func ); // 设置更高的日志级别 agent.set_verbose_level(6); // 使用 start 启动根据官方示例 agent.start(); // 程序会阻塞在 start() 中直到收到停止信号 // 但为了演示我们添加一个简单的循环保持运行 // 实际 start() 内部会处理信号但这里我们简单等待 while (true) { sleep(1); } close(g_sock); delete endpoint; return 0; }步骤 3修改 CMakeLists.txt在 Agent 根目录的CMakeLists.txt中添加对custom_agent_can_isotp.cpp和isotp-c子模块的编译支持# 在 Micro-XRCE-DDS-Agent 根目录的 CMakeLists.txt 中添加 添加 isotp-c 子模块 add_subdirectory(isotp-c) 编译自定义传输源文件 add_library(custom_agent_can_isotp custom_agent_can_isotp.cpp ) target_include_directories(custom_agent_can_isotp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/isotp-c ) target_link_libraries(custom_agent_can_isotp isotp-c ) 将自定义传输库链接到 agent 可执行文件 target_link_libraries(microxrcedds_agent custom_agent_can_isotp ... 其他库 )步骤 4在 agent.cpp 中注册自定义传输在src/cpp/agent/Agent.cpp中声明并注册自定义传输的创建函数// 在 Agent.cpp 顶部声明 extern C { struct uxrCustomTransport *get_custom_can_isotp_transport(void); } // 在 Agent::create_transport 中添加分支 std::unique_ptrTransport Agent::create_transport( const TransportKind transport_kind, const std::string dev) { switch (transport_kind) { case TransportKind::serial: return std::make_uniqueSerialTransport(dev); case TransportKind::udp4: return std::make_uniqueUDPv4Transport(dev); case TransportKind::tcp4: return std::make_uniqueTCPv4Transport(dev); case TransportKind::custom_can_isotp: // 新增 return std::make_uniqueCustomCANIsotpTransport(); default: throw std::runtime_error(Unsupported transport); } }2.3 编译 Agentcd ~/microros_ws colcon build --packages-select microxrcedds_agent source install/setup.bash3. RA6M4 CAN 传输层实现硬件接线示意图如下以下代码基于 Renesas RA6M4 MCU 的 FSPFlexible Software Package和开源 ISO-TP 库lishen2/isotp-c实现。请将代码复制到你的工程中。3.1 头文件 (microros_transports.h)#ifndef MICROROS_TRANSPORTS_H_ #define MICROROS_TRANSPORTS_H_ #include stdbool.h #include stddef.h #include stdint.h struct uxrCustomTransport; bool renesas_e2_transport_open(struct uxrCustomTransport * transport); bool renesas_e2_transport_close(struct uxrCustomTransport * transport); size_t renesas_e2_transport_write(struct uxrCustomTransport * transport, const uint8_t * buf, size_t len, uint8_t * error); size_t renesas_e2_transport_read(struct uxrCustomTransport * transport, uint8_t * buf, size_t len, int timeout, uint8_t * error); #endif /* MICROROS_TRANSPORTS_H_ */3.2 源文件 (microros_transport_can_adapter.c)/* * micro-ROS custom transport for Renesas RA6M4 using Classic CAN ISO-TP (lishen2/isotp-c) */ #include microros_transports.h #include hal_data.h #include string.h #include stdbool.h #include stddef.h #include stdint.h #include uxr/client/transport.h #include uxr/client/util/time.h #include isotp.h /* CAN hardware config */ #define CAN_BAUDRATE 500000U #define CAN_MAILBOX_TX 0U #define CAN_MAILBOX_RX_FIFO 0U /* ISO-TP parameters (must match the agent side) */ #define ISOTP_TX_ID 0x1 // CAN ID used for transmission #define ISOTP_RX_ID 0x2 // CAN ID to accept (RX filter) #define ISOTP_BUFFER_SIZE 512 // RX/TX buffer size /* ISO-TP global state */ static IsoTpLink g_link; static uint8_t g_isotp_send_buf[ISOTP_BUFFER_SIZE]; static uint8_t g_isotp_recv_buf[ISOTP_BUFFER_SIZE]; /* CAN hardware state */ static volatile bool g_tx_complete false; static volatile bool g_tx_error false; /* Raw CAN frame RX queue */ typedef struct { can_frame_t frame; bool valid; } can_rx_item_t; #define RX_QUEUE_SIZE 32 static can_rx_item_t g_rx_queue[RX_QUEUE_SIZE]; static volatile size_t g_rx_head 0; static volatile size_t g_rx_tail 0; /* callbacks required by isotp-c */ /** brief Send a single CAN frame (called by isotp-c) param arbitration_id CAN ID param data Data pointer, length 8 param size Data length return ISOTP_RET_OK on success, otherwise an error */ int isotp_user_send_can(const uint32_t arbitration_id, const uint8_t *data, const uint8_t size) { if (size 8) return -1; can_frame_t tx_frame; memset(tx_frame, 0, sizeof(tx_frame)); tx_frame.id arbitration_id 0x7FF; tx_frame.id_mode CAN_ID_MODE_STANDARD; tx_frame.type CAN_FRAME_TYPE_DATA; tx_frame.data_length_code size; memcpy(tx_frame.data, data, size); g_tx_complete false; g_tx_error false; fsp_err_t err R_CAN_Write(g_can_ctrl, CAN_MAILBOX_TX, tx_frame); if (err ! FSP_SUCCESS) { return -1; } /* Wait for TX completion (max 100 ms) */ int64_t start uxr_millis(); while (!g_tx_complete !g_tx_error (uxr_millis() - start) 100) { R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } return (g_tx_complete !g_tx_error) ? ISOTP_RET_OK : -1; } /** brief System millisecond clock (called by isotp-c) */ uint32_t isotp_user_get_ms(void) { return (uint32_t)uxr_millis(); } /** brief Optional debug output (called by isotp-c) */ void isotp_user_debug(const char *message, ...) { (void)message; } /* CAN callback (FSP interrupt) */ void can_callback(can_callback_args_t *p_args) { if (!p_args) return; switch (p_args-gt;event) { case CAN_EVENT_TX_COMPLETE: g_tx_complete true; break; case CAN_EVENT_RX_COMPLETE: { const can_frame_t *p_frame amp;amp;p_args-amp;gt;frame; size_t next_tail (g_rx_tail 1) % RX_QUEUE_SIZE; if (next_tail ! g_rx_head) { g_rx_queue[g_rx_tail].frame *p_frame; g_rx_queue[g_rx_tail].valid true; g_rx_tail next_tail; } break; } case CAN_EVENT_TX_ABORTED: case CAN_EVENT_ERR_BUS_OFF: default: g_tx_error true; break; } } /* micro-ROS transport interface */ bool renesas_e2_transport_open(struct uxrCustomTransport *transport) { (void)transport; fsp_err_t err R_CAN_Open(amp;g_can_ctrl, amp;g_can_cfg); if (err ! FSP_SUCCESS) return false; /* Initialize the ISO-TP link */ isotp_init_link(amp;g_link, ISOTP_TX_ID, g_isotp_send_buf, sizeof(g_isotp_send_buf), g_isotp_recv_buf, sizeof(g_isotp_recv_buf)); g_rx_head g_rx_tail 0; return true; } bool renesas_e2_transport_close(struct uxrCustomTransport *transport) { (void)transport; return (R_CAN_Close(g_can_ctrl) FSP_SUCCESS); } size_t renesas_e2_transport_write(struct uxrCustomTransport *transport, const uint8_t *buf, size_t len, uint8_t *error) { (void)transport; if (!buf || len 0 || len gt; ISOTP_BUFFER_SIZE) { if (error) *error 1; return 0; } int ret isotp_send(amp;g_link, buf, (uint16_t)len); if (ret ISOTP_RET_OK) { if (error) *error 0; return len; } else { if (error) *error 1; return 0; } } size_t renesas_e2_transport_read(struct uxrCustomTransport *transport, uint8_t *buf, size_t len, int timeout, uint8_t *error) { (void)transport; if (!buf || len 0) { if (error) *error 1; return 0; } int64_t start uxr_millis(); while ((uxr_millis() - start) lt; timeout) { /* 1. Feed CAN frames from the HW queue into ISO-TP */ if (g_rx_head ! g_rx_tail) { can_rx_item_t item amp;g_rx_queue[g_rx_head]; / Only accept ISOTP_RX_ID. NOTE: the CANable may report the agents * standard frame (0x002) as an extended ID (SID shifted to bits 18-28, * e.g. 0x00080000), so compare the SID bits for extended frames. */ uint32_t rx_id item-gt;frame.id amp; 0x7FF; if (item-gt;frame.id_mode CAN_ID_MODE_EXTENDED) { rx_id (item-gt;frame.id gt;gt; 18) amp; 0x7FF; } if (rx_id ISOTP_RX_ID) { isotp_on_can_message(amp;g_link, item-gt;frame.data, item-gt;frame.data_length_code); } item-gt;valid false; g_rx_head (g_rx_head 1) % RX_QUEUE_SIZE; } /* 2. Poll periodically to drive the multi-frame state machine */ isotp_poll(amp;amp;g_link); /* 3. Try to extract a complete message */ uint16_t out_size 0; int ret isotp_receive(amp;amp;g_link, buf, (uint16_t)len, amp;amp;out_size); if (ret ISOTP_RET_OK amp;amp;amp;amp; out_size amp;gt; 0) { if (error) *error 0; return out_size; } R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } if (error) *error 1; return 0; }4. 运行效果展示下面展示 micro-ROS 节点通过 CAN ISO-TP 自定义传输与 Agent 通信的实际运行效果。4.1 硬件连接与实物图RA6M4 开发板通过 CAN 收发器如 TJA1050连接 CAN 总线另一端接入运行 Micro-XRCE-DDS-Agent 的主机通过 USB-CAN 适配器如 CANable。整体连接示意如下4.2 Agent 端运行日志启动 Agent 并指定自定义 CAN 传输后终端输出如下日志可以看到 Agent 成功初始化 CAN 套接字并等待客户端连接zhzh:~/work/ros/Micro-XRCE-DDS-Agent/build$ ./custom_agent_can_isotp [1787941708.460342] info | Root.cpp | set_verbose_level | logger setup | verbose_level: 6 [DEBUG] custom_init() called [1787941708.460421] info | CustomAgent.cpp | init | Custom agent status: opened | CAN_ISOTP agent running [ISOTP] RX CAN id0x001 dlc8 data10 18 80 00 00 00 00 01 [ISOTP] TX CAN id0x002 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data21 10 00 58 52 43 45 01 [ISOTP] RX CAN id0x001 dlc8 data22 00 01 0f 12 34 56 78 [ISOTP] RX CAN id0x001 dlc8 data23 81 00 fc 01 00 00 00 [DEBUG] custom_recv: received complete message, len24 80 00 00 00 00 01 10 00 58 52 43 45 01 00 01 0f 12 34 56 78 81 00 fc 01 [1787941710.004017] debug | CustomAgent.cpp | recv_message | [ CAN_ISOTP ] | client_key: 0x00000000, len: 24, data: 0000: 80 00 00 00 00 01 10 00 58 52 43 45 01 00 01 0F 12 34 56 78 81 00 FC 01 [1787941710.004333] info | Root.cpp | create_client | create | client_key: 0x12345678, session_id: 0x81 [1787941710.004368] info | SessionManager.hpp | establish_session | session established | client_key: 0x12345678, address: [DEBUG] custom_send called, message_length19 81 00 00 00 04 01 0b 00 00 00 58 52 43 45 01 00 01 0f 00 [ISOTP] TX CAN id0x002 dlc8 data10 13 81 00 00 00 04 01 [DEBUG] custom_send: isotp_send succeeded [1787941710.004511] debug | CustomAgent.cpp | send_message | [** CAN_ISOTP **] | client_key: 0x12345678, len: 19, data: 0000: 81 00 00 00 04 01 0B 00 00 00 58 52 43 45 01 00 01 0F 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] TX CAN id0x002 dlc8 data21 0b 00 00 00 58 52 43 [ISOTP] TX CAN id0x002 dlc8 data22 45 01 00 01 0f 00 00 [ISOTP] RX CAN id0x001 dlc8 data10 34 81 80 00 00 01 07 [ISOTP] TX CAN id0x002 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data21 2c 00 00 0a 00 01 01 [ISOTP] RX CAN id0x001 dlc8 data22 03 00 00 1e 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data23 00 01 00 00 16 00 00 [ISOTP] RX CAN id0x001 dlc8 data24 00 72 61 36 6d 34 5f [ISOTP] RX CAN id0x001 dlc8 data25 66 6c 61 73 68 5f 70 [ISOTP] RX CAN id0x001 dlc8 data26 75 62 6c 69 73 68 65 [ISOTP] RX CAN id0x001 dlc8 data27 72 00 00 00 00 00 00 [DEBUG] custom_recv: received complete message, len52 81 80 00 00 01 07 2c 00 00 0a 00 01 01 03 00 00 1e 00 00 00 00 01 00 00 16 00 00 00 72 61 36 6d 34 5f 66 6c 61 73 68 5f 70 75 62 6c 69 73 68 65 72 00 00 00 [1787941710.122750] debug | CustomAgent.cpp | recv_message | [ CAN_ISOTP ] | client_key: 0x12345678, len: 52, data: 0000: 81 80 00 00 01 07 2C 00 00 0A 00 01 01 03 00 00 1E 00 00 00 00 01 00 00 16 00 00 00 72 61 36 6D 0020: 34 5F 66 6C 61 73 68 5F 70 75 62 6C 69 73 68 65 72 00 00 00 [1787941710.129614] info | ProxyClient.cpp | create_participant | participant created | client_key: 0x12345678, participant_id: 0x000(1) [DEBUG] custom_send called, message_length14 81 80 00 00 05 01 06 00 00 0a 00 01 00 00 [ISOTP] TX CAN id0x002 dlc8 data10 0e 81 80 00 00 05 01 [DEBUG] custom_send: isotp_send succeeded [1787941710.130041] debug | CustomAgent.cpp | send_message | [** CAN_ISOTP **] | client_key: 0x12345678, len: 14, data: 0000: 81 80 00 00 05 01 06 00 00 0A 00 01 00 00 [DEBUG] custom_send called, message_length13 81 00 00 00 0a 01 05 00 01 00 00 00 80 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] TX CAN id0x002 dlc8 data21 06 00 00 0a 00 01 00 [ISOTP] RX CAN id0x001 dlc8 data10 0d 81 00 00 00 0b 01 [ISOTP] TX CAN id0x002 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] TX CAN id0x002 dlc8 data22 00 00 00 00 00 00 00 [DEBUG] custom_send: waited 53ms for previous TX, retry [ISOTP] TX CAN id0x002 dlc8 data10 0d 81 00 00 00 0a 01 [DEBUG] custom_send: isotp_send succeeded [1787941710.209888] debug | CustomAgent.cpp | send_message | [** CAN_ISOTP **] | client_key: 0x12345678, len: 13, data: 0000: 81 00 00 00 0A 01 05 00 01 00 00 00 80 [ISOTP] RX CAN id0x001 dlc8 data21 05 00 00 00 00 00 80 [DEBUG] custom_recv: received complete message, len13 81 00 00 00 0b 01 05 00 00 00 00 00 80 [1787941710.210877] debug | CustomAgent.cpp | recv_message | [ CAN_ISOTP ] | client_key: 0x12345678, len: 13, data: 0000: 81 00 00 00 0B 01 05 00 00 00 00 00 80 [DEBUG] custom_send called, message_length13 81 00 00 00 0a 01 05 00 01 00 00 00 80 [ISOTP] RX CAN id0x001 dlc8 data10 0d 81 00 00 00 0a 01 [ISOTP] TX CAN id0x002 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] RX CAN id0x001 dlc8 data30 08 00 00 00 00 00 00 [ISOTP] TX CAN id0x002 dlc8 data21 05 00 01 00 00 00 80 [DEBUG] custom_send: waited 3ms for previous TX, retry [ISOTP] TX CAN id0x002 dlc8 data10 0d 81 00 00 00 0a 01 [DEBUG] custom_send: isotp_send succeeded [1787941710.215807] debug | CustomAgent.cpp | send_message | [** CAN_ISOTP **] | client_key: 0x12345678, len: 13, data:4.4 视频演示以下视频演示了从 Agent 启动、节点连接、话题发布到数据接收的完整流程微信视频2026-08-29_234547_053视频中可以看到Agent 端打印的 CAN 帧收发日志TX/RX 十六进制数据。RA6M4 端通过串口输出的 micro-ROS 节点状态信息。上位机订阅到话题数据并实时刷新显示。STM32_PY32_CH32_RA8/ra6m4https://download.csdn.net/download/MakeWorks/93356894
返回列表