标准CAN帧介绍
标准CAN(Controller Area Network)结构
CAN总线通信的核心就是节点间通过发送和接收符合特定格式的“帧”来实现。标准CAN有两种主要的帧格式:标准帧(11位标识符)和扩展帧(29位标识符)。这里我们首先聚焦于最基础的标准数据帧。
 一个标准CAN数据帧由以下7个不同的位字段(Bit Fields)组成,如下图所示:
 
1.帧起始(SOF-Start Of Frame)
- 长度:1 bit
- 值:显性位(Dominant Bit,逻辑0)
- 作用:标志着数据帧的开始。它同步所有总线上的节点,表示一个新的报文即将开始传输。在总线空闲时,第一个发送显性位的节点获得总线访问权。
2.仲裁段(Arbitration Field)
这个字段决定了报文的优先级,并在多个节点同时发送时解决冲突(仲裁)。
 它由两部分组成:
- 标识符(Identifier):
 – 长度:11 bits
 – 作用:标识报文的含义和优先级。ID值越小,优先级越高(因为显性位0优先)。例如,ID为0x000的报文优先级最高,ID为0x7FF的优先级最低。接收节点根据ID来决定是否接收该报文。
- 远程传输请求位(RTR-Remote Transmission Request):
 – 长度:1 bit
 – 作用:区分是 数据帧 还是 远程帧。
 — 数据帧(Data Frame):RTR = 显性位(0)。表示该帧带有数据。
 — 远程帧(Remote Frame):RTR = 隐性位(1)。用于向其他节点请求发送具有相同ID的数据帧。远程帧没有数据段。
3.控制段(Control Field)
这个字段提供了关于数据长度的信息。
 它由三部分组成:
- 标识符扩展位(IDE-Identifier Extension) 
 – 长度:1 bit
 – 作用:区分标准帧和扩展帧。
 — 标准帧:IDE = 显性位(0)。
 — 扩展帧:IDE = 隐性位(1)。(扩展帧的仲裁段结构不同)
- 保留位(r0): 
 – 长度:1 bit
 – 作用:保留位,必须发送显性位(0),但接收器可以接收显性或隐性位。
- 数据长度码(DLC-Data Length Code): 
 – 长度:4 bit
 – 作用:指示数据段中包含的字节数。取值范围从0到8。DLC值大于8的情况在CAN FD中定义,经典CAN中无效。
4.数据段(Data Field)
- 长度:0-8 bytes(由DLC决定)
- 作用:包含实际要传输的数据内容。这是报文的有效载荷。CAN协议允许灵活的数据长度,非常适合传输控制命令、传感器读数等短小精悍的信息。
5.CRC段(CRC Field)
这个字段用于检测传输错误。
 它由两部分组成:
- CRC序列(CRC Sequence) 
 – 长度:15 bits
 – 作用:循环冗余校验值。由发送器根据帧起始、仲裁段、控制段和数据段的内容计算得出。
- CRC界定符(CRC Dilimiter) 
 – 长度:1 bit
 – 值:隐性位(1)
 – 作用:作为一个固定的分隔符,标志着CRC序列的结束。
6.应答段(ACK Field)
这个字段用于接收节点确认是否接收到报文。
 它由两部分组成:
- 应答间隙(ACK Slot): 
 – 长度:1 bit
 – 发送器行为:发送一个隐性位(1)。
 – 接收器行为:任何正确接收到报文(通过CRC校验)的接收节点,都会在ACK Slot时间段内发送一个**显性位(0)**来覆盖它。
- 应答界定符(ACK Dilimiter): 
 – 长度:1 bit
 – 值:隐性位(1)
 – 作用:标志着应答段的结束。它必须为隐性位。
ACK段的工作方式:发送器发送隐性位(1),如果至少有一个接收器正确接收了帧,它就会用显性位(0)覆盖这个位。因此,发送器如果在ACK Slot读到隐性位(1),就知道没有节点成功接收,会触发错误并重发。
7.结束段(EOF-End Of Frame)
– 长度:7 bits
 – 值:全部为隐性位(1)
 – 作用:明确标志该帧的传输结束。
8.帧间间隔(IFS-Inter Frame Space)
虽然严格来说不属于帧的一部分,但它是帧与帧之间必需的间隔。
- 长度:3 bits(或更多,具体由控制器实现决定)
- 作用:在帧结束和下一帧开始(或总线空闲)之间提供一个最小间隔,让控制器内部有足够的时间处理刚接收到的帧。
总结与特点
- 非破坏性仲裁:基于标识符(ID)的优先级仲裁机制。优先级高的报文继续发送,优先级低的自动退出发送并在总线空闲时重试,没有任何数据损失或丢失。
- 高可靠性:通过CRC校验、应答位(ACK)和强大的错误检测与信令机制,保证了数据传输的极高可靠性。
- 广播与过滤:报文被广播到所有节点,每个节点通过标识符过滤来决定是否接收和处理该报文。
- 短小高效:数据长度最高为8字节,开销小,非常适合高实时性要求的控制系统。
具体例子
下面,我将通过一个具体的例子来详细说明标准CAN帧的每一部分。
场景设定
假设我们有一个简单的汽车网络,里面有两个节点:
 1.发动机控制单元(ECU_Engine):负责报告发动机转速。
 2.仪表盘单元(ECU_Dashboard):负责接收转速并显示在转速表上。
 ECU_Engine需要没秒发送100次发动机转速数据。我们假设当前发动机转速为 3000 RPM。
