GD32H759 + RT-Thread 工控实战--第1篇 CAN总线
前言本篇开始调试GD32H7的CAN目标为跑通回环、自发收、以及与上位机通讯。需要额外2条杜邦线、一块candlelight USB-CAN模块。本篇为边开发边记载所以阅读顺序可能不那么愉快。一、驱动代码编写与kconfig修改1.1 为何要自己编写can驱动首先rt-thread v5.2.2自带的gd32驱动(~/gd32/rt-thread/bsp/gd32/arm/libraries/gd32_drivers/drv_can.c)并不支持gd32h7的flexCAN IP。它只支持 bxCAN ( basic extended CANgd32F4/F5/F1都是这个类型的CAN IP)。以下对比来自于互联网flexCAN vs bxCANbxCANflexCAN收发缓冲3 个发送邮箱 2 个三级接收 FIFO32 个 mailbox 外设内部 message RAM滤波28 组滤波器标识符/掩码模式RX FIFO ID 滤波表 单 mailbox 精确匹配CAN-FD不支持支持1.2 、驱动代码及kconfig修改1.2.1、一些有用的用户手册信息官方例程参考引脚CAN1tx:PB12、rx:PB5(AF9) CAN2tx:PF7、PF6(AF2)时钟rcu_can_clock_config(IDX_CANx, RCU_CANSRC_APB2)→300MHz分频器只有 10bit最大 1024300MHz 时钟下10kbps 物理达不到官方固件库代码里RCU 宏只有RCU_CAN1 / RCU_CAN2——没有 CAN0因此我们从CAN1开始编号message RAM 在外设地址空间CAN_BASE0x80不经过 D-cache无需考虑一致性问题发送完成判定发送邮箱 code 从CAN_MB_TX_STATUS_DATA(12)自动变回INACTIVE(8)1.2.2、驱动代码花了少许时间看了一会rt-thread的CAN框架代(components/drivers/can/dev_can.c)可知can框架对can驱动要实现为以下5个接口以及我总结的5点理解struct rt_can_ops { rt_err_t (*configure)(struct rt_can_device *can, struct can_configure *cfg); rt_err_t (*control)(struct rt_can_device *can, int cmd, void *arg); int (*sendmsg)(struct rt_can_device *can, const void *buf, rt_uint32_t boxno); int (*recvmsg)(struct rt_can_device *can, void *buf, rt_uint32_t fifo); int (*sendmsg_nonblocking)(struct rt_can_device *can, const void *buf); };sendmsg返回 RT_EOK只表示硬件已经受理写完成依靠中断上报。rt_hw_can_isr(can, RT_CAN_EVENT_TX_DONE | (boxno 8));接收对称RX 中断里清标志、上报RT_CAN_EVENT_RX_IND | (fifo 8)框架回调应用的 rx_indicate应用再rt_device_read→ 驱动recvmsg取帧。config.sndboxnumber必须如实填硬件发送邮箱数——框架据此分配 box 号。设备 open 时框架会依次control(SET_INT)使能 RX/TX 中断并总是附带RT_DEVICE_CAN_INT_ERR注册入口rt_hw_can_register(dev-can_dev, can1, ops, priv)建议INIT_BOARD_EXPORT自动初始化现在贴出代码(bsp/gd32/arm/libraries/gd32_drivers/drv_can_h7.c)/* * File : drv_can_h7.c * This file is part of RT-Thread RTOS * COPYRIGHT (C) 2006 - 2025, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * GD32H7xx 的 CAN 控制器是 FlexCAN 风格架构邮箱 片内消息 RAM原生支持 * CAN-FD与 GD32F1/F4/F5 的 bxCAN 完全不同无法复用 drv_can.c故独立实现。 * * v1 范围经典 CAN 8 字节帧标准/扩展帧中断收发 * RX 固定邮箱 MB0TX 固定邮箱 MB1 * 支持 NORMAL / LISTEN(监听) / LOOPBACK(回环) 模式。 * * 已知限制v2 计划 * - 未接 CANx_Error_IRQn/Busoff_IRQn总线异常时 blocking 写会等不到 TX_DONE * - 过滤器为单邮箱粗粒度id public mask未实现 RT_CAN_USING_HDR 多过滤器 * - 未启用 CAN-FD。 */ #include board.h #include rtdevice.h #if defined(BSP_USING_CAN) defined(SOC_SERIES_GD32H7xx) #define H7_CAN_RX_MB 0U #define H7_CAN_TX_MB 1U /* 硬件实例配置 */ struct h7_can_hw { rt_uint32_t can_x; const char *name; rcu_periph_enum can_clk; can_idx_enum rcu_idx; IRQn_Type msg_irqn; rt_uint32_t af; rt_uint32_t tx_port; rt_uint32_t tx_pin; rcu_periph_enum tx_clk; rt_uint32_t rx_port; rt_uint32_t rx_pin; rcu_periph_enum rx_clk; }; struct h7_can_device { struct rt_can_device can_dev; const struct h7_can_hw *hw; can_mailbox_descriptor_struct rx_desc; rt_uint8_t rx_buf[8]; rt_uint32_t filter_id; /* 0 mask 0 接收全部 */ rt_uint32_t filter_mask; /* bit1 表示该位参与匹配 */ }; /* 波特率表CAN 时钟 APB2 300MHz * TQ 1 prop tseg1 tseg2baud 300MHz / presc / TQ * 分频器 10bit最大 102410kbps 在 300MHz 下不可达故不提供 */ struct h7_can_baud { rt_uint32_t baud; rt_uint8_t sjw, prop, tseg1, tseg2; rt_uint16_t presc; }; static const struct h7_can_baud h7_can_baud_tbl[] { /* baud sjw prop tseg1 tseg2 presc TQ15 采样点80%1M 为官方例程值 */ { CAN1MBaud, 1, 2, 5, 2, 30 },/* TQ10采样点 80% */ { CAN800kBaud, 1, 2, 9, 3, 25 }, { CAN500kBaud, 1, 2, 9, 3, 40 }, { CAN250kBaud, 1, 2, 9, 3, 80 }, { CAN125kBaud, 1, 2, 9, 3, 160 }, { CAN100kBaud, 1, 2, 9, 3, 200 }, { CAN50kBaud, 1, 2, 9, 3, 400 }, { CAN20kBaud, 1, 2, 9, 3, 1000 }, }; static const struct h7_can_hw h7_can_hw_tbl[] { #ifdef BSP_USING_CAN1 { .can_x CAN1, .name can1, .can_clk RCU_CAN1, .rcu_idx IDX_CAN1, .msg_irqn CAN1_Message_IRQn, .af GPIO_AF_9, #if defined(BSP_CAN1_TX_PB13) .tx_port GPIOB, .tx_pin GPIO_PIN_13, .tx_clk RCU_GPIOB, #else #error Select CAN1 tx pin #endif #if defined(BSP_CAN1_RX_PB5) .rx_port GPIOB, .rx_pin GPIO_PIN_5, .rx_clk RCU_GPIOB, #else #error Select CAN1 rx pin #endif }, #endif #ifdef BSP_USING_CAN2 { .can_x CAN2, .name can2, .can_clk RCU_CAN2, .rcu_idx IDX_CAN2, .msg_irqn CAN2_Message_IRQn, .af GPIO_AF_2, #if defined(BSP_CAN2_TX_PF7) .tx_port GPIOF, .tx_pin GPIO_PIN_7, .tx_clk RCU_GPIOF, #else #error Select CAN2 tx pin #endif #if defined(BSP_CAN2_RX_PF6) .rx_port GPIOF, .rx_pin GPIO_PIN_6, .rx_clk RCU_GPIOF, #else #error Select CAN2 rx pin #endif }, #endif }; #ifdef BSP_USING_CAN1 static struct h7_can_device h7_can1; #endif #ifdef BSP_USING_CAN2 static struct h7_can_device h7_can2; #endif static void h7_can_gpio_init(const struct h7_can_hw *hw) { /* CAN 时钟源选 APB2300MHz官方例程做法 */ rcu_can_clock_config(hw-rcu_idx, RCU_CANSRC_APB2); rcu_periph_clock_enable(hw-can_clk); rcu_periph_clock_enable(hw-tx_clk); rcu_periph_clock_enable(hw-rx_clk); gpio_output_options_set(hw-tx_port, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, hw-tx_pin); gpio_mode_set(hw-tx_port, GPIO_MODE_AF, GPIO_PUPD_PULLUP, hw-tx_pin); gpio_af_set(hw-tx_port, hw-af, hw-tx_pin); gpio_output_options_set(hw-rx_port, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, hw-rx_pin); gpio_mode_set(hw-rx_port, GPIO_MODE_AF, GPIO_PUPD_NONE, hw-rx_pin); gpio_af_set(hw-rx_port, hw-af, hw-rx_pin); } static rt_err_t h7_can_config(struct rt_can_device *can, struct can_configure *cfg) { struct h7_can_device *dev (struct h7_can_device *)can-parent.user_data; rt_uint32_t can_x dev-hw-can_x; can_parameter_struct param; can_operation_modes_enum mode CAN_NORMAL_MODE; const struct h7_can_baud *bt h7_can_baud_tbl[0]; ErrStatus st; rt_kprintf([drv] cfg enter: mode%lu baud%lu\n, cfg-mode, cfg-baud_rate); for (rt_uint32_t i 0; i sizeof(h7_can_baud_tbl) / sizeof(h7_can_baud_tbl[0]); i) { if (h7_can_baud_tbl[i].baud cfg-baud_rate) { bt h7_can_baud_tbl[i]; break; } } can_deinit(can_x); can_struct_para_init(CAN_INIT_STRUCT, param); param.internal_counter_source CAN_TIMER_SOURCE_BIT_CLOCK; param.mb_tx_order CAN_TX_HIGH_PRIORITY_MB_FIRST; param.mb_tx_abort_enable ENABLE; param.local_priority_enable DISABLE; param.mb_rx_ide_rtr_type CAN_IDE_RTR_FILTERED; param.mb_remote_frame CAN_STORE_REMOTE_REQUEST_FRAME; param.rx_private_filter_queue_enable DISABLE; param.edge_filter_enable DISABLE; param.protocol_exception_enable DISABLE; param.rx_filter_order CAN_RX_FILTER_ORDER_MAILBOX_FIRST; param.memory_size CAN_MEMSIZE_32_UNIT; param.mb_public_filter dev-filter_mask; param.self_reception (cfg-mode RT_CAN_MODE_LOOPBACK || cfg-mode RT_CAN_MODE_LOOPBACKANLISTEN) ? ENABLE : DISABLE; param.resync_jump_width bt-sjw; param.prop_time_segment bt-prop; param.time_segment_1 bt-tseg1; param.time_segment_2 bt-tseg2; param.prescaler bt-presc; st can_init(can_x, param); rt_kprintf([drv] can_init - %d\n, st); if (st ! SUCCESS) return -RT_ERROR; can_struct_para_init(CAN_MDSC_STRUCT, dev-rx_desc); dev-rx_desc.code CAN_MB_RX_STATUS_EMPTY; dev-rx_desc.id dev-filter_id; dev-rx_desc.data (rt_uint32_t *)dev-rx_buf; can_mailbox_config(can_x, H7_CAN_RX_MB, dev-rx_desc); rt_kprintf([drv] rx mailbox ok\n); switch (cfg-mode) { case RT_CAN_MODE_NORMAL: mode CAN_NORMAL_MODE; break; case RT_CAN_MODE_LISTEN: mode CAN_MONITOR_MODE; break; case RT_CAN_MODE_LOOPBACK: case RT_CAN_MODE_LOOPBACKANLISTEN: mode CAN_LOOPBACK_SILENT_MODE; break; default: rt_kprintf([drv] bad mode\n); return -RT_ERROR; } st can_operation_mode_enter(can_x, mode); rt_kprintf([drv] mode_enter(%d) - %d\n, mode, st); if (st ! SUCCESS) return -RT_ERROR; if (can-parent.open_flag RT_DEVICE_FLAG_INT_RX) { can_interrupt_enable(can_x, CAN_INT_MB0); nvic_irq_enable(dev-hw-msg_irqn, 1, 0); } if (can-parent.open_flag RT_DEVICE_FLAG_INT_TX) { can_interrupt_enable(can_x, CAN_INT_MB1); nvic_irq_enable(dev-hw-msg_irqn, 1, 0); } rt_kprintf([drv] cfg done\n); return RT_EOK; } static rt_err_t h7_can_control(struct rt_can_device *can, int cmd, void *arg) { struct h7_can_device *dev (struct h7_can_device *)can-parent.user_data; rt_uint32_t can_x dev-hw-can_x; rt_uint32_t argval; switch (cmd) { case RT_DEVICE_CTRL_SET_INT: argval (rt_uint32_t)(rt_ubase_t)arg; if (argval RT_DEVICE_FLAG_INT_RX) { can_interrupt_enable(can_x, CAN_INT_MB0); nvic_irq_enable(dev-hw-msg_irqn, 1, 0); } else if (argval RT_DEVICE_FLAG_INT_TX) { can_interrupt_enable(can_x, CAN_INT_MB1); nvic_irq_enable(dev-hw-msg_irqn, 1, 0); } /* RT_DEVICE_CAN_INT_ERRv1 未接错误中断直接忽略 */ break; case RT_DEVICE_CTRL_CLR_INT: argval (rt_uint32_t)(rt_ubase_t)arg; if (argval RT_DEVICE_FLAG_INT_RX) { can_interrupt_disable(can_x, CAN_INT_MB0); nvic_irq_disable(dev-hw-msg_irqn); } else if (argval RT_DEVICE_FLAG_INT_TX) { can_interrupt_disable(can_x, CAN_INT_MB1); nvic_irq_disable(dev-hw-msg_irqn); } break; case RT_CAN_CMD_SET_MODE: argval (rt_uint32_t)(rt_ubase_t)arg; if (argval ! RT_CAN_MODE_NORMAL argval ! RT_CAN_MODE_LISTEN argval ! RT_CAN_MODE_LOOPBACK argval ! RT_CAN_MODE_LOOPBACKANLISTEN) { return -RT_ERROR; } if (argval ! can-config.mode) { can-config.mode argval; return h7_can_config(can, can-config); } break; case RT_CAN_CMD_SET_BAUD: argval (rt_uint32_t)(rt_ubase_t)arg; if (argval ! CAN1MBaud argval ! CAN800kBaud argval ! CAN500kBaud argval ! CAN250kBaud argval ! CAN125kBaud argval ! CAN100kBaud argval ! CAN50kBaud argval ! CAN20kBaud) { return -RT_ERROR; /* 10k 在 300MHz 时钟下不可达 */ } if (argval ! can-config.baud_rate) { can-config.baud_rate argval; return h7_can_config(can, can-config); } break; case RT_CAN_CMD_SET_PRIV: argval (rt_uint32_t)(rt_ubase_t)arg; if (argval ! RT_CAN_MODE_PRIV argval ! RT_CAN_MODE_NOPRIV) { return -RT_ERROR; } can-config.privmode argval; /* 由框架层处理无需重配硬件 */ break; case RT_CAN_CMD_SET_FILTER: if (arg RT_NULL) { dev-filter_id 0; dev-filter_mask 0; } else { struct rt_can_filter_config *fcfg (struct rt_can_filter_config *)arg; if (fcfg-count 0) { dev-filter_id fcfg-items[0].id; dev-filter_mask fcfg-items[0].mask; } } return h7_can_config(can, can-config); case RT_CAN_CMD_GET_STATUS: { can_error_counter_struct errcnt; can_error_counter_get(can_x, errcnt); can-status.rcverrcnt errcnt.rx_errcnt; can-status.snderrcnt errcnt.tx_errcnt; can-status.errcode (rt_uint32_t)can_error_state_get(can_x); rt_memcpy(arg, can-status, sizeof(can-status)); break; } default: break; } return RT_EOK; } static rt_ssize_t h7_can_sendmsg(struct rt_can_device *can, const void *buf, rt_uint32_t box_num) { struct h7_can_device *dev (struct h7_can_device *)can-parent.user_data; struct rt_can_msg *pmsg (struct rt_can_msg *)buf; rt_uint32_t can_x dev-hw-can_x; can_mailbox_descriptor_struct tx_msg; /* 上一帧还没发出去 */ if (can_mailbox_code_get(can_x, H7_CAN_TX_MB) CAN_MB_TX_STATUS_DATA) { return -RT_EBUSY; } can_struct_para_init(CAN_MDSC_STRUCT, tx_msg); tx_msg.code CAN_MB_TX_STATUS_DATA; tx_msg.ide (pmsg-ide RT_CAN_EXTID) ? 1U : 0U; tx_msg.rtr (pmsg-rtr RT_CAN_RTR) ? 1U : 0U; tx_msg.id pmsg-id; tx_msg.data_bytes (pmsg-len 8U) ? 8U : pmsg-len; tx_msg.data (rt_uint32_t *)pmsg-data; can_mailbox_config(can_x, H7_CAN_TX_MB, tx_msg); return RT_EOK; } static rt_ssize_t h7_can_recvmsg(struct rt_can_device *can, void *buf, rt_uint32_t fifo) { struct h7_can_device *dev (struct h7_can_device *)can-parent.user_data; struct rt_can_msg *pmsg (struct rt_can_msg *)buf; if (can_mailbox_receive_data_read(dev-hw-can_x, H7_CAN_RX_MB, dev-rx_desc) ! SUCCESS) { return -RT_ERROR; } pmsg-ide dev-rx_desc.ide ? RT_CAN_EXTID : RT_CAN_STDID; pmsg-id dev-rx_desc.id; pmsg-rtr dev-rx_desc.rtr ? RT_CAN_RTR : RT_CAN_DTR; pmsg-len (dev-rx_desc.data_bytes 8U) ? 8U : (rt_uint8_t)dev-rx_desc.data_bytes; rt_memcpy(pmsg-data, dev-rx_buf, pmsg-len); pmsg-hdr_index 0; return RT_EOK; } static rt_ssize_t h7_can_sendmsg_nonblocking(struct rt_can_device *can, const void *buf) { return h7_can_sendmsg(can, buf, 0); } static const struct rt_can_ops h7_can_ops { h7_can_config, h7_can_control, h7_can_sendmsg, h7_can_recvmsg, h7_can_sendmsg_nonblocking, }; static void h7_can_msg_isr(struct h7_can_device *dev) { rt_uint32_t can_x dev-hw-can_x; if (can_interrupt_flag_get(can_x, CAN_INT_FLAG_MB0)) { can_interrupt_flag_clear(can_x, CAN_INT_FLAG_MB0); rt_hw_can_isr(dev-can_dev, RT_CAN_EVENT_RX_IND | (0 8)); } if (can_interrupt_flag_get(can_x, CAN_INT_FLAG_MB1)) { can_interrupt_flag_clear(can_x, CAN_INT_FLAG_MB1); rt_hw_can_isr(dev-can_dev, RT_CAN_EVENT_TX_DONE | (0 8)); } } #ifdef BSP_USING_CAN1 void CAN1_Message_IRQHandler(void) { rt_interrupt_enter(); h7_can_msg_isr(h7_can1); rt_interrupt_leave(); } #endif #ifdef BSP_USING_CAN2 void CAN2_Message_IRQHandler(void) { rt_interrupt_enter(); h7_can_msg_isr(h7_can2); rt_interrupt_leave(); } #endif int rt_hw_can_init(void) { struct can_configure config CANDEFAULTCONFIG; rt_uint32_t i 0; config.privmode RT_CAN_MODE_NOPRIV; config.ticks 50; config.sndboxnumber 1; /* 单 TX 邮箱框架只会分配 box 0 */ #ifdef BSP_USING_CAN1 h7_can1.hw h7_can_hw_tbl[i]; h7_can1.can_dev.config config; h7_can_gpio_init(h7_can1.hw); rt_hw_can_register(h7_can1.can_dev, h7_can1.hw-name, h7_can_ops, h7_can1); #endif #ifdef BSP_USING_CAN2 h7_can2.hw h7_can_hw_tbl[i]; h7_can2.can_dev.config config; h7_can_gpio_init(h7_can2.hw); rt_hw_can_register(h7_can2.can_dev, h7_can2.hw-name, h7_can_ops, h7_can2); #endif return 0; } INIT_BOARD_EXPORT(rt_hw_can_init); #endif /* BSP_USING_CAN SOC_SERIES_GD32H7xx */代码说明驱动并没实现对接rt-thread框架下的hdr滤波表也仅使用了32 个 mailbox 中的两个MB0 收、MB1 发。后续或许会花些时间把它写完整 :D1.2.3、修改kconfig文件以及sconscript编译脚本gd32_drivers/SConscript修改如下if GetDepend(RT_USING_CAN): if GetDepend(SOC_SERIES_GD32H7xx): src [drv_can_h7.c] else: src [drv_can.c]board/Kconfig:menuconfig BSP_USING_CAN bool Enable CAN select RT_USING_CAN default n if BSP_USING_CAN config BSP_USING_CAN1 bool Enable CAN1 (TX:PB13 RX:PB5) default n config BSP_USING_CAN2 bool Enable CAN2 (TX:PF7 RX:PF6) default n endif二、测试代码2.1、代码编写~/gd32/rt-thread/bsp/gd32/arm/gd32h759i-eval/applications/can_test.c/* * can_test.c — RT-Thread 标准 CAN 设备 API 验证 * can_test loop : can1 回环自测免接线 * can_test net : can2 发 - can1 收需 JP12-JP14 H-H L-L */ #include rtthread.h #include rtdevice.h static rt_err_t can_rx_ind(rt_device_t dev, rt_size_t size) { struct rt_can_msg msg; while (rt_device_read(dev, 0, msg, sizeof(msg)) sizeof(msg)) { rt_kprintf([CAN] rx id0x%lX len%d data, msg.id, msg.len); for (int i 0; i msg.len; i) rt_kprintf(%02X , msg.data[i]); rt_kprintf(\n); } return RT_EOK; } static void can_test(int argc, char **argv) { rt_bool_t loop (argc 1 !rt_strcmp(argv[1], loop)); const char *txname loop ? can1 : can2; rt_device_t rxdev rt_device_find(can1); rt_device_t txdev rt_device_find(txname); if (!rxdev || !txdev) { rt_kprintf(can device not found, check menuconfig\n); return; } rt_device_control(rxdev, RT_CAN_CMD_SET_BAUD, (void *)CAN1MBaud); rt_device_control(txdev, RT_CAN_CMD_SET_BAUD, (void *)CAN1MBaud); if (loop) { rt_device_control(rxdev, RT_CAN_CMD_SET_MODE, (void *)RT_CAN_MODE_LOOPBACK); } else { rt_device_control(rxdev, RT_CAN_CMD_SET_MODE, (void *)RT_CAN_MODE_NORMAL); rt_device_control(txdev, RT_CAN_CMD_SET_MODE, (void *)RT_CAN_MODE_NORMAL); } rt_device_open(rxdev, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_INT_TX); if (txdev ! rxdev) rt_device_open(txdev, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_TX); rt_device_set_rx_indicate(rxdev, can_rx_ind); struct rt_can_msg msg {0}; msg.id 0x55; msg.ide RT_CAN_STDID; msg.rtr RT_CAN_DTR; msg.len 8; for (int i 0; i 8; i) msg.data[i] 0xA1 i; rt_kprintf([CAN] %s tx id0x55, A1..A8\n, txname); if (rt_device_write(txdev, 0, msg, sizeof(msg)) ! sizeof(msg)) rt_kprintf([CAN] tx failed\n); } MSH_CMD_EXPORT(can_test, can test: can_test loop | net);2026年9月11日追加编译过程scons --menuconfig , (Top) → Hardware Drivers Config → On-chip Peripheral Drivers → Enable CAN ,选中 CAN1和CAN2scons -j162.2、跳线设置JP65(1,2) 、 JP53(2,3) 、 JP67(2,3)插跳线时可以观察板子上的丝印印刷得很清楚2.3、自发自收回环测试在串口中msh控制台中输入msh /can_test loop看到如下信息就代表回环自发自发测试成功:[drv] cfg enter: mode2 baud1000000 [drv] can_init - 1 [drv] rx mailbox ok [drv] mode_enter(2) - 1 [drv] cfg done [drv] cfg enter: mode2 baud1000000 [drv] can_init - 1 [drv] rx mailbox ok [drv] mode_enter(2) - 1 [drv] cfg done [CAN] can1 tx id0x55, A1..A8 [CAN] rx id0x55 len8 dataA1 A2 A3 A4 A5 A6 A7 A82.4、双节点通讯(net模式)板上自带两路 CAN 收发器杜邦线对接两个排针JP12、JP14即可组网JP12 的 H ── JP14 的 H JP12 的 L ── JP14 的 L串口输入msh /can_test net看到如下信息就代表双节点通讯成功[drv] cfg enter: mode0 baud1000000 [drv] can_init - 1 [drv] rx mailbox ok [drv] mode_enter(0) - 1 [drv] cfg done [drv] cfg enter: mode0 baud1000000 [drv] can_init - 1 [drv] rx mailbox ok [drv] mode_enter(0) - 1 [drv] cfg done [CAN] can2 tx id0x55, A1..A8 [CAN] rx id0x55 len8 dataA1 A2 A3 A4 A5 A6 A7 A82.5、上位机通讯测试2026年9月11日追加我购买的模块是 candlelight USB-CAN类型的。上位机测试软件使用了 cangaroo windows版感谢社区有人编译了它https://github.com/yltzdhbc/cangaroo_win/tree/master/接插线板子的JP12 H/L 对应链接模块的 H/L上位机cangaroo设置如图板子上电使用双节点通讯模式一样输入msh / can_test net然后可以在板子串口里看到msh /can_test net [CAN] can2 tx id0x55, A1..A8 msh /上位机上可以看到(截图中可以看到我发了很多次)