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干的就是这个活。
咱们画个图,把诊断通信的层次关系理清楚:
这张图我建议你存下来。每次调试Bootloader通信问题时,就对照着看——问题出在哪一层?是物理层的收发器坏了?还是传输层的分包逻辑写错了?
3.4 诊断通信的核心流程
在Bootloader开发中,诊断通信的典型流程是这样的:
- 诊断请求:上位机(比如诊断仪)发送UDS请求,目标ECU的CAN ID。
- CAN-TP分包:如果请求数据超过8字节,传输层拆成多个CAN帧。第一帧(FF)告诉对方总长度,后续帧(CF)发数据。
- ECU接收:目标ECU收到所有帧后,重组数据,解析UDS服务。
- 诊断响应:ECU处理完请求,发送响应报文。同样,如果响应太长,也要分包。
- 流控制:接收方可以通过流控制帧(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顺畅对话。