roboto_usb2can 是 Atom01 机器人平台配套的单通道 CAN2.0 适配器固件,基于 STM32G431CBT6 微控制器与 Zephyr RTOS 构建,完整兼容开源 gs_usb 协议(candleLight 标准)。该固件的核心设计目标是在免驱即插即用的前提下,提供高可靠的 CAN 总线通信能力,并通过多层级状态监控与硬件保护机制,确保在复杂电磁环境下的鲁棒性。对于 Atom01 整机系统而言,此适配器承担着主控板(Orange Pi 5 Plus)与执行器 CAN 总线之间的协议转换与数据桥接职能,是后续 电机驱动与CAN总线通信 章节的物理层基础。
Sources: readme_cn.md
系统架构设计
固件采用分层架构,自下而上依次为硬件抽象层(Zephyr 设备树与驱动)、协议适配层(gs_usb 与 USB 设备栈)以及应用层(错误监控与状态指示)。这种分层使得协议逻辑与硬件实现解耦,便于向其他 STM32 系列 MCU 迁移。
graph TB
subgraph HW["硬件层 (STM32G431)"]
USB["USB FS (PA11/PA12)"]
CAN["FDCAN1 (PB8/PB9)"]
PHY["ISO1050 收发器 (PC13/PC14)"]
LED["状态 LEDs (PA15/PA0/PA7)"]
TIM["TIM2 时间戳计数器"]
end
subgraph Zephyr["Zephyr RTOS 抽象层"]
UDC["UDC 驱动"]
CAN_DRV["CAN 子系统"]
GPIO_DRV["GPIO 子系统"]
CNT["Counter API"]
end
subgraph Protocol["协议适配层"]
USBD["新 USB 设备栈 (usbd)"]
GS_USB["gs_usb Class 驱动"]
MSOS2["MSOS 2.0 描述符"]
end
subgraph App["应用层"]
MAIN["main.c 生命周期管理"]
ERR["CAN 错误监控"]
LED_SW["LED 状态机"]
end
USB --> UDC --> USBD --> GS_USB --> MAIN
CAN --> CAN_DRV --> GS_USB
PHY --> GPIO_DRV
LED --> GPIO_DRV --> LED_SW
TIM --> CNT --> GS_USB
GS_USB -.->|事件回调| LED_SW
CAN_DRV -.->|状态变更| ERR
ERR -.->|状态反馈| LED_SW
整个项目源码仅包含三个核心文件(main.c、led.c、roboto_usb2can.h),在功能完整性与代码精简之间取得了良好平衡。版本管理通过 CMakeLists.txt 中的宏定义与 version.h.in 模板联动,编译时自动生成带有 BCD 编码的版本号,供 USB 设备描述符使用。
Sources: main.c, CMakeLists.txt, roboto_usb2can.h
硬件抽象与设备树配置
硬件抽象完全依赖 Zephyr 的设备树(DeviceTree)机制,所有外设引脚与时钟配置集中在 roboto_usb2can.dts 中声明,无需在 C 代码中硬编码寄存器地址。这种声明式配置不仅提升了可移植性,也使得硬件变更可以通过修改设备树而非重编译业务逻辑来实现。
系统时钟采用 HSI 内部高速时钟经 PLL 倍频至 160 MHz,分频策略保持 AHB/APB1/APB2 均为 1:1,确保 CAN 与 USB 外设获得最高总线带宽。USB 时钟源独立挂载到 HSI48(48 MHz),以满足 Full Speed 物理层的精确时序要求。FDCAN1 的位时钟则来源于 PLL_Q(160 MHz / 4 = 40 MHz),为后续 1 Mbps 的位定时配置提供充足的时钟粒度。
CAN 物理层选用 TI ISO1050 隔离收发器,设备树中定义了 can-phy0 与 can-phy1 两个节点,当前固件仅启用单通道,但预留了双通道的硬件扩展接口。收发器使能引脚为 GPIO_ACTIVE_LOW,默认上电使能。LED 方面,通过 aliases 将 led0、led1、led2 分别映射到蓝、绿、黄三色灯,后续 C 代码中使用 GPIO_DT_SPEC_GET(DT_ALIAS(ledX), gpios) 即可获取引脚描述符,实现编译期硬件绑定。
时间戳功能依赖 TIM2 计数器,预分频器设为 159(160 MHz / 160 = 1 MHz),为 gs_usb 协议提供微秒级精度的帧时间戳,满足总线分析场景下的时序测量需求。
Sources: roboto_usb2can.dts, roboto_usb2can.dts
USB 协议栈与 WinUSB 免驱机制
固件同时兼容 Zephyr 的新旧两代 USB 设备栈,但通过 prj.conf 显式选择了 CONFIG_USB_DEVICE_STACK_NEXT,即新栈(USBD)。新栈提供了更模块化的描述符注册机制,使得 MSOS 2.0 平台描述符的注入更为优雅。
Windows 免驱的核心在于 Microsoft OS 2.0 描述符。当设备插入 Windows 8.1+ 系统时,主机首先读取标准设备描述符;若检测到 bcdUSB 为 0x0201,便会进一步查询 BOS(Binary Object Store)描述符。固件在 roboto_usb2can.h 中完整定义了 BOS 的 LPM 扩展能力与 MSOSv2 平台能力描述符,其中平台能力 UUID 固定为 DF60DDD8-8945-C74C-9CD2-659D9E648A9F。BOS 中的 bMS_VendorCode 设为 0x01,当主机发送 bRequest=0x01 且 wIndex=0x07(MS_OS_20_DESCRIPTOR_INDEX)的厂商请求时,msos_vendor_handler 回调将返回包含兼容 ID(WINUSB)与设备接口 GUID 的描述符集。该 GUID 采用 candleLight 的公共值 {c6e515a2-8dc6-4fc4-a03c-9325555d68e6},确保与 Linux 的 gs_usb 驱动以及广泛使用的 CAN 分析软件生态兼容。
sequenceDiagram
participant Host as Windows Host
participant Dev as USB Device
Host->>Dev: GET_DESCRIPTOR (Device)
Dev-->>Host: bcdUSB = 0x0201
Host->>Dev: GET_DESCRIPTOR (BOS)
Dev-->>Host: BOS with MSOSv2 Platform Cap
Host->>Dev: Vendor Request 0x01, wIndex=7
Dev-->>Host: MSOS2 Descriptor Set
Note over Dev: Contains CompatibleID=WINUSB<br/>and DeviceInterfaceGUID
Host->>Dev: WinUSB Driver Bind
在 main.c 中,新栈的初始化遵循严格的注册顺序:语言描述符 → 厂商/产品/序列号字符串 → 全速配置 → gs_usb 类实例 → BOS 描述符。最后通过 usbd_init() 与 usbd_enable() 完成枚举。任何步骤的失败都会导致 main() 提前返回,防止设备以不完整状态接入主机。
Sources: roboto_usb2can.h, main.c
GS-USB 协议集成与通道管理
gs_usb 是 candleLight 项目定义的开源 USB-CAN 协议,已被 Linux 内核原生支持(gs_usb 驱动)。固件通过引入 CANnectivity 项目作为 Zephyr 外部模块,获得经过充分测试的 gs_usb 协议栈实现,而非自行从头实现协议解析状态机。
在 main() 中,应用层首先通过 DEVICE_DT_GET(DT_NODELABEL(gs_usb0)) 获取 gs_usb 虚拟设备句柄,再通过 DEVICE_DT_GET(DT_NODELABEL(fdcan1)) 获取物理 CAN 控制器句柄。二者通过 gs_usb_register() 完成绑定:gs_usb 负责解析主机下发的控制传输(位定时配置、通道启停、滤波器设置),并将数据帧通过 Zephyr 的 CAN 子系统 API 下发到 FDCAN1;反之,CAN 子系统收到的总线帧也会经 gs_usb 打包为 USB Bulk IN 报文回传主机。gs_usb_ops 结构体中注册的 .event 回调指向 status_led_event(),使得协议栈的通道启停与收发事件能够实时驱动 LED 状态变化。
协议栈的关键运行参数在 prj.conf 中配置。当前固件为单通道设计,因此 CONFIG_USBD_GS_USB_MAX_CHANNELS=1。为了在高负载下维持低延迟,RX 与 TX 线程的优先级均设为 -1(Zephyr 协作线程中的较高优先级),线程栈大小各为 2048 字节,中间件池大小为 64 个报文对象。CONFIG_USBD_GS_USB_COMPATIBILITY_MODE=y 确保了与旧版 candleLight 上位机的最大程度兼容。
CAN 总线状态监控与容错保护
在机器人等强电磁干扰环境中,CAN 总线可能出现瞬态故障甚至错误帧洪水。固件在应用层实现了独立的 CAN 错误监控器(CAN Error Monitor),通过 can_set_state_change_callback() 注册到 Zephyr CAN 子系统,实时跟踪总线状态迁移。
监控器为每个通道维护一个 can_error_monitor 结构体,包含以下关键机制:
| 机制 | 阈值 | 动作 | 设计意图 |
|---|---|---|---|
| 错误帧洪水检测 | 1 秒内超过 50 个错误帧 | 触发限流,强制关闭 CAN 总线 | 防止故障节点持续占用总线带宽 |
| ERROR_PASSIVE 累积 | 连续进入 ERROR_PASSIVE 超过 10 次 | 强制 Bus-Off | 处理持续性电气故障 |
| 状态恢复 | TEC/REC 回落至 ERROR_ACTIVE | 自动清除错误计数,恢复正常状态 | 允许瞬态干扰自恢复 |
当检测到错误帧洪水时,监控器首先通过 can_stop() 主动断开节点,保护总线上其他正常通信的单元;同时设置 mon->forced_busoff = true 防止重复触发,并通过 LED 系统立即向操作者反馈故障状态。若总线后续恢复正常(CAN_STATE_ERROR_ACTIVE),监控器会自动重置所有错误计数与限流标志,无需人工干预即可重新投入通信。
值得注意的是,状态回调中并未实现自动重启动逻辑——Bus-Off 后的恢复策略交由主机端(Linux 的 restart-ms 参数或 Windows 上位机)决策。这种设计遵循了"适配器忠实转发、策略由主机制定"的分层原则。
Sources: main.c, roboto_usb2can.h
LED 状态指示系统
固件板载三颗 LED,分别对应 USB 状态(蓝)、CAN 状态(黄)与数据活动(绿)。LED 控制并非简单的 GPIO 翻转,而是基于 Zephyr 延迟工作队列(k_work_delayable) 实现的非阻塞状态机,确保 LED 闪烁不会占用中断上下文或影响 CAN/USB 的实时性。
蓝灯与黄灯采用模式切换方式工作:应用层调用 status_led_usb_set() 或 status_led_can_set() 时,先取消当前正在执行的延迟工作,再根据新状态从 usb_led_patterns 或 can_led_patterns 数组中取出对应的亮/灭时长与重复次数,重新调度工作项。这种设计使得状态转换的响应是瞬时的,无需等待当前闪烁周期结束。各状态的闪烁语义如下:
| LED | 状态 | 闪烁模式 | 含义 |
|---|---|---|---|
| 蓝灯 | Ready | 0.5s 亮 / 0.5s 灭 | USB 枚举成功,等待主机指令 |
| 蓝灯 | Error | 0.1s 亮 / 0.1s 灭 | USB 通信异常 |
| 黄灯 | Off | 0.05s 亮 / 3.95s 灭 | CAN 通道关闭或 Bus-Off |
| 黄灯 | Active | 0.5s 亮 / 0.5s 灭 | CAN 通道正常运行 |
| 黄灯 | Warning | 0.2s 亮 / 1.8s 灭 | CAN 警告态(ERROR_WARNING) |
| 黄灯 | Error | 0.1s 亮 / 0.1s 灭 | CAN 错误态或错误帧洪水 |
绿灯(活动指示)的实现更为精细。由于 CAN 帧可能以极高频率收发,直接逐帧翻转 GPIO 会导致 LED 常亮且徒增 CPU 负载。固件采用 低通滤波 + 定时器节拍 方案:gs_usb 事件回调中收到 GS_USB_EVENT_CHANNEL_ACTIVITY_RX 或 TX 时,先检查 last_activity_time 是否已过期(防抖动窗口为 100 ms),若允许通过则设置 activity_ticks = 2;一个 50 ms 周期的内核定时器每滴答一次递减计数器,并在 ticks == 1 时点亮 LED,在 ticks == 0 时熄灭。这样无论总线帧率多高,绿灯始终以最高 10 Hz 的频率闪烁,清晰指示数据流动而不过载。
此外,LED 事件回调还包含针对 Linux 内核 5.x/6.1 已知缺陷的补偿逻辑:某些内核版本会在通道正常工作时偶发虚假的 GS_USB_EVENT_CHANNEL_STOPPED 事件。固件通过 last_stopped_time 时间戳进行 1 秒窗口的去抖过滤,避免黄灯因此类假事件而频繁跳变。
Sources: led.c, led.c, roboto_usb2can.h
内存与性能优化
作为运行在 128 KB Flash / 144 KB SRAM 微控制器上的固件,资源优化是开发的关键考量。prj.conf 中体现了一系列面向嵌入式场景的剪裁策略:
USB 与协议栈层面,UDC_BUF_COUNT 设为 48,配合 4096 字节的池大小,确保在 1 Mbps 高负载下仍有充足的 DMA 缓冲区;USBD_GS_USB_POOL_SIZE 设为 64,覆盖单通道并发收发需求。线程栈方面,主线程、系统工作队列、gs_usb 的 RX/TX 线程均分配 2048 字节,中断栈 1536 字节,整体堆内存池 2048 字节,满足运行时的动态分配需求。
为了降低固件体积与运行时开销,几乎所有非必要功能均被关闭:CONFIG_SERIAL=n、CONFIG_CONSOLE=n、CONFIG_LOG=n 彻底移除了 UART 串口与日志子系统;CONFIG_ASSERT=n、CONFIG_THREAD_NAME=n、CONFIG_THREAD_MONITOR=n 关闭了调试基础设施;CONFIG_SIZE_OPTIMIZATIONS=y 启用编译器 -Os 优化。在此配置下,Release 模式编译的二进制文件体积控制在数十 KB 级别,为后续 OTA 或现场升级预留了充足空间。
Sources: prj.conf
构建系统与开发流程
项目采用 West + CMake 的标准 Zephyr 构建体系。west.yml 声明了两个外部依赖:Zephyr 主仓库(main 分支)与 CANnectivity 模块(main 分支,映射到 custom/cannectivity 路径)。开发者只需执行 west update 即可自动拉取全部依赖源码。
本地编译命令极为简洁:
west build -b roboto_usb2can
CMakeLists.txt 中除了常规的目标源文件注册外,还实现了 Release 模式的自动化产物管理:通过 string(TIMESTAMP ...) 捕获 UTC 构建日期,结合主/次/修订版本号,生成形如 roboto_usb2can_v3.0.0_20250101.bin 的带版本二进制文件。该产物规则通过自定义目标 release_files 暴露,CI 流水线或开发者可在编译后执行 cmake --build . --target release_files 直接获得可用于发布的固件。
烧录支持五种主流调试器,配置集中声明于 board.cmake:
| 调试器 | 命令 | 推荐场景 |
|---|---|---|
| STM32CubeProgrammer | west flash --runner stm32cubeprogrammer |
STLINK-V3MINIE(推荐) |
| Probe-rs | west flash --runner probe-rs |
开源调试生态 |
| J-Link | west flash --runner jlink |
商业调试器 |
| PyOCD | west flash --runner pyocd |
CMSIS-DAP 适配器 |
| OpenOCD | west flash --runner openocd |
社区通用方案 |
GitHub Actions 工作流 zephyr-build.yml 完整封装了从环境初始化、SDK 安装、缓存加速到产物上传与自动 Release 的全流程,确保每次代码推送都能获得可验证的构建产物。
Sources: CMakeLists.txt, board.cmake, zephyr-build.yml
配套工具链
固件仓库内提供了一站式工具脚本,覆盖开发验证与生产测试全周期。
Linux 端的 test_roboto_usb2can.sh 是一个多节点压力测试脚本,专为 USB Hub 扩展多路 CAN 的场景设计。它会自动配置 can0~can3 接口(若存在),设置 1 Mbps 波特率、启用 100 ms 自动 Bus-Off 恢复、并将 MTU 强制设为 16 字节以优化 USB 传输效率。随后使用 cangen 以随机 ID 和固定间隔生成总线负载,同时每秒刷新一次基于内核 sysfs 统计的实时仪表盘,展示各接口的 TX/RX 速率、错误计数器、总线状态及硬件 TEC/REC 值。
跨平台 Python 上位机 roboto_usb2can_tool.py 基于 pyusb 与 tkinter 构建,支持 Windows 免驱与 Linux libusb 两种后端。工具内部完整实现了 gs_usb 控制传输协议(HOST_FORMAT、BITTIMING、MODE 等请求),并采用独立后台线程进行 USB Bulk IN 轮询。GUI 提供多设备同时管理、统一波特率设置、单播/广播发送、周期发送、ID 过滤与实时日志功能,是固件功能验证与产线测试的便捷手段。
Sources: test_roboto_usb2can.sh, roboto_usb2can_tool.py
阅读衔接与后续步骤
完成 USB2CAN 适配器固件的理解后,建议按以下顺序深入 Atom01 的嵌入式与部署体系:
- 若需构建主控板操作系统镜像,请继续阅读 主控板镜像构建与烧录,了解 Orange Pi 5 Plus 的 Armbian 内核编译与设备树适配。
- 若关注整机 CAN 总线通信的软件实现,请前往 电机驱动与CAN总线通信,其中将涉及 SocketCAN 接口的初始化、CAN 帧封装以及执行器控制协议。