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总线通信基础 - 知识体系 CAN帧结构 • SOF: 帧起始位 • 标识符: 11/29位 • DLC: 数据长度码 • 数据段: 0~8字节 • CRC: 15位校验 • ACK/EOF: 应答与结束 CAN标识符分配 • 功能寻址: 0x7DF • 物理寻址: 0x7E0/0x7E8 • 广播标识符: 0x7FF • 优先级: 数值越小越高 • 遵循ISO 15765-2标准 • 避免与常规通信冲突 多帧传输机制 • 单帧(SF): ≤7字节 • 首帧(FF): 总长度信息 • 连续帧(CF): 带序列号 • 流控帧(FC): BS/STmin • 块大小控制发送节奏 • 超时重传机制 核心要点 帧结构是基础 → 标识符决定通信对象 → 多帧机制解决大数据传输 三者结合,才能实现可靠的Bootloader刷写 图:CAN总线通信基础三大模块及其关系

好了,CAN总线通信基础就讲到这里。帧结构、标识符分配、多帧传输机制,这三块是Bootloader刷写的通信基石。你把这些搞懂了,后面讲刷写流程时,就能理解为什么要有那么多握手、流控、超时处理了。

一句话总结:CAN帧结构决定了数据怎么装,标识符决定了发给谁,多帧传输决定了大数据怎么拆。三者缺一不可。

可可精品资料库