CAN 在汽车和机器人领域统治了三十年,靠的不是”快”,而是多主、非破坏性仲裁、强抗干扰、自愈错误处理这四个特性。本文从物理层讲起,把”为什么”讲透:差分信号为什么抗干扰 → 仲裁为什么是非破坏的 → 错误状态机怎么自愈 → SocketCAN 生产级代码 → RoboMaster M3508/C620 协议逐字节解析 → 1kHz 全车控制的带宽预算。读完你能自己算总线负载、自己写驱动、自己排故障。
物理层:差分信号与位时序
为什么差分信号抗干扰
CAN 用两根线 CAN_H / CAN_L 传输差分信号:
- 显性位(dominant,逻辑 0):CAN_H ≈ 3.5V,CAN_L ≈ 1.5V,差分 ≈ 2V
- 隐性位(recessive,逻辑 1):两线都 ≈ 2.5V,差分 ≈ 0V
- 显性覆盖隐性——这是仲裁机制的物理基础
接收端只关心 $V_{diff} = V_{CAN_H} - V_{CAN_L}$。外部电磁干扰(电机大电流、功率线)通常以共模方式同时叠加到两根线上,差分相减后抵消。这就是 CAN 敢跟功率线一起走线、在电机旁边跑 1Mbps 的原因。代价:双绞线(绞距越小抗干扰越强)+ 屏蔽。
波特率与总线长度:物理极限
| 波特率 | 最大总线长度(ISO 11898 经验值) |
|---|---|
| 1 Mbps | 40 m |
| 500 kbps | 100 m |
| 250 kbps | 250 m |
| 125 kbps | 500 m |
为什么波特率越高线越短:位时间变短后,信号在线上往返的传播延迟(约 5ns/m)占位时间的比例变大,采样点采到的电平可能是”上一位的余波”。1Mbps 时位时间 1µs,40m 线往返延迟 ~400ns,占 40%——已经是极限。
位时序与采样点
一个位时间由 4 段组成:SYNC_SEG(1 tq)+ PROP_SEG + PHASE_SEG1 + PHASE_SEG2,采样点在 PHASE_SEG1/PHASE_SEG2 交界处,**典型 75%~87.5%**:
1 | |<-SYNC->|<-PROP->|<-SEG1->|<-SEG2->| |
工程含义:同一总线上所有节点的波特率必须一致,采样点最好也一致(不同控制器默认采样点可能差 5%,长总线+高温下就是偶发丢帧的根源)。STM32 配置:CAN_BS1=8, CAN_BS2=1, Prescaler=... 时采样点 = (1+8)/(1+8+1) = 90%;要压到 80% 就 BS1=7, BS2=2(采样点 (1+7)/10 = 80%)。
终端电阻:为什么是 120Ω×2
双绞线特征阻抗约 120Ω。总线两端各接一个 120Ω 电阻(中间节点不接),是为了阻抗匹配:信号到线端不反射。缺终端电阻的表现:短总线、低速时”好像能跑”,一拉长或提速就偶发丢帧/错误帧风暴。判断方法:总线断电时用万用表量 CAN_H-CAN_L,应该读到 ≈ 60Ω(两个 120Ω 并联);读到 120Ω = 只有一端有;读到无穷 = 两端都没有。
帧格式与仲裁
标准数据帧逐段拆解
1 | SOF(1) | 仲裁场(12) | 控制场(6) | 数据场(0~64) | CRC(16) | ACK(2) | EOF(7) | IFS(3) |
- 仲裁场:11 位 ID + RTR(远程帧请求位)
- 控制场:IDE(1=扩展帧)+ r0 + DLC(4 位数据长度)
- 数据场:0~8 字节。CAN 一帧最多 8 字节——这是 1991 年定的,够传状态但传不了大数据,所以点云/图像永远不走 CAN
- CRC:15 位 CRC + 1 位定界符
- 位填充:从 SOF 到 CRC 前,连续 5 个相同位后强制插入 1 个反相位——保证有足够跳变沿供接收方锁相环同步
标准帧(11 位 ID)8 字节数据的基本帧长:1+12+6+64+16+2+7+3 = 111 bit,加位填充后最多 ~131 bit。这个数后面算带宽要用。
非破坏性仲裁:为什么 ID 越小优先级越高
多主模式下多个节点同时发帧怎么办?CAN 的答案是边发边听(发送节点同时监听总线):
- 每个发送节点从 SOF 开始逐位发送自己的 ID
- 发送隐性位(1)时如果监听到总线是显性(0)→ 说明有更高优先级节点在发 → 立即退出发送转为接收,不产生任何冲突破坏
- 因为显性覆盖隐性,ID 数值越小,前面的 0 越多,越晚被”挤掉” → 优先级越高
这就是”非破坏性”:仲裁输了的节点自动等下一轮,总线上的有效帧一个都不丢。注意仲裁只发生在仲裁场(ID+RTR),进入数据场后没有仲裁。工程结论:把关键帧(控制帧、急停帧)的 ID 配小,状态帧配大;ID 规划要留余量,别把 0x200~0x210 全占了不留扩展位。
帧类型速查
| 类型 | 用途 | 关键点 |
|---|---|---|
| 数据帧 | 传数据 | 标准帧 11 位 ID / 扩展帧 29 位 ID |
| 远程帧 | 请求对方发数据 | RTR=1;同 ID 远程帧优先级低于数据帧(数据帧 RTR=0 显性) |
| 错误帧 | 报错 | 6 个显性位,由检测到错误的节点发出 |
| 过载帧 | 请求延迟 | 接收方忙时发 |
错误处理:总线为什么”死不了”
CAN 的错误检测有五重:位错误、填充错误、CRC 错误、形式错误、ACK 错误。检测到错误的节点发错误帧(6 个显性位)打断当前帧,让所有节点丢弃这帧数据,然后重传。不丢帧是不可能的,但绝不会把坏帧当好的用——这是 CAN 和串口最本质的区别。
每个节点维护两个计数器 TEC(发送错误)/ REC(接收错误),状态机三级:
1 | 错误主动 (TEC<128, REC<128) ──TEC>127──> 错误被动 (TEC>127) |
工程含义:某个节点硬件故障(比如线缆短路)会不断发错误帧拖垮整条总线,但 CAN 的自愈机制保证其他节点不会死,只是通信质量下降。排查时看 ip -s link show can0 的错误计数:TEC 持续上涨 = 本节点发送有问题;REC 上涨 = 接收有问题(多半是对方/线路)。
SocketCAN 编程实战
生产级收发:过滤 + 超时 + 错误帧
1 |
|
发送端的两个坑
write不保证发送成功:CAN 发送缓冲区满(总线忙)时write返回 -1 且 errno=ENOBUFS。必须检查返回值,失败要重试/计数,不能静默丢弃——控制帧丢了电机就停- 发送频率要限速:没有流控,你 10kHz 狂发,总线 1Mbps 顶不住,错误帧风暴把全车干趴
1 | // 限速发送:控制帧严格 1kHz |
RoboMaster 电机协议解析(逐字节)
C620 电调:0x200 控制帧
一帧控制 4 个电机,8 字节 = 4 个 int16 电流指令(大端,高字节在前):
1 | // 0x200: [M1电流][M2电流][M3电流][M4电流],int16 大端 |
C620 反馈帧:0x201 + 电机 ID(0x201~0x204)
1 | // 反馈帧解析:机械角度(13bit)/转速(rpm)/实际电流/温度 |
三个实战要点:
- 机械角度 0~8191 对应转子一圈,断电即丢(增量式)。位置控制必须上电回中,且关节角要除减速比:M3508 减速比 3591:187 ≈ 19.2:1
- rpm 是转子转速:反馈转速 = 转子转速,换算成输出轴要除 19.2;做速度环时搞混转子/输出轴,Kp 直接差 19 倍,表现就是”参数怎么调都怪”
- 温度字节要监控:M3508 连续大电流跑几分钟温度就上 80°C,比赛打一半电机过热保护 = 白给
GM6020 云台电机:电压控制
GM6020 内置驱动器,控制量是电压不是电流:0x1FF 控制帧一帧管 3 个电机,每个 int16(30000 ≈ 30V);反馈帧 0x20A(电机 13)/ 0x20B(电机 46),同样含角度/转速/电流/温度。
坑:GM6020 是电压环,位置环要自己包——云台”点头”(pitch 往下掉)通常不是参数问题,是重力矩没有前馈补偿:u = PID(e) + m·g·L·cos(θ) / K_t,电压里必须叠加重力前馈项,否则 Kp 再大也顶不住静态下垂。
总线负载与带宽预算:为什么全车 CAN 要精打细算
算一算:1kHz 下 8 电机还能不能跑
标准帧 8 字节 ≈ 111 bit(不含填充)~ 131 bit(含填充,取 125 保守平均)。RoboMaster 步兵典型配置:底盘 4×M3508 + 云台 2×GM6020:
- 控制帧:0x200(4 电机 1 帧)+ 0x1FF(3 电机 1 帧,GM6020 只需 1 帧)= 2 帧/ms
- 反馈帧:6 个电机各 1 帧 = 6 帧/ms
- 合计 8 帧/ms × 125 bit ≈ 1000 bit/ms = 1Mbps 满载 100%
结论:8 电机全 1kHz 反馈,1Mbps 总线已经贴满。实战建议:
- 负载率控制在 80% 以下(留余量给错误重传、远程帧、调试帧)
- 方案 A:反馈降频(500Hz 反馈 + 1kHz 控制,靠插值)
- 方案 B:分两路 CAN(底盘一路、云台一路)——RoboMaster 强队标准做法
- 方案 C:控制帧合并(0x200 一帧 4 电机已经是合并的)
这个预算意识很重要:协议设计阶段就要算负载,不然装机后才发现带宽不够,只能推倒重来。
踩坑清单(实战汇总)
- 波特率不一致:双方都”配置成功”,但互相收不到,且没有任何报错——上电先
candump验证 - 终端电阻缺失:短距离能用,拉长/提速就偶发丢帧;断电量 CAN_H-CAN_L 应为 ~60Ω
- CAN_H/CAN_L 接反:完全不通,但万用表量不出问题;线序在两端都要核对(尤其是自制线)
- 接口没 up:
candump can0报 “No such device” / 收不到数据,先ip link set can0 up type can bitrate 1000000 - 错误帧风暴:
ip -s link show can0看错误计数,找 TEC/REC 上涨的节点;bus-off 后需 128 次总线空闲才恢复,表现为”莫名断连几分钟又自己好” - write 返回值不检查:ENOBUFS 静默丢控制帧 → 电机偶发停转,排查半天
- 转子/输出轴转速混淆:忘除减速比 19.2,速度环参数全部失真
- GM6020 无重力前馈:pitch 静态下垂,加
m·g·L·cos(θ)/K_t前馈 - ID 规划不留余量:全车 ID 挤在一起,后期加设备只能重配;关键帧(急停)ID 必须最小
- 供电与信号共地:电调/电机功率地与控制器地不共,干扰直接进总线;共地 + 双绞 + 屏蔽层单端接地
速查:物理层三件套(波特率一致、双终端电阻、线序正确)→ 数据链路看错误计数 → 应用层看 ID/协议/负载率。CAN 的问题 90% 出在物理层,先量电阻、再看计数、最后才怀疑代码。