cpd75

CAN通信协议

 ​一、基础概念​

  1. 物理连接​:
    • TX/RX无需交叉连接(高速CAN默认)
    • 采用差分信号传输(抗干扰性强)
  2. 通信方式​:
    • 广播式通信:发送方主动广播数据(数据帧)
    • 请求式通信:接收方主动请求数据(遥控帧)
    • 接收方通过拉低总线电平(显性电平0)确认接收(类似停止位)
  3. 关键术语​:
    • DLC​:数据长度码(1~8字节)
    • IDE​:标识符扩展位(0=标准帧11位ID,1=扩展帧29位ID)
    • RTR​:远程传输请求位(0=数据帧,1=遥控帧)
    • r0/r1​:保留位(为协议升级预留)

​二、帧类型与结构​

帧类型

用途

数据帧

主动广播数据(发送设备→所有设备)

遥控帧

请求数据(接收设备→发送设备,无数据段)

错误帧

设备检测错误时通知总线(破坏当前通信)

过载帧

接收设备未准备好时延缓发送方

帧间隔

分隔数据帧/遥控帧与其他帧

​数据帧结构​(标准格式):

字段

功能

SOF (帧起始)

显性电平0,标志传输开始

ID (标识符)

11位,区分功能并决定优先级(值越小优先级越高)

RTR

0=数据帧

IDE

0=标准帧

DLC

4位,指定数据段长度(0~8字节)

Data

实际数据(0~8字节)

CRC

15位循环冗余校验 + 1位界定符(隐性1)

ACK

发送方发隐性1,接收方拉低为显性0确认

EOF (帧结束)

7位隐性1,标志传输结束

扩展帧差异​:

  • 29位ID(11位基础ID + 18位扩展ID)
  • 新增SRR位(固定隐性1,保证标准帧优先)

​三、核心机制​

  1. 非破坏性仲裁​:
    • 线与特性​:总线显性0覆盖隐性1(0 & x = 0,1 & 1 = 1)
    • 回读机制​:设备发送后回读总线电平,确认是否发送成功
    • 仲裁规则​:
      • 逐位比较ID,显性0优先级高
      • ID相同时:数据帧 > 遥控帧,标准帧 > 扩展帧
  2. 位填充规则​:
    • 发送方:连续5个相同电平后插入一个反向电平(填充位)
    • 接收方:移除填充位恢复原始数据
    • 作用​:提供定时同步、区分错误帧/过载帧、保持总线活跃
  3. 再同步机制​:
    • 位时间划分:同步段(SS) + 传播段(PTS) + 相位缓冲段1(PBS1) + 相位缓冲段2(PBS2)
    • 接收方通过调整PBS1/PBS2(±SJW值)对齐采样点

​四、错误处理​

  1. 错误类型​:

错误类型

触发条件

位错误

发送电平与总线电平不一致

填充错误

连续检测到6个相同电平(违反填充规则)

CRC错误

接收方计算的CRC与帧内CRC不匹配

格式错误

固定格式字段(如界定符、EOF)电平错误

ACK错误

发送方未检测到ACK显性0(无设备接收)

  1. 错误状态转换​:

状态

行为

主动错误

可正常通信,检测错误时发送主动错误帧(破坏总线)

被动错误

可正常通信,检测错误时发送被动错误帧(不破坏总线)

总线关闭

禁止通信(需检测128次连续11位隐性1恢复)

  1. 错误计数器​:
    • TEC​(发送错误计数器):错误发送时增加,成功发送时减少
    • REC​(接收错误计数器):接收错误时增加,成功接收时减少
    • 状态转换阈值:
      • TEC/REC < 128 → 主动错误
      • TEC/REC ≥ 128 → 被动错误
      • TEC > 255 → 总线关闭

​五、物理层特性​

  • 无时钟线​:设备约定波特率,通过位时序同步
  • 采样点​:位于数据位中心附近(通过再同步调整)
  • 总线空闲​:连续11位隐性1(设备仅此时可发起发送)

​六、STM32 CAN外设简介​

  1. 特性​:
    • 支持CAN 2.0A/B
    • 最高波特率1 Mbps
    • 3个发送邮箱(可配置优先级)
    • 2个接收FIFO(3级深度)
    • 14个过滤器组(互联型28个)
  2. 关键功能​:
    • 自动重传控制
    • 时间触发通信模式
    • 自动离线恢复
    • 接收FIFO溢出处理可配置

​关键总结表​

​机制​

​核心规则​

优先级仲裁

ID值小者优先 → 数据帧 > 遥控帧 → 标准帧 > 扩展帧

位填充

连续5相同电平 → 插入1反向电平

总线空闲判定

连续11位隐性1

错误状态切换

TEC/REC ≥ 128 → 被动错误;TEC > 255 → 总线关闭

