3、CAN总线基础:CAN协议简介、CAN报文结构、CAN网络在汽车诊断中的应用

各位同学,咱们今天聊聊CAN总线。说实话,搞Bootloader开发,CAN总线是绕不过去的坎。你想想看,ECU刷写、诊断通信、网络管理,哪样离得开CAN?我当年刚入行时,总觉得CAN协议太底层,先啃协议栈去了。结果第一次调试Bootloader,报文死活发不出去,折腾了两天才发现是CAN控制器初始化时波特率配错了。嗯,从那以后我学乖了——先搞懂CAN,再谈别的。

3.1 CAN协议简介:为什么是它?

CAN(Controller Area Network)是德国Bosch公司在80年代搞出来的。初衷很简单——让车上的各种ECU能可靠地共享数据。为什么汽车行业几乎都选它?说白了,就三个字:稳、快、省。

  • 稳:差分信号传输,抗干扰能力强。车上的电磁环境有多恶劣,做过实车测试的都懂。
  • 快:经典CAN最高1Mbps,CAN FD能到8Mbps以上。刷写Bootloader时,速度就是生产力。
  • 省:多主总线结构,任意节点都能发消息,不需要主从仲裁。线束少,成本低。

核心要点:CAN总线是事件触发的广播式通信。任何节点检测到总线空闲,都可以开始发送。如果两个节点同时发,靠标识符(ID)仲裁——ID越小,优先级越高。

我记得有一次做台架测试,一个ECU的CAN收发器坏了,不停地发错误帧,把整个网络都拉垮了。CAN协议的错误检测和自动重发机制,在这种场景下就显得特别重要。所以,搞Bootloader,你得先敬畏CAN的物理层和协议层。

3.2 CAN报文结构:数据在线上怎么跑?

CAN报文的结构,我建议你把它记牢。因为Bootloader的刷写流程,本质上就是按照特定格式往总线上塞报文。报文分两种格式:标准帧(11位ID)和扩展帧(29位ID)。汽车诊断和Bootloader里,两种都会用到。

咱们先看标准帧的结构:

字段 位数 说明
SOF 1 帧起始,同步信号
标识符(ID) 11 决定优先级,ID越小优先级越高
RTR 1 远程帧标志,0=数据帧,1=远程帧
IDE 1 扩展标志,0=标准帧
r0 1 保留位
DLC 4 数据长度,0~8字节
数据段 0~64 实际数据,CAN FD可到64字节
CRC 15 循环冗余校验
ACK 2 应答位
EOF 7 帧结束

我的习惯:在Bootloader开发中,我一般把CAN ID按功能分区。比如0x700~0x7FF留给诊断请求,0x600~0x6FF留给诊断响应。这样调试时看报文,一眼就能知道是谁在说话。

扩展帧的结构类似,只是ID变成了29位,IDE位为1。在UDS诊断协议里,扩展帧用得比较多,因为29位ID可以编码更多的地址信息。

咱们看一个实际的CAN报文例子。假设我们要发送一个诊断请求,读取ECU的软件版本号:

// 标准CAN数据帧示例
// ID: 0x7DF (诊断请求的广播ID)
// DLC: 8 (数据长度8字节)
// Data: 02 10 01 00 00 00 00 00
// 解释: 02表示后续数据长度2字节
//       10是诊断会话控制服务ID
//       01表示请求切换到编程会话

