4、CAN总线通信基础:CAN帧结构、CAN标识符分配、多帧传输机制
各位同学,咱们今天聊CAN总线。说实话,做Bootloader开发,CAN通信是绕不过去的坎。你想想看,ECU刷写时数据怎么传?靠的就是CAN总线。我见过不少新手,上来就写刷写逻辑,结果连CAN帧结构都没搞明白,最后调试时抓瞎。今天咱们就把这块地基打牢。
4.1 CAN帧结构——数据在线上怎么跑
CAN总线上的数据,是以“帧”为单位传输的。每一帧就像一封信,有信封、有内容、有校验。咱们做Bootloader最常用的是数据帧和远程帧,其中数据帧占了99%的通信量。
一个标准CAN数据帧(CAN 2.0A)长这样:
| SOF | 11位标识符 | RTR | IDE | r0 | DLC | 数据段(0-8字节) | CRC | ACK | EOF |
| 1 | 11 | 1 | 1 | 1 | 4 | 0-64 | 15 | 2 | 7 |
我简单解释几个关键字段:
- SOF(Start of Frame):帧起始,1个显性位。告诉总线上其他节点:“我要发数据了,你们听着。”
- 标识符(Identifier):11位(标准帧)或29位(扩展帧)。这玩意儿决定了帧的优先级——数值越小,优先级越高。我在项目中遇到过,两个节点同时发数据,标识符小的那个抢到总线,大的那个自动退让。这就是CAN的仲裁机制。
- RTR(Remote Transmission Request):区分数据帧和远程帧。数据帧RTR=0,远程帧RTR=1。咱们刷写时几乎只用数据帧。
- DLC(Data Length Code):4位,表示数据段有多少字节。范围0~8。注意,DLC可以写0,但数据段长度必须≤8。
- 数据段:0~8字节。Bootloader刷写时,一帧最多带8字节有效数据。嗯,这里要注意,UDS诊断协议里很多多帧传输,就是因为8字节装不下完整消息。
- CRC:15位校验码。保证数据在传输过程中没被干扰。我曾经调试一个刷写失败的问题,查了半天发现是CAN收发器坏了,CRC一直报错。
重点记忆:CAN数据帧最大有效载荷是8字节。这是硬件决定的,改不了。所以刷写大数据时,必须拆成多帧发送。
4.2 CAN标识符分配——谁是谁的“门牌号”
标识符在CAN通信里,相当于门牌号。每个ECU、每个功能模块,都有自己的标识符范围。做Bootloader时,标识符分配直接决定了通信能不能打通。
我个人的习惯是,把标识符分成几类:
- 功能寻址标识符:比如0x7DF,用于诊断请求。所有ECU都能收到,但只有符合条件的ECU才响应。
- 物理寻址标识符:比如0x7E0(请求)、0x7E8(响应)。一对一通信,刷写时最常用。
- 广播标识符:比如0x7FF,所有节点都处理。但刷写时很少用,怕冲突。
举个例子,咱们做UDS刷写时,标准做法是:
请求ID: 0x7E0 (Tester -> ECU)
响应ID: 0x7E8 (ECU -> Tester)
为什么这么定?因为ISO 15765-2协议里就是这么规定的。你想想看,如果每个OEM都自己乱定标识符,那诊断仪插到不同车上就没法用了。所以行业里有个共识:标识符分配要遵循标准,别自己瞎搞。
避坑指南:我曾经在一个项目里,客户要求把刷写标识符改成0x600和0x608。结果发现和另一个ECU的常规通信冲突了,总线上一片混乱。后来老老实实改回标准ID。所以我的建议是:除非有特殊需求,否则别动标准标识符。
4.3 多帧传输机制——8字节不够用怎么办?
前面说了,一帧最多8字节。但Bootloader刷写时,一个数据块可能几百KB甚至几MB。怎么办?拆成多帧发。这就是多帧传输机制。
ISO 15765-2(俗称CAN-TP)定义了三种帧类型:
- 单帧(SF):数据≤7字节时,一帧搞定。第一个字节高4位是0,低4位是数据长度。
- 首帧(FF):数据>7字节时,第一帧发首帧。第一个字节高4位是1,低4位和第二个字节组成16位总长度。
- 连续帧(CF):首帧之后,剩下的数据用连续帧发。每个连续帧带一个序列号(SN),从1开始递增。
这里有个关键角色——流控帧(FC)。接收方收到首帧后,会回复一个流控帧,告诉发送方:“你慢点发,我处理不过来”或者“你快点发,我等着呢”。
流控帧的结构:
| 字节0 | 字节1 | 字节2 |
| 0x30 | BS | STmin |
- 0x30:流控帧标识。高4位是3,低4位是流控状态(0=继续发,1=等待,2=溢出/中止)。
- BS(Block Size):块大小。告诉发送方,每发BS个连续帧后,必须等一个流控帧才能继续。BS=0表示不限量。
- STmin(Separation Time minimum):最小间隔时间。单位是毫秒或100微秒。告诉发送方,两个连续帧之间至少隔多久。
我举个例子,让你直观感受一下:
发送方(Tester) 接收方(ECU)
| |
|--- 首帧(FF, 总长度1024字节) ---->|
| |--- 流控帧(FC, BS=5, STmin=10ms) ---->|
|--- 连续帧1(SN=1) -------------->|
|--- 连续帧2(SN=2) -------------->|
|--- 连续帧3(SN=3) -------------->|
|--- 连续帧4(SN=4) -------------->|
|--- 连续帧5(SN=5) -------------->|
| |--- 流控帧(FC, BS=5, STmin=10ms) ---->|
|--- 连续帧6(SN=6) -------------->|
| ... 以此类推 ... |
看到没?每发5帧,ECU就发一个流控帧,告诉Tester继续。如果ECU忙不过来,可以把BS设小一点,或者把STmin设大一点。这就是流控的核心——协调收发双方的速度。
注意:连续帧的序列号SN是4位,范围0~15。SN=0是特殊用途(用于连续帧的第一帧?不对,首帧之后第一个连续帧SN=1)。如果数据量很大,SN会循环。我曾经遇到一个Bug:发送方SN从15跳到0时,接收方没处理好,导致数据错乱。所以实现时一定要处理好SN的循环逻辑。
4.4 多帧传输的避坑经验
做Bootloader刷写时,多帧传输最容易出问题的地方有这几个:
- 流控帧丢失:如果ECU发的流控帧被总线干扰丢了,Tester会一直等,直到超时。我建议Tester端加一个超时重传机制,比如等200ms没收到流控帧,就重发首帧。
- BS和STmin设置不合理:有些ECU处理能力弱,BS设太大或STmin设太小,会导致ECU缓冲区溢出。我曾经调一个项目,ECU的CAN接收缓冲区只有8帧,BS设成10,结果连续丢帧。后来把BS改成5,问题解决。
- 连续帧顺序错乱:如果总线负载高,连续帧可能乱序到达。接收方必须按SN重组数据。我见过一个实现,直接用数组按SN存,最后按顺序拼接,简单可靠。
我的习惯:在Bootloader的CAN驱动层,我会把多帧接收做成一个状态机。状态包括:IDLE(空闲)、WAIT_FF(等首帧)、WAIT_CF(等连续帧)、WAIT_FC(等流控帧)。每个状态都有超时处理。这样代码结构清晰,不容易出Bug。
4.5 本章知识体系图
下面这张图,把CAN帧结构、标识符分配、多帧传输的核心逻辑串起来了。你可以保存下来,做项目时对照着看。
好了,CAN总线通信基础就讲到这里。帧结构、标识符分配、多帧传输机制,这三块是Bootloader刷写的通信基石。你把这些搞懂了,后面讲刷写流程时,就能理解为什么要有那么多握手、流控、超时处理了。
一句话总结:CAN帧结构决定了数据怎么装,标识符决定了发给谁,多帧传输决定了大数据怎么拆。三者缺一不可。