ACK机制

发送方发1,接收方拉低为0确认

CAN总线仲裁机制示例

下面是一个用C语言实现的简化版CAN总线仲裁模拟程序,配合波形图解释CAN总线的核心机制。

  1 #include <stdio.h>
  2 
  3 #include <stdlib.h>
  4 
  5 #include <stdbool.h>
  6 
  7 #include <string.h>
  8 
  9 #include <unistd.h>
 10 
 11 // CAN节点结构体
 12 
 13 typedef struct {
 14 
 15 int id; // 节点ID
 16 
 17 bool active; // 节点是否活跃
 18 
 19 int state; // 0:发送显性位, 1:发送隐性位
 20 
 21 int bit_index; // 当前发送的位索引
 22 
 23 char frame[14]; // 要发送的帧(SOF+ID+RTR+IDE+控制段)
 24 
 25 } CAN_Node;
 26 
 27 // 总线状态枚举
 28 
 29 typedef enum {
 30 
 31 RECESSIVE, // 隐性 (逻辑1)
 32 
 33 DOMINANT // 显性 (逻辑0)
 34 
 35 } BusState;
 36 
 37 // 模拟CAN总线
 38 
 39 void simulate_can_bus() {
 40 
 41 // 创建三个节点
 42 
 43 CAN_Node nodes[3];
 44 
 45 
 46 
 47 // 初始化节点 (ID越小优先级越高)
 48 
 49 nodes[0] = (CAN_Node){0x123, true, -1, 0, {0,0,0,1,0,0,1,0,0,0,1,1,0,0}}; // ID: 0x123
 50 
 51 nodes[1] = (CAN_Node){0x124, true, -1, 0, {0,0,0,1,0,0,1,0,0,1,0,0,0,0}}; // ID: 0x124
 52 
 53 nodes[2] = (CAN_Node){0x122, true, -1, 0, {0,0,0,1,0,0,1,0,0,0,1,0,0,0}}; // ID: 0x122 (最高优先级)
 54 
 55 
 56 
 57 printf("CAN总线仲裁模拟 - 节点ID: 0x%X, 0x%X, 0x%X\n\n",
 58 
 59 nodes[0].id, nodes[1].id, nodes[2].id);
 60 
 61 
 62 
 63 BusState bus = RECESSIVE;
 64 
 65 bool arbitration_complete = false;
 66 
 67 int winner_id = -1;
 68 
 69 
 70 
 71 // 逐位模拟总线传输
 72 
 73 for (int bit_pos = 0; bit_pos < 14; bit_pos++) {
 74 
 75 // 重置总线为隐性
 76 
 77 bus = RECESSIVE;
 78 
 79 
 80 
 81 // 每个节点发送当前位
 82 
 83 for (int i = 0; i < 3; i++) {
 84 
 85 if (nodes[i].active && bit_pos < strlen(nodes[i].frame)) {
 86 
 87 // 发送显性位会覆盖隐性位
 88 
 89 if (nodes[i].frame[bit_pos] == '0') {
 90 
 91 bus = DOMINANT;
 92 
 93 }
 94 
 95 }
 96 
 97 }
 98 
 99 
100 
101 // 节点检测总线状态
102 
103 for (int i = 0; i < 3; i++) {
104 
105 if (nodes[i].active && bit_pos < strlen(nodes[i].frame)) {
106 
107 // 回读机制:比较发送值与总线实际值
108 
109 if (nodes[i].frame[bit_pos] == '1' && bus == DOMINANT) {
110 
111 // 仲裁失败:发送隐性位但读到显性位
112 
113 nodes[i].active = false;
114 
115 printf("位 %2d: 节点 0x%X 仲裁失败\n", bit_pos, nodes[i].id);
116 
117 }
118 
119 }
120 
121 }
122 
123 
124 
125 // 检查仲裁是否完成
126 
127 int active_count = 0;
128 
129 for (int i = 0; i < 3; i++) {
130 
131 if (nodes[i].active) {
132 
133 active_count++;
134 
135 winner_id = nodes[i].id;
136 
137 }
138 
139 }
140 
141 
142 
143 // 打印当前总线状态
144 
145 printf("位 %2d: 总线状态: %s | 发送节点: ",
146 
147 bit_pos, bus == DOMINANT ? "显性(0)" : "隐性(1)");
148 
149 
150 
151 for (int i = 0; i < 3; i++) {
152 
153 if (nodes[i].active && bit_pos < strlen(nodes[i].frame)) {
154 
155 printf("0x%X:%c ", nodes[i].id, nodes[i].frame[bit_pos]);
156 
157 }
158 
159 }
160 
161 printf("\n");
162 
163 
164 
165 // 仲裁完成判断
166 
167 if (!arbitration_complete && active_count == 1) {
168 
169 printf("\n>>> 仲裁完成! 胜出节点: 0x%X <<<\n\n", winner_id);
170 
171 arbitration_complete = true;
172 
173 }
174 
175 
176 
177 // 位之间延迟
178 
179 usleep(300000);
180 
181 }
182 
183 
184 
185 printf("\n传输结束\n");
186 
187 }
188 
189 int main() {
190 
191 simulate_can_bus();
192 
193 return 0;
194 
195 }

 