步骤1:组帧前的准备
1.报文标识符(ID):我们需要为转速报文分配一个唯一的ID。假设我们分配 ID = 0x123(十六进制)。这个ID决定了报文的优先级。0x123是一个中等偏高的优先级(因为数值较小)。
 2.数据:转速数据为3000。我们需要用2个字节(16位)来表示它。假设我们使用大端模式(Big-endian)(即高位字节在前):
- 3000的十六进制是0x0BB8。
- 因此,数据段的两个字节为:
 – Byte 0 =0x0B(高字节)
 – Byte 1 =0xB8(低字节)
 3.数据长度:我们有2个字节的数据,所以数据长度码(DLC)为2。
步骤2:构建标准数据帧
现在,我们来逐字段构建这个转速数据帧。
| 字段 | 值(二进制) | 值(十六进制) | 说明 | 
|---|---|---|---|
| 1.帧起始(SOF) | 0 | - | 一个显性位(0),标志开始。 | 
| 2.仲裁段 | |||
| 标识符(11位) | 001 0010 0011 | 0x123 | 这是报文的ID。注意最高7位是 0x09(0010010),最低4位是0x3(0011),合起来是0x123。 | 
| RTR位 | 0 | - | 显性位(0),表面这是一个数据帧 | 
| 3.控制段 | |||
| IDE位 | 0 | - | 显性位(0),表面这是标准帧(11位ID)。 | 
| 保留位r0 | 0 | - | 必须发送显性位(0)。 | 
| DLC(4位) | 0100 | 0x2 | 数据长度为 2个字节。 | 
| 4.数据段(2字节) | |||
| 数据字节0 | 0000 1011 | 0x0B | 转速数据的高字节(3000的高位部分)。 | 
| 数据字节1 | 1011 1000 | 0xB8 | 转速数据的低字节(3000的低位部分)。 | 
| 5.CRC段 | |||
| CRC序列(15位) | CRC算法(计算得出) | e.g., 0x7FAC | 发送器根据前面所有位计算出的校验值。假设这里是 0x7FAC | 
| CRC界定符 | 1 | - | 隐性位(1),固定格式。 | 
| 6.应答段(ACK) | |||
| ACK间隙 | 1→ \to →0 | - | 发送器发送隐性位(1),但被接收器用显性位(0)覆盖。 | 
| ACK界定符 | 1 | - | 隐性位(1),固定格式。 | 
| 7.结束段(EOF) | 1111111 | - | 7个连续的隐性位(1),标志帧结束。 | 
| 8.帧间间隔(IFS) | - | - | 总线短暂空闲,为下一帧做准备。 | 
步骤3:总线上的传输与仲裁过程
1.发送:ECU_Engine开始将上述比特流逐位放到CAN总线上,从SOF开始。
 2.仲裁(假设此时有另一个节点要发送低优先级消息):
- 在发送仲裁段(ID+RTR)时,ECU_Engine也在监听总线。 
- 它发送ID - 0x123(- 00100100011)。
- 如果另一个节点同时发送一个ID为 - 0x124(- 00100100100)的报文,仲裁会发生:
 – 前8位(- 00100100)两者完全相同,总线状态也一致,没有胜负。
 – 比较第9位:ECU_Engine发- 0,另一个节点发- 0,还是平手。
 – 比较第10位:ECU_Engine发- 0,另一个节点发- 1。
 – 显性位(0)优先于隐性位(1)。因此,ECU_Engine赢得仲裁,继续发送。
 – 另一个节点检测到它发送的是隐性位(1),但读到的是显性位(0),意识到自己优先级更低,立即退出发送模式转为接收模式,等待下次重试。
- 这就是非破坏性仲裁:高优先级报文无延迟地继续发送,低优先级报文自动退出,没有任何数据损坏。 
步骤4:接收与应答过程
1.接收:总线上所有节点(包括ECU_Dashboard)都接收这个帧。
 2.CRC校验:每个接收节点都用同样的算法对接收到的数据(SOF,仲裁段,控制段,数据段)进行计算,得到一个CRC值。
 3.发送应答(ACK):
- 在ACK Slot时间段,发送器ECU_Engine会释放总线,发送一个隐性位(1)。
- 所有正确接收到该帧(即CRC校验通过)的节点(在这个例子中,ECU_Dashboard肯定会的),都会在ACK Slot时间段内**发送一个显性位(0)**来覆盖这个隐性位。
4.确认:
- 发送器ECU_Engine在ACK Slot读总线。如果它读到了显性位(0),它就知道至少有一个节点成功接收了它的报文,于是认为发送成功。
- 如果它读到的仍然是隐性位(1),就意味着没有一个节点成功接收,这将出发错误计数器,ECU_Engine会稍后自动重发该报文。
步骤5数据处理
ECU_Dashboard成功接收帧后:
 1.通过ID0x123识别出这是发动机转速报文。
 2.根据DLC2知道数据段有2个字节。
 3.从数据段提出两个字节0x0B和0xB8。
 4.将它们组合成0x0BB8,转换为十进制数字 3000 。
 5.最后,驱动转速表指针指向3000 RPM的位置。