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

资讯详情

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

STM32G431 单路 CAN 移植到 STM32G474 双路 GS-USB:一次真实的 Zephyr 4.4 踩坑实录

STM32G431 单路 CAN 移植到 STM32G474 双路 GS-USB:一次真实的 Zephyr 4.4 踩坑实录 摘要本文记录 roboto_usb2can 从 STM32G431CBT6 单通道方案迁移到 STM32G474CBT6 双 FDCAN 的完整过程。内容覆盖 Zephyr 板卡配置、设备树引脚、GS-USB 双通道、USB API 编译错误、LSE/LPTIM1 启动故障、ST-Link 烧录、Linux 驱动、四路 SocketCAN 验证以及接口顺序固定思路。建议标签STM32G474、Zephyr、FDCAN、GS-USB、SocketCAN这次需求看起来只是“把 STM32G431 换成 STM32G474”实际却同时涉及 MCU 型号、时钟树、设备树、两路 FDCAN、Zephyr USB API 和 Linux 网络接口管理。过程中既遇到了编译期类型错误也遇到了“USB 串口测试正常但 GS-USB 固件不枚举”这类很容易误判的硬件问题。最终结果是两颗 STM32G474CBT6 均通过 USB Hub 连接 RK3588每颗 MCU 提供两路 GS-USB 通道Linux 最终识别出can0至can3四个 SocketCAN 接口。文章目录一、原工程和目标硬件二、板卡定义从 G431 切换到 G4741. 修改 SoC 选择2. 修改烧录器目标3. 切换 DTSI 和系统时钟三、根据原理图配置双 FDCAN1. 引脚映射2. 添加两个 CANnectivity 通道四、Zephyr 4.4 USB API 编译错误五、固件逻辑扩展为双通道六、当前硬件没有状态灯怎么办七、第二颗 STM32 不枚举LSE/LPTIM1 踩坑现象定位解决八、ST-Link 烧录和 GDB 调试问题1. DEV_TARGET_CMD_ERR2. GDB 找不到 zephyr.elf3. target extended-remote :3333 连接失败九、RK3588 上的 gs_usb、udev 和 can-utils1. lsusb 能说明什么2. udev 规则不是驱动3. 安装测试工具十、四路 CAN 验证与接口顺序1. 查看所有 CAN 设备2. 逐个启动接口3. 最小收发测试4. 为什么 can0can3 顺序会变化5. RK3588 原生 CAN 没有外部接口怎么办十一、最终验证结果总结参考资料与代码一、原工程和目标硬件原始项目基于 STM32G431只有一路 FDCAN。新的背板使用 STM32G474CBT6而且一颗 MCU 需要同时提供两路 CAN。项目原版本移植后MCUSTM32G431CBT6STM32G474CBT6Zephyr SoCstm32g431xxstm32g474xx系统时钟HSI PLL8 MHz HSE PLL160 MHzCANFDCAN1PB8/PB9FDCAN2PB5/PB6FDCAN3PB3/PB4GS-USB 通道12状态灯三路 GPIO LED当前背板未连接 MCU 状态灯Linux 结果单设备单接口两设备共四个 SocketCAN 接口完整链路可以理解为RK3588 Linux └── USB Hub ├── STM32G474 #1USB VID:PID 1d50:606f │ ├── GS-USB channel 0 → FDCAN2 → 物理 CAN1 │ └── GS-USB channel 1 → FDCAN3 → 物理 CAN2 └── STM32G474 #2USB VID:PID 1d50:606f ├── GS-USB channel 0 → FDCAN2 → 物理 CAN1 └── GS-USB channel 1 → FDCAN3 → 物理 CAN2二、板卡定义从 G431 切换到 G474Zephyr 中不能只修改编译器宏。板卡的 Kconfig、元数据、runner 和 DTS/DTSI 必须一起切换。1. 修改 SoC 选择boards/roboto_usb2can/Kconfig.roboto_usb2canconfig BOARD_ROBOTO_USB2CAN select SOC_STM32G474XXboard.yml和roboto_usb2can.yaml同步改为stm32g474xx并根据 STM32G474CBT6 调整 RAM/Flash 描述。2. 修改烧录器目标board.cmake中最容易遗漏的是调试器芯片名称board_runner_args(jlink --deviceSTM32G474CB) board_runner_args(pyocd --targetstm32g474cbtx) board_runner_args(stm32cubeprogrammer --portswd --reset-modehw) board_runner_args(probe-rs --chipSTM32G474CBTx)如果这里仍然保留 G431可能出现编译正常、烧录器却按错误目标连接的情况。3. 切换 DTSI 和系统时钟#include st/g4/stm32g474Xb.dtsi #include st/g4/stm32g474c(b-c-e)tx-pinctrl.dtsi clk_hse { clock-frequency DT_FREQ_M(8); status okay; }; pll { div-m 2; mul-n 80; div-p 2; div-q 4; div-r 2; clocks clk_hse; status okay; }; rcc { clocks pll; clock-frequency DT_FREQ_M(160); };USB Full Speed 继续使用 PA11/PA12并由 HSI48 提供 48 MHz 时钟。这里要区分“CPU 系统时钟”和“USB 48 MHz 时钟”不要因为系统使用 HSE PLL 就误删 HSI48。三、根据原理图配置双 FDCAN1. 引脚映射根据背板原理图第 13 页GS-USB 通道STM32 外设引脚背板网络0FDCAN2PB5 RX / PB6 TXCAN11FDCAN3PB3 RX / PB4 TXCAN2这里有一个实际踩坑点原理图中 PB4 附近的外设文字与 PB3 标注存在矛盾。最终不能只看网络旁的一行文本而要同时核对 STM32G474 数据手册和 Zephyr pinctrl 定义。PB3/PB4 在当前封装和复用配置下应作为 FDCAN3_RX/FDCAN3_TX。设备树配置如下fdcan2 { pinctrl-0 fdcan2_rx_pb5 fdcan2_tx_pb6; pinctrl-names default; clocks rcc STM32_CLOCK(APB1, 25), rcc STM32_SRC_PLL_Q FDCAN_SEL(1); status okay; }; fdcan3 { pinctrl-0 fdcan3_rx_pb3 fdcan3_tx_pb4; pinctrl-names default; clocks rcc STM32_CLOCK(APB1, 25), rcc STM32_SRC_PLL_Q FDCAN_SEL(1); status okay; };2. 添加两个 CANnectivity 通道cannectivity: cannectivity { compatible cannectivity; timestamp-counter counters2; channel0 { compatible cannectivity-channel; can-controller fdcan2; }; channel1 { compatible cannectivity-channel; can-controller fdcan3; }; };同时将prj.conf中最大通道数由 1 改为 2CONFIG_USBD_GS_USBy CONFIG_USBD_GS_USB_MAX_CHANNELS2 CONFIG_USBD_GS_USB_COMPATIBILITY_MODEy四、Zephyr 4.4 USB API 编译错误第一次在当前 Zephyr 上编译时报错核心内容如下error: initialization of struct net_buf * (*)(...) from incompatible pointer type int (*)(..., struct net_buf *)错误出现在USBD_DESC_BOS_VREQ_DEFINE。原工程的 vendor request 回调属于旧接口返回int响应缓冲区由参数传入当前 Zephyr 4.4 接口要求回调直接返回struct net_buf *。修改后的实现staticstructnet_buf*msos_vendor_handler(conststructusbd_context*constctx,conststructusb_setup_packet*constsetup){if(setup-bRequest0x01setup-wIndexMS_OS_20_DESCRIPTOR_INDEX){uint16_tlenMIN(setup-wLength,sizeof(msos2_desc));structnet_buf*bufusbd_ep_ctrl_data_in_alloc(ctx,len);if(bufNULL){returnNULL;}net_buf_add_mem(buf,msos2_desc,len);returnbuf;}returnNULL;}这个问题的处理原则不是关闭编译警告而是找到宏定义要求的真实函数指针类型再按新 API 完成缓冲区申请和返回。五、固件逻辑扩展为双通道只修改 DTS 和CONFIG_USBD_GS_USB_MAX_CHANNELS还不够应用代码原来仍然只注册 FDCAN1。staticconststructdevice*can_devices[]{DEVICE_DT_GET(DT_NODELABEL(fdcan2)),DEVICE_DT_GET(DT_NODELABEL(fdcan3)),};staticstructcan_error_monitorerr_monitors[ARRAY_SIZE(can_devices)]{0};注册时传入完整数组errgs_usb_register(gs_usb,can_devices,ARRAY_SIZE(can_devices),ops,NULL);原状态回调把所有错误都记到通道 0。双通道后通过user_data传递通道号for(inti0;iARRAY_SIZE(can_devices);i){can_set_state_change_callback(can_devices[i],can_state_change_callback,(void*)(intptr_t)i);}回调中先检查索引范围再访问对应的err_monitors[channel]否则 FDCAN3 的 Bus-Off 或错误计数会污染 FDCAN2 的状态。上位机脚本也同步增加 Channel 0/1 选择框并在启动和停止时遍历两个通道。如果固件已经改成双通道而上位机仍固定发送到通道 0看起来就会像“第二路 CAN 没有工作”。六、当前硬件没有状态灯怎么办原板卡 DTS 定义了三个 LED但 STM32G474 背板没有对应的 MCU 状态灯网络。如果照搬旧引脚可能误占用 TTL、SPI 或 SWD 等功能。处理方式是在led.c中增加设备树条件编译#ifDT_HAS_ALIAS(led0)DT_HAS_ALIAS(led1)DT_HAS_ALIAS(led2)/* 原 LED 实现 */#elseintstatus_led_init(void){return0;}voidstatus_led_usb_set(enumled_statusstatus){ARG_UNUSED(status);}/* 其他接口保持为空操作 */#endif这样既保留上层 GS-USB 事件接口也不会为了“编译通过”随便占一个 GPIO。七、第二颗 STM32 不枚举LSE/LPTIM1 踩坑这是本次最典型的问题。现象第一颗 STM32 烧录后可以出现两路 CAN第二颗烧录同一固件后没有新增 CAN 接口使用简单 USB 虚拟串口测试程序时第二颗 MCU 可以被识别。串口测试成功说明 USB D/D-、供电和 Hub 链路基本正常但不能证明 GS-USB 固件的时钟和所有初始化路径正常。定位对比原理图和设备树后发现固件启用了外部 LSE并把 LPTIM1 作为系统计时来源而当前 MCU2 并没有连接 32.768 kHz 晶振。程序可能在早期时钟或定时器初始化阶段异常导致后面的 GS-USB 注册没有完成。解决clk_lse { status disabled; }; lptim1 { status disabled; };系统计时改用 Zephyr 已启用的 Cortex-M SysTickTIM2 继续单独为 CAN 时间戳提供 1 MHz 计数器timers2 { st,prescaler 159; counters2: counter { status okay; }; };重新执行 pristine 构建并烧录后第二个 GS-USB 设备正常出现Linux 最终识别到四个 CAN 接口。这里得到的经验是同一份测试程序能枚举不代表完整业务固件一定能运行必须继续检查完整固件在 USB 注册前依赖的时钟、定时器和设备初始化。八、ST-Link 烧录和 GDB 调试问题1.DEV_TARGET_CMD_ERR使用软件复位烧录时曾出现Error: ST-LINK error (DEV_TARGET_CMD_ERR)ST-Link 序列号和目标电压能够读到说明主机已经找到调试器失败点在目标控制阶段。最终配置改用硬件复位并保留 NRST 连接west flash--runnerstm32cubeprogrammer如果仍然不稳定可降低频率并 under-reset 连接STM32_Programmer_CLI\-cportSWDfreq400modeURresetHWrst\-wbuild/zephyr/zephyr.hex-v-rst2. GDB 找不到zephyr.elfbuild/zephyr/zephyr.elf: 没有那个文件或目录这是路径错误或构建尚未完成不是 MCU 故障。应先进入项目目录确认文件存在cd~/zephyrproject/samples/roboto_usb2canls-lbuild/zephyr/zephyr.elf3.target extended-remote :3333连接失败GDB 不能凭空提供 3333 端口。需要两个终端# 终端 AST-Link 启动 OpenOCD/GDB Serveropenocd-finterface/stlink.cfg\-ctransport select hla_swd\-ftarget/stm32g4x.cfg\-creset_config none\-cadapter speed 400# 终端 B再启动 GDBarm-zephyr-eabi-gdb build/zephyr/zephyr.elf然后在 GDB 中逐行执行target extended-remote localhost:3333 monitor reset halt break main continuetimed out while waiting for target halted后又出现halted due to debug-request只能说明调试器的 halt 时序不稳定不能直接断定程序卡死。真正判断运行状态要看info registers bt x/10i $pc info threads如果多次暂停时 PC 总停在同一异常处理、轮询或等待位置再结合调用栈判断是否死锁或卡在外设初始化。九、RK3588 上的 gs_usb、udev 和 can-utils1.lsusb能说明什么lsusb看到OpenMoko, Inc. Geschwister Schneider CAN adapter说明 USB 描述符已经被主机读取但它不等于 CAN 控制器已经正常收发。完整检查应包括sudomodprobe gs_usb lsmod|grepgs_usbdmesg|grep-i-Egs_usb|1d50|606fip-brlinkshowtypecanip-detailslinkshowtypecan大多数发行版已经把gs_usb作为内核模块提供不需要从网上另找第三方驱动。如果modprobe gs_usb提示模块不存在应先安装与当前内核匹配的 modules 包只有内核配置确实没有启用时才需要重新配置和编译内核模块。2. udev 规则不是驱动项目中的规则为SUBSYSTEMusb, ATTRS{idVendor}1d50, \ ATTRS{idProduct}606f, MODE0666, GROUPplugdev它只修改 USB 设备节点权限适合 Python/libusb 上位机直接访问设备。Linux SocketCAN 接口由内核gs_usb驱动创建不安装这条规则也不会影响 root 使用can0。两个 STM32 使用相同 VID/PID 时这条规则不需要复制两份一条规则会匹配两个设备。但它也不能保证它们每次开机一定对应固定的can0can3。3. 安装测试工具sudoaptupdatesudoaptinstallcan-utils如果把测试脚本复制到远端不要写入普通用户无权限的/home/根目录scpscripts/test_roboto_usb2can.sh catRK3588_IP:~/sshcatRK3588_IPchmodx ~/test_roboto_usb2can.shsudo~/test_roboto_usb2can.shLC_ALL: cannot change locale只是远端缺少对应 locale 的警告Permission denied的真正原因是普通用户不能直接写/home/。十、四路 CAN 验证与接口顺序1. 查看所有 CAN 设备ip-brlinkshowtypecan也可以直接查看 sysfsls-1/sys/class/net|grep^can本次最终出现can0 can1 can2 can32. 逐个启动接口fordevincan0 can1 can2 can3;dosudoiplinkset$devdownsudoiplinkset$devtypecan bitrate1000000restart-ms100sudoiplinkset$devtxqueuelen2000sudoiplinkset$devupdone检查状态ip-details-statisticslinkshowtypecan正常通信时应重点观察CAN 状态是否为ERROR-ACTIVEberr-counter tx/rx是否持续增加RX/TX packets是否按测试预期变化是否出现BUS-OFF以及restart-ms 100后能否恢复。3. 最小收发测试终端 Acandump can1终端 Bcansend can0123#1122334455667788确认物理总线、终端电阻和波特率一致后再使用cangen或项目脚本进行多通道压力测试。测试结束按CtrlC并确认后台cangen已停止避免持续占用总线。4. 为什么 can0can3 顺序会变化can0等名称由 Linux 按设备探测顺序分配。两个完全相同的 USB 设备经过 Hub 枚举时先后顺序并不是稳定硬件标识。先收集每个接口的 USB 拓扑和序列号fordevin/sys/class/net/can*;doecho${dev##*/}udevadm info--queryproperty--path$dev|\grep-EID_PATH|ID_SERIAL|ID_VENDOR_ID|ID_MODEL_IDdone稳定绑定时应遵循两条原则用ID_PATH区分 USB Hub 的物理端口或者用确认唯一的ID_SERIAL区分两颗 STM32一颗 STM32 的两个通道保持 channel 0、channel 1 的内部顺序再映射为业务名称例如can_mcu1_ch0、can_mcu1_ch1。不要只用相同的 VID/PID 判断是哪一颗 STM32。机器相关的ID_PATH必须通过本机udevadm info获取不能从其他 RK3588 板卡照抄。5. RK3588 原生 CAN 没有外部接口怎么办如果 SoC 原生 CAN 节点仍启用它们可能先占用can0/can1。真正永久禁用应在板级 DTS 或设备树 overlay 中将对应控制器节点设为can0 { status disabled; };实际节点名称以当前 RK3588 板卡 DTS 为准。通常只需要重新生成并部署 DTB/overlay不一定重编整个内核单纯执行ip link set can0 down只会关闭接口不会阻止下次开机重新枚举。十一、最终验证结果固件执行了完整 pristine 构建west build-palways-broboto_usb2can --\-DCMAKE_BUILD_TYPERelease cmake--buildbuild--targetrelease_files结果如下检查项结果STM32G474CBT6 编译通过GS-USB 通道数每颗 MCU 2 路FDCAN2/FDCAN3 设备树状态okayFlash53,096 B / 128 KBRAM32,636 B / 128 KB两颗 STM32 USB 枚举通过RK3588 SocketCAN共识别 4 路基础通信测试通过并已正常停止Windows 双通道实机测试待验证Bus-Off/长时间压力测试待验证发布镜像 SHA-256d80f6a00ddf9f81bd9dd8c418162cd9397318df484d57648b0a9b9906f704ed1总结这次移植最重要的经验不是某一行代码而是把问题分层编译失败 → 检查 API 和类型 烧录失败 → 检查 runner、复位方式和 SWD 链路 USB 不枚举 → 检查完整固件的时钟与初始化路径 lsusb 有设备但无 canX → 检查 gs_usb 驱动绑定 canX 存在但不能通信 → 检查 bitrate、总线和 CAN 状态 接口编号变化 → 使用 USB 物理路径或唯一序列号绑定尤其要避免把lsusb成功等同于程序全部正常也不要因为 USB 串口测试通过就忽略业务固件自己的时钟依赖。把编译、烧录、USB 枚举、内核驱动、SocketCAN 和物理总线分别验证定位效率会高很多。参考资料与代码本文项目https://gitee.com/penguin-ants/gs_usbZephyr 编译、烧录与调试https://docs.zephyrproject.org/latest/develop/west/build-flash-debug.htmlZephyr DeviceTree 文档https://docs.zephyrproject.org/latest/build/dts/index.htmlLinux SocketCAN 文档https://docs.kernel.org/networking/can.html
返回列表