CAN通信协议
一、基础概念
[*]物理连接:
[*]TX/RX无需交叉连接(高速CAN默认)
[*]采用差分信号传输(抗干扰性强)
[*]通信方式:
[*]广播式通信:发送方主动广播数据(数据帧)
[*]请求式通信:接收方主动请求数据(遥控帧)
[*]接收方通过拉低总线电平(显性电平0)确认接收(类似停止位)
[*]关键术语:
[*]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,保证标准帧优先)
三、核心机制
[*]非破坏性仲裁:
[*]线与特性:总线显性0覆盖隐性1(0 & x = 0,1 & 1 = 1)
[*]回读机制:设备发送后回读总线电平,确认是否发送成功
[*]仲裁规则:
[*]逐位比较ID,显性0优先级高
[*]ID相同时:数据帧 > 遥控帧,标准帧 > 扩展帧
[*]位填充规则:
[*]发送方:连续5个相同电平后插入一个反向电平(填充位)
[*]接收方:移除填充位恢复原始数据
[*]作用:提供定时同步、区分错误帧/过载帧、保持总线活跃
[*]再同步机制:
[*]位时间划分:同步段(SS) + 传播段(PTS) + 相位缓冲段1(PBS1) + 相位缓冲段2(PBS2)
[*]接收方通过调整PBS1/PBS2(±SJW值)对齐采样点
四、错误处理
[*]错误类型:
错误类型
触发条件
位错误
发送电平与总线电平不一致
填充错误
连续检测到6个相同电平(违反填充规则)
CRC错误
接收方计算的CRC与帧内CRC不匹配
格式错误
固定格式字段(如界定符、EOF)电平错误
ACK错误
发送方未检测到ACK显性0(无设备接收)
[*]错误状态转换:
状态
行为
主动错误
可正常通信,检测错误时发送主动错误帧(破坏总线)
被动错误
可正常通信,检测错误时发送被动错误帧(不破坏总线)
总线关闭
禁止通信(需检测128次连续11位隐性1恢复)
[*]错误计数器:
[*]TEC(发送错误计数器):错误发送时增加,成功发送时减少
[*]REC(接收错误计数器):接收错误时增加,成功接收时减少
[*]状态转换阈值:
[*]TEC/REC < 128 → 主动错误
[*]TEC/REC ≥ 128 → 被动错误
[*]TEC > 255 → 总线关闭
五、物理层特性
[*]无时钟线:设备约定波特率,通过位时序同步
[*]采样点:位于数据位中心附近(通过再同步调整)
[*]总线空闲:连续11位隐性1(设备仅此时可发起发送)
六、STM32 CAN外设简介
[*]特性:
[*]支持CAN 2.0A/B
[*]最高波特率1 Mbps
[*]3个发送邮箱(可配置优先级)
[*]2个接收FIFO(3级深度)
[*]14个过滤器组(互联型28个)
[*]关键功能:
[*]自动重传控制
[*]时间触发通信模式
[*]自动离线恢复
[*]接收FIFO溢出处理可配置
关键总结表
机制
核心规则
优先级仲裁
ID值小者优先 → 数据帧 > 遥控帧 → 标准帧 > 扩展帧
位填充
连续5相同电平 → 插入1反向电平
总线空闲判定
连续11位隐性1
错误状态切换
TEC/REC ≥ 128 → 被动错误;TEC > 255 → 总线关闭
ACK机制
发送方发1,接收方拉低为0确认
CAN总线仲裁机制示例
下面是一个用C语言实现的简化版CAN总线仲裁模拟程序,配合波形图解释CAN总线的核心机制。
1 #include 2 3 #include 4 5 #include 6 7 #include 8 9 #include1011 // CAN节点结构体 1213 typedef struct { 1415 int id; // 节点ID 1617 bool active; // 节点是否活跃 1819 int state; // 0:发送显性位, 1:发送隐性位 2021 int bit_index; // 当前发送的位索引 2223 char frame; // 要发送的帧(SOF+ID+RTR+IDE+控制段) 2425 } CAN_Node; 2627 // 总线状态枚举 2829 typedef enum { 3031 RECESSIVE, // 隐性 (逻辑1) 3233 DOMINANT // 显性 (逻辑0) 3435 } BusState; 3637 // 模拟CAN总线 3839 void simulate_can_bus() { 4041 // 创建三个节点 4243 CAN_Node nodes; 44454647 // 初始化节点 (ID越小优先级越高) 4849 nodes = (CAN_Node){0x123, true, -1, 0, {0,0,0,1,0,0,1,0,0,0,1,1,0,0}}; // ID: 0x123 5051 nodes = (CAN_Node){0x124, true, -1, 0, {0,0,0,1,0,0,1,0,0,1,0,0,0,0}}; // ID: 0x124 5253 nodes = (CAN_Node){0x122, true, -1, 0, {0,0,0,1,0,0,1,0,0,0,1,0,0,0}}; // ID: 0x122 (最高优先级) 54555657 printf("CAN总线仲裁模拟 - 节点ID: 0x%X, 0x%X, 0x%X\n\n", 5859 nodes.id, nodes.id, nodes.id); 60616263 BusState bus = RECESSIVE; 6465 bool arbitration_complete = false; 6667 int winner_id = -1; 68697071 // 逐位模拟总线传输 7273 for (int bit_pos = 0; bit_pos < 14; bit_pos++) { 7475 // 重置总线为隐性 7677 bus = RECESSIVE; 78798081 // 每个节点发送当前位 8283 for (int i = 0; i < 3; i++) { 8485 if (nodes.active && bit_pos < strlen(nodes.frame)) { 8687 // 发送显性位会覆盖隐性位 8889 if (nodes.frame == '0') { 9091 bus = DOMINANT; 9293 } 9495 } 9697 } 9899 100 101 // 节点检测总线状态102 103 for (int i = 0; i < 3; i++) {104 105 if (nodes.active && bit_pos < strlen(nodes.frame)) {106 107 // 回读机制:比较发送值与总线实际值108 109 if (nodes.frame == '1' && bus == DOMINANT) {110 111 // 仲裁失败:发送隐性位但读到显性位112 113 nodes.active = false;114 115 printf("位 %2d: 节点 0x%X 仲裁失败\n", bit_pos, nodes.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.active) {132 133 active_count++;134 135 winner_id = nodes.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.active && bit_pos < strlen(nodes.frame)) {154 155 printf("0x%X:%c ", nodes.id, nodes.frame);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
页:
[1]