uint32_t can_id = 0x7DF;
uint8_t data[8] = {0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t dlc = 8;

你可能会问:为什么DLC是8,但有效数据只有2字节?这是CAN协议的规定——DLC表示数据段长度,但实际有效数据可以少于DLC。剩下的字节填充0就行。不过我个人建议,在Bootloader通信中,尽量把DLC设成实际数据长度,别浪费带宽。

3.3 CAN网络在汽车诊断中的应用

汽车诊断,说白了就是通过CAN总线跟ECU对话。你问它答,或者你让它干活。在Bootloader场景下,诊断协议主要用的是UDS(ISO 14229)和底层传输协议ISO 15765-2(也叫CAN-TP)。

为什么需要CAN-TP?因为CAN报文一次最多传8字节(经典CAN),但诊断数据可能很长,比如刷写一个256KB的应用程序。这时候就需要把大数据包拆成多个CAN帧,接收端再拼回去。CAN-TP干的就是这个活。

咱们画个图,把诊断通信的层次关系理清楚:

汽车诊断通信协议栈 应用层 UDS诊断服务 (ISO 14229) 传输层 CAN-TP (ISO 15765-2) 数据链路层 CAN控制器 (CAN 2.0B / CAN FD) 物理层 CAN收发器 + 差分总线 Bootloader刷写时,应用层发UDS请求,传输层分包,数据链路层组帧,物理层发差分信号 每一层都可能有坑,调试时建议逐层排查

这张图我建议你存下来。每次调试Bootloader通信问题时,就对照着看——问题出在哪一层?是物理层的收发器坏了?还是传输层的分包逻辑写错了?

3.4 诊断通信的核心流程

在Bootloader开发中,诊断通信的典型流程是这样的:

  1. 诊断请求:上位机(比如诊断仪)发送UDS请求,目标ECU的CAN ID。
  2. CAN-TP分包:如果请求数据超过8字节,传输层拆成多个CAN帧。第一帧(FF)告诉对方总长度,后续帧(CF)发数据。
  3. ECU接收:目标ECU收到所有帧后,重组数据,解析UDS服务。
  4. 诊断响应:ECU处理完请求,发送响应报文。同样,如果响应太长,也要分包。
  5. 流控制:接收方可以通过流控制帧(FC)告诉发送方“慢点发”或“继续发”。

曾经踩过的坑:有一次我写Bootloader的CAN-TP接收逻辑,没处理好流控制帧的超时。结果ECU发响应时,上位机一直没给FC帧,ECU就卡在发送状态,整个网络都堵了。后来我加了个超时机制——如果100ms内没收到FC,就主动取消发送,释放总线。

3.5 常用的诊断服务(Bootloader相关)

在刷写Bootloader时,下面这几个UDS服务你一定会用到:

服务ID 服务名称 用途
0x10 诊断会话控制 切换到编程会话(0x02)
0x27 安全访问 解锁ECU,防止非法刷写
0x31 例程控制 执行擦除、检查等例程
0x34 请求下载 告诉ECU我要开始刷数据了
0x36 传输数据 实际的数据传输
0x37 请求传输退出 结束数据传输
0x11 ECU复位 刷完后重启ECU

举个例子,刷写Bootloader的典型流程:

// 1. 切换到编程会话
发送: 02 10 02 00 00 00 00 00  (ID: 0x7DF)
接收: 06 50 02 00 32 01 F4 00  (ID: 0x7E8)
// 50 02 表示成功,后面是支持的定时参数

// 2. 安全访问(解锁)
发送: 02 27 01 00 00 00 00 00  (请求种子)
接收: 04 67 01 12 34 00 00 00  (种子: 0x1234)
发送: 04 27 02 56 78 00 00 00  (发送密钥)
接收: 02 67 02 00 00 00 00 00  (解锁成功)

// 3. 请求下载
发送: 05 34 00 44 00 10 00 00  (地址长度4字节,数据长度0x1000)
接收: 04 74 20 00 10 00 00 00  (同意下载,最大块大小0x1000)

// 4. 传输数据(循环发送)
发送: 10 0C 36 01 00 00 00 00  (第一帧,总长度12字节)
发送: 21 A5 A5 A5 A5 A5 A5 A5  (后续帧)
发送: 22 A5 A5 A5 A5 00 00 00  (最后一帧)
接收: 06 76 01 00 00 00 00 00  (响应:数据块1接收成功)

// 5. 请求传输退出
发送: 01 37 00 00 00 00 00 00
接收: 02 77 00 00 00 00 00 00

// 6. ECU复位
发送: 01 11 01 00 00 00 00 00  (硬件复位)
接收: 02 51 01 00 00 00 00 00

个人经验:写Bootloader时,我习惯在每步之间加一个短延时(比如5ms)。别小看这个延时,它能避免ECU处理不过来导致丢帧。另外,安全访问的种子-密钥算法,不同OEM差别很大,有的用AES,有的用自定义算法。这部分代码建议单独封装,方便移植。

3.6 小结

CAN总线是Bootloader的通信基石。你得搞懂报文结构、诊断协议栈、CAN-TP分包机制。调试时,一个CAN分析仪是必备的——我见过太多人靠猜来调通信问题,结果越调越乱。

嗯,这一章的内容就到这儿。记住:CAN报文是Bootloader的“语言”,UDS是“语法”,两者结合,你才能跟ECU顺畅对话。