一、基础概念
-
物理连接:
- 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相同时:数据帧 > 遥控帧,标准帧 > 扩展帧
- 逐位比较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总线的核心机制。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
// CAN节点结构体
typedef struct {
int id; // 节点ID
bool active; // 节点是否活跃
int state; // 0:发送显性位, 1:发送隐性位
int bit_index; // 当前发送的位索引
char frame[14]; // 要发送的帧(SOF+ID+RTR+IDE+控制段)
} CAN_Node;
// 总线状态枚举
typedef enum {
RECESSIVE, // 隐性 (逻辑1)
DOMINANT // 显性 (逻辑0)
} BusState;
// 模拟CAN总线
void simulate_can_bus() {
// 创建三个节点
CAN_Node nodes[3];
// 初始化节点 (ID越小优先级越高)
nodes[0] = (CAN_Node){0x123, true, -1, 0, {0,0,0,1,0,0,1,0,0,0,1,1,0,0}}; // ID: 0x123
nodes[1] = (CAN_Node){0x124, true, -1, 0, {0,0,0,1,0,0,1,0,0,1,0,0,0,0}}; // ID: 0x124
nodes[2] = (CAN_Node){0x122, true, -1, 0, {0,0,0,1,0,0,1,0,0,0,1,0,0,0}}; // ID: 0x122 (最高优先级)
printf("CAN总线仲裁模拟 - 节点ID: 0x%X, 0x%X, 0x%X\n\n",
nodes[0].id, nodes[1].id, nodes[2].id);
BusState bus = RECESSIVE;
bool arbitration_complete = false;
int winner_id = -1;
// 逐位模拟总线传输
for (int bit_pos = 0; bit_pos < 14; bit_pos++) {
// 重置总线为隐性
bus = RECESSIVE;
// 每个节点发送当前位
for (int i = 0; i < 3; i++) {
if (nodes[i].active && bit_pos < strlen(nodes[i].frame)) {
// 发送显性位会覆盖隐性位
if (nodes[i].frame[bit_pos] == '0') {
bus = DOMINANT;
}
}
}
// 节点检测总线状态
for (int i = 0; i < 3; i++) {
if (nodes[i].active && bit_pos < strlen(nodes[i].frame)) {
// 回读机制:比较发送值与总线实际值
if (nodes[i].frame[bit_pos] == '1' && bus == DOMINANT) {
// 仲裁失败:发送隐性位但读到显性位
nodes[i].active = false;
printf("位 %2d: 节点 0x%X 仲裁失败\n", bit_pos, nodes[i].id);
}
}
}
// 检查仲裁是否完成
int active_count = 0;
for (int i = 0; i < 3; i++) {
if (nodes[i].active) {
active_count++;
winner_id = nodes[i].id;
}
}
// 打印当前总线状态
printf("位 %2d: 总线状态: %s | 发送节点: ",
bit_pos, bus == DOMINANT ? "显性(0)" : "隐性(1)");
for (int i = 0; i < 3; i++) {
if (nodes[i].active && bit_pos < strlen(nodes[i].frame)) {
printf("0x%X:%c ", nodes[i].id, nodes[i].frame[bit_pos]);
}
}
printf("\n");
// 仲裁完成判断
if (!arbitration_complete && active_count == 1) {
printf("\n>>> 仲裁完成! 胜出节点: 0x%X <<<\n\n", winner_id);
arbitration_complete = true;
}
// 位之间延迟
usleep(300000);
}
printf("\n传输结束\n");
}
int main() {
simulate_can_bus();
return 0;
}
程序输出解析
运行上述程序会显示类似以下输出:
CAN总线仲裁模拟 - 节点ID: 0x123, 0x124, 0x122
位 0: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
位 1: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
位 2: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
位 3: 总线状态: 显性(0) | 发送节点: 0x123:1 0x124:1 0x122:1
位 4: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
位 5: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
位 6: 总线状态: 显性(0) | 发送节点: 0x123:1 0x124:1 0x122:1
位 7: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
位 8: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0
位 9: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:1 0x122:0
位 10: 总线状态: 显性(0) | 发送节点: 0x123:1 0x124:0 0x122:1
位 11: 总线状态: 显性(0) | 发送节点: 0x122:0
位 12: 总线状态: 显性(0) | 发送节点: 0x122:0
位 13: 总线状态: 显性(0) | 发送节点: 0x122:0
传输结束
CAN总线波形图与仲裁过程
位位置: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
-----------------------------------------------------
节点0x122: 0 0 0 1 0 0 1 0 0 0 1 0 0 0
节点0x123: 0 0 0 1 0 0 1 0 0 0 1 1 0 0
节点0x124: 0 0 0 1 0 0 1 0 0 1 0 0 0 0
-----------------------------------------------------
总线状态: 0 0 0 1 0 0 1 0 0 0 1 0 0 0
↑ ↑ ↑ ↑
SOF | | 仲裁结束
差异点 仲裁失败点
关键机制解释:
-
线与特性:
- 当任何节点发送显性位(0)时,总线表现为显性(0)
- 只有当所有节点都发送隐性位(1)时,总线才表现为隐性(1)
-
非破坏性仲裁:
- 所有节点从SOF(显性位)开始同时发送
- 逐位比较ID(高位先发送)
- 在位9:节点0x123发送0(显性),节点0x124发送1(隐性),总线为0
- 节点0x124检测到自己发送1但总线为0 → 仲裁失败,停止发送
- 在位10:节点0x122发送1(隐性),节点0x123发送1(隐性),总线为1
- 在位11:节点0x122发送0(显性),节点0x123发送1(隐性) → 节点0x123仲裁失败
-
优先级规则:
- ID值越小优先级越高(0x122 < 0x123 < 0x124)
- 前8位所有节点ID相同(00100100)
- 第9位:0x122和0x123发送0(显性),0x124发送1(隐性) → 0x124失败
- 第10位:所有节点发送1(隐性) → 无冲突
- 第11位:0x122发送0(显性),0x123发送1(隐性) → 0x123失败
-
帧结构:
- 位0:SOF(帧起始),固定为显性(0)
- 位1-11:11位标识符(ID)
- 位12:RTR(远程传输请求),0表示数据帧
- 位13:IDE(标识符扩展),0表示标准帧
波形图详解:
位位置: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
-----------------------------------------------------
总线波形: __ __ __ -- __ __ -- __ __ __ -- __ __ __
0 0 0 1 0 0 1 0 0 0 1 0 0 0
图例: "__" = 显性(0), "--" = 隐性(1)
- SOF(位0):显性起始位,标志帧开始
- ID字段(位1-11):
- 前8位(位1-8):00100100 (所有节点相同)
- 位9:0(显性) → 淘汰ID高位为1的节点
- 位10:1(隐性) → 无冲突
- 位11:0(显性) → 淘汰发送1的节点
- 控制段(位12-13):RTR=0(数据帧),IDE=0(标准帧)
通过这个示例,可以看到CAN总线通过非破坏性仲裁实现多节点无冲突通信,优先级高的节点能够无损完成传输,而优先级低的节点会自动退出发送等待下次机会。
这里推荐江科大的CAN总线教学视频,我觉得讲得特别好