STM32 + Rust CAN 总线协议解析系统
机器人本体上所有「说话」的部件——电机、电池、编码器——都挂在 CAN 总线上,这个项目就是那条总线上的协议解析中枢:帧收发、滤波、设备管理、安全监控全部用 Rust 在 STM32F407 裸机上从零实现。
一、项目特性
| 特性 | 说明 |
|---|---|
| 无 std 环境 | 纯裸机开发,no_std,无动态内存分配 |
| 实时响应 | CAN 解析 + 响应 ≤10ms(目标),实测 ~2ms |
| 内存安全 | 充分利用 Rust 借用检查器,缓冲区/队列在编译期保证无越界 |
| 硬件容错 | 总线异常、设备离线、电磁干扰均有明确处理路径 |
| 功耗优化 | 待机 ≤50mA,运行 ≤200mA |
二、支持设备
电机(大疆系)
| 型号 | 厂商 | 控制帧 ID | 反馈帧 ID | 控制模式 | 控制范围 |
|---|---|---|---|---|---|
| M3508 | 大疆 | 0x200 / 0x1FF | 0x201-0x208 | 电流控制 | ±16384 |
| M2006 | 大疆 | 0x200 / 0x1FF | 0x201-0x208 | 电流控制 | ±10000 |
| GM6020 | 大疆 | 0x1FF / 0x2FF | 0x205-0x20B | 电压控制 | ±30000 |
大疆电机的协议特点:一条控制帧携带 4 个电机的控制量(8 字节按 ID 顺序排布),反馈帧每电机一条(转速、转子机械角度、实际电流、温度)。M3508 的电流控制范围 ±16384 对应满量程,直接映射到 C620 电调的期望电流。
其他设备
| 设备类型 | 型号 | CAN ID 范围 | 协议 |
|---|---|---|---|
| BMS | 通用 CAN BMS | 0x100-0x10F | 小端字节序 |
| 编码器 | 增量式编码器 | 0x300-0x30F | 小端字节序 |
三、技术栈
| 层 | 选型 |
|---|---|
| MCU | STM32F407VG(168MHz,1MB Flash,192KB RAM) |
| HAL | stm32f4xx-hal v0.21 |
| CAN 驱动 | bxcan v0.7 |
| 日志 | defmt + defmt-rtt |
| 调试烧录 | probe-rs |
四、系统架构与项目结构
rust-stm32-robot/
├── Cargo.toml # 项目配置
├── memory.x # 内存布局
├── Embed.toml # probe-rs 配置
├── src/
│ ├── lib.rs # 库入口
│ ├── main.rs # 主程序
│ ├── config.rs # 系统配置
│ ├── error.rs # 错误定义
│ ├── protocol.rs # 协议抽象
│ ├── safety.rs # 安全监控
│ ├── utils.rs # 工具函数
│ ├── can/
│ │ ├── mod.rs # CAN 模块
│ │ ├── driver.rs # CAN 驱动
│ │ ├── filter.rs # 滤波器配置
│ │ └── queue.rs # 帧队列
│ └── devices/
│ ├── mod.rs # 设备模块
│ ├── motor.rs # M3508 电机
│ ├── bms.rs # BMS 电池
│ ├── encoder.rs # 编码器
│ └── manager.rs # 设备管理器
└── docs/
├── REQUIREMENTS.md # 需求文档
├── ARCHITECTURE.md # 架构设计
├── TESTING.md # 测试指南
├── DEPLOYMENT.md # 部署指南
└── MAINTENANCE.md # 运维手册设计上的分层逻辑:protocol.rs 定义统一的帧抽象,can/ 负责「怎么收发」,devices/ 负责「怎么解释」——新增一种设备只需实现协议 trait,CAN 层与设备层互不感知。safety.rs 独立成模块是刻意的:安全逻辑(急停、失联保护)不和业务逻辑混在一起。
五、CAN 协议细节
波特率配置
| 波特率 | Prescaler | BS1 | BS2 | 采样点 |
|---|---|---|---|---|
| 1Mbps | 3 | 11 | 2 | 85.7% |
| 500kbps | 6 | 11 | 2 | 85.7% |
采样点统一在 85.7%(CANopen 推荐范围内偏后),保证总线长度和抗干扰余量。
帧 ID 分配
0x001 心跳帧
0x002 紧急停止
0x003 系统状态
0x100-0x10F BMS 帧
0x1FF 电机控制 (5-8)
0x200 电机控制 (1-4)
0x201-0x208 电机反馈
0x300-0x30F 编码器数据ID 段按设备类型划分,配合 bxCAN 硬件滤波器做硬过滤——不关心的帧根本不进软件层,CPU 只处理该处理的。
滤波器策略
利用 bxcan 的 14 组筛选器,把「心跳/急停(必须响应)」「电机反馈(高频)」「BMS/编码器(低频)」分成不同 FIFO 通道,优先级在硬件层就拉开,这是 2ms 响应的底层保障之一。
六、关键实现
Rust 裸机开发的三个核心收益
- 无动态内存:帧队列、设备表全部用固定容量集合(
heapless::Vec之类),启动时分配完毕,运行时零 malloc——没有内存碎片,就没有碎片导致的偶发宕机 - 借用检查器兜底:DMA 缓冲区、中断共享状态的所有权关系编译期强制理清,传统 C 里「中断改了一半主循环读了」这类 bug 在 Rust 里写不出来
- 错误处理强制化:
Result类型逼着每一个 CAN 操作都处理超时/错误分支,错误路径不是「忘了写」而是「必须写」
实时性设计
- 主循环周期 1ms,电机控制周期 10ms
- CAN 接收走中断 + 帧队列,主循环消费,避免在中断里做重活
- defmt-rtt 日志格式化在主机侧完成,MCU 只传压缩数据——日志不打断实时性
安全监控(safety.rs)
- 心跳帧 0x001 周期广播,设备失联按超时分级处理
- 紧急停止 0x002 最高优先级:任何状态下收到立即执行
- 总线错误计数(TEC/REC)监控,异常率超阈值自动重初始化总线
七、硬件连接
CAN 总线
STM32F407 CAN 收发器 (SN65HVD230)
PA11 (CAN_RX) -> RXD
PA12 (CAN_TX) -> TXD
3.3V -> VCC
GND -> GND
CANH ----[120Ω]---- CANH (设备)
CANL ----[120Ω]---- CANL (设备)两端 120Ω 终端电阻匹配总线阻抗,反射噪声压下去,长线通信才稳。
LED 指示
| LED | GPIO | 含义 |
|---|---|---|
| 绿色 | PD12 | 系统正常 |
| 橙色 | PD13 | 通信活动 |
| 红色 | PD14 | 错误状态 |
| 蓝色 | PD15 | 心跳指示 |
调试现场不接调试器也能一眼判断板子状态——嵌入式系统的可观测性不只有日志。
八、性能指标
| 指标 | 目标值 | 实测值 |
|---|---|---|
| CAN 响应延迟 | ≤10ms | ~2ms |
| 主循环周期 | 1ms | 1ms |
| 电机控制周期 | 10ms | 10ms |
| 待机功耗 | ≤50mA | TBD |
| 运行功耗 | ≤200mA | TBD |
九、快速开始
# 环境准备
rustup target add thumbv7em-none-eabihf
rustup component add llvm-tools
cargo install probe-rs --features cli
cargo install cargo-embed
# 编译
cargo build --release
# probe-rs 烧录并运行
cargo embed
# RTT 日志输出
probe-rs run --chip STM32F407VGTx target/thumbv7em-none-eabihf/release/rust-stm32-robot十、收获
这个项目让我把 Rust 的价值观在「最不容出错」的场景里验证了一遍:裸机、无 GC、无兜底的物理世界里,编译期保证的价值被放到最大。传统嵌入式一半的调试时间花在「内存相关玄学问题」上,Rust 把这一类问题整个从运行时挪到了编译时。同时大疆电机这类成熟工业协议的接入也让我体会到:协议解析系统拼的不是「能不能解析」,是滤波器怎么配、错误怎么恢复、安全怎么兜底的工程完整度。