程序输出解析

运行上述程序会显示类似以下输出:

 1 CAN总线仲裁模拟 - 节点ID: 0x123, 0x124, 0x122
 2 
 30: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
 4 
 51: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
 6 
 72: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
 8 
 93: 总线状态: 显性(0) | 发送节点: 0x123:1 0x124:1 0x122:1
10 
114: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
12 
135: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
14 
156: 总线状态: 显性(0) | 发送节点: 0x123:1 0x124:1 0x122:1
16 
177: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
18 
198: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
20 
219: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:1 0x122:0
22 
2310: 总线状态: 显性(0) | 发送节点: 0x123:1 0x124:0 0x122:1
24 
2511: 总线状态: 显性(0) | 发送节点: 0x122:0
26 
2712: 总线状态: 显性(0) | 发送节点: 0x122:0
28 
2913: 总线状态: 显性(0) | 发送节点: 0x122:0
30 
31 传输结束

 

CAN总线波形图与仲裁过程

 1 位位置: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
 2 
 3 -----------------------------------------------------
 4 
 5 节点0x122: 0 0 0 1 0 0 1 0 0 0 1 0 0 0
 6 
 7 节点0x123: 0 0 0 1 0 0 1 0 0 0 1 1 0 0
 8 
 9 节点0x124: 0 0 0 1 0 0 1 0 0 1 0 0 0 0
10 
11 -----------------------------------------------------
12 
13 总线状态: 0 0 0 1 0 0 1 0 0 0 1 0 0 0
14 
15 ↑ ↑ ↑ ↑
16 
17 SOF | | 仲裁结束
18 
19 差异点 仲裁失败点

 

关键机制解释:

  1. 线与特性​:
    • 当任何节点发送显性位(0)时,总线表现为显性(0)
    • 只有当所有节点都发送隐性位(1)时,总线才表现为隐性(1)
  2. 非破坏性仲裁​:
    • 所有节点从SOF(显性位)开始同时发送
    • 逐位比较ID(高位先发送)
    • 在位9:节点0x123发送0(显性),节点0x124发送1(隐性),总线为0
    • 节点0x124检测到自己发送1但总线为0 → 仲裁失败,停止发送
    • 在位10:节点0x122发送1(隐性),节点0x123发送1(隐性),总线为1
    • 在位11:节点0x122发送0(显性),节点0x123发送1(隐性) → 节点0x123仲裁失败
  3. 优先级规则​:
    • ID值越小优先级越高(0x122 < 0x123 < 0x124)
    • 前8位所有节点ID相同(00100100)
    • 第9位:0x122和0x123发送0(显性),0x124发送1(隐性) → 0x124失败
    • 第10位:所有节点发送1(隐性) → 无冲突
    • 第11位:0x122发送0(显性),0x123发送1(隐性) → 0x123失败
  4. 帧结构​:
    • 位0:SOF(帧起始),固定为显性(0)
    • 位1-11:11位标识符(ID)
    • 位12:RTR(远程传输请求),0表示数据帧
    • 位13:IDE(标识符扩展),0表示标准帧

波形图详解:

1 位位置: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
2 
3 -----------------------------------------------------
4 
5 总线波形: __ __ __ -- __ __ -- __ __ __ -- __ __ __
6 
7 0 0 0 1 0 0 1 0 0 0 1 0 0 0
8 
9 图例: "__" = 显性(0), "--" = 隐性(1)

 

  1. SOF(位0)​​:显性起始位,标志帧开始
  2. ID字段(位1-11)​​:
    • 前8位(位1-8):00100100 (所有节点相同)
    • 位9:0(显性) → 淘汰ID高位为1的节点
    • 位10:1(隐性) → 无冲突
    • 位11:0(显性) → 淘汰发送1的节点
  3. 控制段(位12-13)​​:RTR=0(数据帧),IDE=0(标准帧)

通过这个示例,可以看到CAN总线通过非破坏性仲裁实现多节点无冲突通信,优先级高的节点能够无损完成传输,而优先级低的节点会自动退出发送等待下次机会。

这里推荐江科大的CAN总线教学视频,我觉得讲得特别好

CAN总线入门教程-全面细致 面包板教学 多机通信_哔哩哔哩_bilibili

posted on 2025-06-09 23:28  窄路徐行  阅读(654)  评论(0)    收藏  举报

导航