目录
CAN总线概述CAN总线物理层深度解析CAN总线数据链路层协议CAN报文格式详解位时序与同步机制仲裁机制与优先级错误检测与处理CAN总线硬件实现方案完整代码实现高级应用与优化
1. CAN总线概述
1.1 CAN总线的诞生背景
CAN(Controller Area Network)总线是由德国BOSCH公司在1983年为汽车内部测量与执行部件之间的数据通信而开发的串行数据通信协议。在CAN总线出现之前,汽车内部采用点对点的布线方式,随着汽车电子设备的增加,线束数量呈指数增长,带来了以下问题:
线束复杂度: 一辆高档轿车的线束重量可达50kg以上,长度超过2000米可靠性下降: 连接点增多导致故障率上升维护困难: 故障定位和维修工作量巨大成本高昂: 铜线和连接器成本占整车成本的3-5%
1.2 CAN总线的技术优势
CAN总线采用多主机工作方式,具有以下显著优势:
1.2.1 实时性强
最高通信速率可达1Mbps(距离≤40m)非破坏性总线仲裁技术保证优先级高的消息优先发送点对点、点对多点及全局广播传送接收数据
1.2.2 可靠性高
采用差分电压传输,抗干扰能力强完善的错误检测机制(5种错误检测方法)错误帧重传机制CRC校验确保数据完整性
1.2.3 灵活性好
节点可随时加入或退出总线多主机结构,任何节点都可发起通信支持点对点、一点对多点、全局广播通信
1.2.4 成本低
简化布线,减少线束重量和成本标准化接口降低开发成本易于维护和扩展
2. CAN总线物理层深度解析
2.1 物理层拓扑结构
CAN总线采用线性总线型拓扑结构,这是其设计的核心特点:
终端电阻120Ω 终端电阻120Ω
┃ ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ CAN_H (高速线) ┃
┃ ┃
╋───────╋───────╋───────╋───────╋──────╋ ╋
┃ ┃ ┃ ┃ ┃ ┃ ┃
节点1 节点2 节点3 节点4 节点5 节点N 节点终端
┃ ┃ ┃ ┃ ┃ ┃ ┃
╋───────╋───────╋───────╋───────╋──────╋ ╋
┃ ┃
┃ CAN_L (低速线) ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ ┃
2.2 差分信号传输原理
CAN总线使用差分信号传输,由CAN_H和CAN_L两根信号线组成。差分信号的核心原理是:
2.2.1 显性电平与隐性电平
CAN总线定义了两种逻辑电平状态:
显性电平(Dominant)- 逻辑”0″
CAN_H ≈ 3.5VCAN_L ≈ 1.5V差分电压 Vdiff = CAN_H – CAN_L ≈ 2V
隐性电平(Recessive)- 逻辑”1″
CAN_H ≈ 2.5VCAN_L ≈ 2.5V差分电压 Vdiff = CAN_H – CAN_L ≈ 0V
关键特性:显性电平具有优先权,当总线上同时出现显性和隐性电平时,总线状态为显性。这是CAN总线仲裁机制的物理基础。
时间轴 ───────────────────────────────────────→
CAN_H: 2.5V ────┐ ┌──── 2.5V
│ 3.5V │
└─────────┘
CAN_L: 2.5V ────┐ ┌──── 2.5V
│ 1.5V │
└─────────┘
差分: 0V ────┐ ┌──── 0V
│ 2.0V │
└─────────┘
逻辑: 1 ────┐ 0 ┌──── 1
└─────────┘
(隐性) (显性) (隐性)
2.2.2 差分传输的抗干扰原理
差分信号的抗干扰能力来源于共模抑制:
共模干扰抑制: 外部干扰信号会同时作用在CAN_H和CAN_L上,产生相同的电压变化(共模干扰)差分接收: 接收端只关注两根线的电压差,共模干扰被自动抵消数学表达:
接收信号 = (CAN_H + 干扰) - (CAN_L + 干扰) = CAN_H - CAN_L
2.3 终端电阻的作用机制
终端电阻是CAN总线正常工作的关键元件,标准值为120Ω。
2.3.1 阻抗匹配原理
CAN总线的特性阻抗约为120Ω,根据传输线理论:
特性阻抗 Z₀ = √(L/C)
其中:
L: 单位长度电感C: 单位长度电容
当传输线末端接入等于特性阻抗的电阻时,信号能量被完全吸收,不会产生反射。
2.3.2 信号反射问题
如果没有正确的终端电阻,会产生信号反射:
入射波振幅: Vi
反射波振幅: Vr = Vi × (ZL - Z₀)/(ZL + Z₀)
反射系数 Γ = (ZL - Z₀)/(ZL + Z₀)
当 ZL = Z₀ = 120Ω 时:
Γ = 0 (无反射)
当 ZL = ∞ (开路)时:
Γ = 1 (全反射)
当 ZL = 0 (短路)时:
Γ = -1 (反相全反射)
2.4 物理层电气特性
2.4.1 电压规范(ISO 11898标准)
| 参数 | 最小值 | 典型值 | 最大值 | 单位 |
|---|---|---|---|---|
| 显性差分电压 | 1.5 | 2.0 | 3.0 | V |
| 隐性差分电压 | -0.5 | 0 | 0.5 | V |
| CAN_H显性 | 2.75 | 3.5 | 4.5 | V |
| CAN_L显性 | 0.5 | 1.5 | 2.25 | V |
| 共模电压 | 2.0 | 2.5 | 3.0 | V |
2.4.2 传输距离与速率关系
根据ISO 11898-2标准,CAN总线的传输距离与速率存在反比关系:
| 波特率 | 最大传输距离 | 应用场景 |
|---|---|---|
| 1 Mbps | 40 m | 发动机控制、安全系统 |
| 500 kbps | 100 m | 车身控制 |
| 250 kbps | 250 m | 仪表、诊断 |
| 125 kbps | 500 m | 多媒体系统 |
| 50 kbps | 1000 m | 低速车身控制 |
| 10 kbps | 5000 m | 工业长距离通信 |
限制因素分析:
信号衰减: 随距离增加,信号幅度衰减传播延迟: 信号在电缆中的传播速度约为光速的60-70%位时序要求: CAN协议要求所有节点在位时间内完成采样,传播延迟不能超过位时间的一定比例
传播延迟计算:
td = L / v
其中:
L: 总线长度 (m)
v: 信号传播速度 ≈ 2×10⁸ m/s
对于1Mbps (位时间 = 1μs):
最大往返延迟 ≤ 位时间 × 40% = 0.4μs
最大距离 L ≤ 0.4μs × 2×10⁸ m/s / 2 = 40m
3. CAN总线数据链路层协议
3.1 OSI模型中的CAN协议
CAN协议主要定义了OSI模型的数据链路层和物理层:
┌─────────────────────────────────────────┐
│ 应用层 (Application Layer) │
│ - CANopen, DeviceNet, J1939等 │
├─────────────────────────────────────────┤
│ 表示层 (Presentation Layer) │
│ - 数据编码/解码 │
├─────────────────────────────────────────┤
│ 会话层 (Session Layer) │
│ - 会话管理 │
├─────────────────────────────────────────┤
│ 传输层 (Transport Layer) │
│ - TP协议 (ISO 15765-2) │
├─────────────────────────────────────────┤
│ 网络层 (Network Layer) │
│ - 路由、寻址 │
├═════════════════════════════════════════┤ ┐
│ 数据链路层 (Data Link Layer) │ │
│ ┌───────────────────────────────────┐ │ │
│ │ LLC: 逻辑链路控制 │ │ │
│ │ - 过滤、溢出处理 │ │ │ CAN
│ ├───────────────────────────────────┤ │ │ 协议
│ │ MAC: 媒体访问控制 │ │ │ 定义
│ │ - 帧封装、仲裁、错误检测 │ │ │ 范围
│ └───────────────────────────────────┘ │ │
├═════════════════════════════════════════┤ │
│ 物理层 (Physical Layer) │ │
│ - 位编码、位时序、同步 │ │
└─────────────────────────────────────────┘ ┘
3.2 CAN协议的核心特性
3.2.1 多主机访问控制(CSMA/CD+AMP)
CAN采用带优先级的CSMA/CD(Carrier Sense Multiple Access with Collision Detection and Arbitration on Message Priority)机制:
载波侦听(CS): 节点发送前先监听总线是否空闲多路访问(MA): 多个节点共享同一总线碰撞检测(CD): 检测到冲突时通过仲裁决定优先级基于消息优先级的仲裁(AMP): 优先级高的消息获得总线访问权
3.2.2 非破坏性仲裁
CAN的仲裁过程是非破坏性的,这意味着:
仲裁失败的节点不会破坏总线上的数据优先级高的消息能够无延迟地继续发送仲裁失败的节点自动退出并重试
这与以太网的CSMA/CD形成对比:
以太网: 碰撞时所有节点停止发送,等待随机延迟后重试(破坏性)CAN: 碰撞时低优先级节点退出,高优先级节点继续(非破坏性)
4. CAN报文格式详解
4.1 CAN 2.0A标准帧(11位标识符)
标准CAN帧由以下部分组成:
帧起始 仲裁段 控制段 数据段 CRC段 应答段 帧结束
SOF │ │ │ │ │ EOF
│ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼
┌───┬─────────┬──────┬──────────┬─────────┬─────┬──────┐
│ S │Identifier│ RTR │ Data │ CRC │ ACK │ EOF │
│ O │ 11 bit │ IDE │ 0-8 byte│ 15 bit │Slot │7 bit │
│ F │ │ R0 │ │Delimiter│Delim│ │
└───┴─────────┴──────┴──────────┴─────────┴─────┴──────┘
1 11+1 4+2 0-64 15+1 1+1 7 bits
详细字段说明:
4.1.1 帧起始(SOF – Start of Frame)
长度: 1位(显性位)作用:
标志帧的开始用于总线同步只能在总线空闲时发送
4.1.2 仲裁段(Arbitration Field)
标识符(Identifier): 11位
定义消息优先级(数值越小,优先级越高)定义消息内容(功能码)用于接收过滤
远程传输请求(RTR): 1位
RTR = 0: 数据帧RTR = 1: 远程帧(请求数据)
示例标识符分配:
汽车CAN网络标识符示例:
┌────────────┬──────────────┬──────────────┐
│ 标识符(Hex)│ 优先级 │ 功能描述 │
├────────────┼──────────────┼──────────────┤
│ 0x000 │ 最高优先级 │ 紧急停止 │
│ 0x010 │ 高优先级 │ 节气门位置 │
│ 0x018 │ 高优先级 │ 刹车踏板状态 │
│ 0x100 │ 中等优先级 │ 车速信息 │
│ 0x200 │ 中等优先级 │ 转速信息 │
│ 0x300 │ 较低优先级 │ 油温信息 │
│ 0x700 │ 低优先级 │ 环境温度 │
│ 0x7FF │ 最低优先级 │ 诊断信息 │
└────────────┴──────────────┴──────────────┘
4.1.3 控制段(Control Field)
标识符扩展位(IDE): 1位
IDE = 0: 标准帧(11位标识符)IDE = 1: 扩展帧(29位标识符)
保留位(R0): 1位
必须发送为显性位接收器不关心其值
数据长度码(DLC): 4位
指示数据段的字节数(0-8)编码规则:
DLC值 数据字节数
0000 0
0001 1
... ...
1000 8
1001-1111 非法(保留)
4.1.4 数据段(Data Field)
长度: 0-8字节(0-64位)包含: 实际传输的数据字节顺序: 大端序(MSB first)
4.1.5 CRC段(CRC Field)
CRC序列: 15位
使用CRC-15多项式:x¹⁵ + x¹⁴ + x¹⁰ + x⁸ + x⁷ + x⁴ + x³ + 1对SOF到数据段的所有位进行校验不包括位填充位
CRC界定符: 1位(隐性位)
分隔CRC序列和ACK段
4.1.6 应答段(ACK Field)
ACK时隙: 1位
发送节点发送隐性位正确接收的节点发送显性位覆盖确认机制
ACK界定符: 1位(隐性位)
分隔ACK时隙和EOF
4.1.7 帧结束(EOF – End of Frame)
长度: 7位隐性位作用: 标志帧的结束
4.2 CAN 2.0B扩展帧(29位标识符)
扩展帧格式:
┌───┬─────────┬───┬───┬──────────────────┬───┬──────┬──────────┬─────────┬─────┬──────┐
│SOF│ ID-A │SRR│IDE│ ID-B │RTR│ Ctrl │ Data │ CRC │ ACK │ EOF │
│ 1 │ 11 bit │ 1 │ 1 │ 18 bit │ 1 │ 6bit │ 0-8byte │ 15bit+1 │ 2bit│ 7bit │
└───┴─────────┴───┴───┴──────────────────┴───┴──────┴──────────┴─────────┴─────┴──────┘
└─────────────┬─────────────────────────┘
├─ 基本标识符段 (Base ID)
└─ 扩展标识符段 (Extended ID)
完整标识符 = ID-A (11bit) + ID-B (18bit) = 29 bit
扩展帧特殊字段:
SRR(Substitute Remote Request): 1位隐性位,代替RTRIDE: 1位隐性位,标识扩展帧ID-B: 18位扩展标识符R1, R0: 2位保留位
4.3 位填充机制(Bit Stuffing)
CAN协议使用位填充来保证足够的边沿跳变用于同步。
4.3.1 位填充规则
填充规则:
在发送SOF到CRC序列之间连续5个相同极性的位后,插入1个相反极性的填充位
示例:
原始数据: 0 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 0
↓插入 ↓插入 ↓插入
填充后: 0 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 0 1 0
4.3.2 位填充的作用
保证同步: 确保有足够的边沿跳变检测错误: 违反位填充规则视为位填充错误防止DC漂移: 避免长时间保持同一电平
4.3.3 位去填充
接收节点自动删除填充位:
检测到5个相同位后的第6位是填充位删除后恢复原始数据如果填充位不符合规则,报告位填充错误
5. 位时序与同步机制
5.1 位时间分段
CAN总线的一个位时间被分为4个时间段:
一个位时间 (Nominal Bit Time)
┌──────────────────────────────────────────────────────────────┐
│ │
├────────┬──────────────────┬─────────────┬─────────────────────┤
│ SS │ PTS │ PBS1 │ PBS2 │
│ 同步段 │ 传播时间段 │ 相位段1 │ 相位段2 │
└────────┴──────────────────┴─────────────┴─────────────────────┘
↑ ↑采样点
边沿 (Sample Point)
各段功能:
- SS (Sync Segment): 1 TQ, 用于同步
- PTS (Propagation Time Segment): 1-8 TQ, 补偿物理延迟
- PBS1 (Phase Buffer Segment 1): 1-8 TQ, 可动态调整
- PBS2 (Phase Buffer Segment 2): 2-8 TQ, 可动态调整
TQ (Time Quantum): 时间量子,位时间的基本单位
5.2 位时序计算
5.2.1 基本公式
位时间 = SS + PTS + PBS1 + PBS2
= 1 + PTS + PBS1 + PBS2 (单位: TQ)
时间量子 TQ = (BRP + 1) / fCAN
波特率 = 1 / 位时间 = fCAN / ((BRP + 1) × (1 + PTS + PBS1 + PBS2))
其中:
- BRP: 波特率预分频器 (Baud Rate Prescaler)
- fCAN: CAN控制器时钟频率
5.2.2 位时序配置示例
示例1: 配置500kbps @ 36MHz CAN时钟
目标波特率: 500 kbps
CAN时钟: 36 MHz
位时间 = 1 / 500kbps = 2μs
TQ数量选择: 18 TQ (便于配置)
TQ = 2μs / 18 = 0.111μs
BRP = (fCAN × TQ) - 1 = (36MHz × 0.111μs) - 1 = 3
时间段分配:
SS = 1 TQ
PTS = 4 TQ (补偿传播延迟)
PBS1 = 9 TQ
PBS2 = 4 TQ
总计 = 18 TQ
采样点位置 = (SS + PTS + PBS1) / 总TQ × 100%
= (1 + 4 + 9) / 18 × 100%
= 77.8%
示例2: 配置1Mbps @ 48MHz CAN时钟
目标波特率: 1 Mbps
CAN时钟: 48 MHz
位时间 = 1 / 1Mbps = 1μs
TQ数量选择: 16 TQ
TQ = 1μs / 16 = 0.0625μs
BRP = (48MHz × 0.0625μs) - 1 = 2
时间段分配:
SS = 1 TQ
PTS = 3 TQ
PBS1 = 8 TQ
PBS2 = 4 TQ
总计 = 16 TQ
采样点位置 = 75%
5.3 硬同步与重同步
5.3.1 硬同步(Hard Synchronization)
发生在帧起始的SOF位:
检测到隐性→显性的跳变重启位时间所有节点同步到SOF边沿
5.3.2 重同步(Resynchronization)
在帧传输过程中持续进行:
相位误差 e = 检测边沿位置 - 预期边沿位置
重同步规则:
┌──────────────────────────────────────────┐
│ e < 0 (边沿提前) │
│ PBS1 延长 min(|e|, SJW) │
├──────────────────────────────────────────┤
│ e > 0 (边沿滞后) │
│ PBS2 缩短 min(e, SJW) │
└──────────────────────────────────────────┘
SJW (Synchronization Jump Width): 1-4 TQ
最大调整量,用于限制单次重同步的幅度
5.4 采样点配置建议
根据ISO 11898标准和实践经验:
| 波特率 | 推荐采样点位置 | 原因 |
|---|---|---|
| ≥ 800 kbps | 75-80% | 短距离,传播延迟小 |
| 500 kbps | 75-87.5% | 标准汽车网络 |
| 250 kbps | 85-87.5% | 较长距离 |
| 125 kbps | 87.5% | 长距离网络 |
| ≤ 100 kbps | 87.5-90% | 最长距离 |
配置原则:
采样点尽量靠后(提高容错性)PBS2 ≥ SJW(保证重同步有效)PBS2 ≥ IPT(信息处理时间)
6. 仲裁机制与优先级
6.1 非破坏性仲裁原理
CAN的仲裁机制是其最重要的特性之一,基于线与逻辑:
节点A发送: 0 (显性) ─┐
├─→ 总线状态: 0 (显性)
节点B发送: 1 (隐性) ─┘
规则: 显性 AND 隐性 = 显性
显性具有优先权
6.2 仲裁过程详解
假设三个节点同时开始发送:
时间轴 →
标识符位: b10 b9 b8 b7 b6 b5 b4 b3 b2 b1 b0
──┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬──
节点A ID: 0 1 1 0 0 1 0 1 1 0 0
──┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴──
──┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬──
节点B ID: 0 1 1 0 1 0 0 0 1 0 0
──┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴──
──┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬──
节点C ID: 0 1 1 0 0 0 1 1 0 1 0
──┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴──
──┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬──
总线状态: 0 1 1 0 0 0 X X X X X
──┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴──
↑ ↑
节点A 节点B
节点C 失败
继续
仲裁结果:
- 节点C (ID最小) 赢得仲裁,继续发送
- 节点A、B 仲裁失败,切换到接收模式
- 节点A、B 在下一帧间隙重新尝试发送
6.3 优先级设计策略
在汽车CAN网络中,合理的优先级设计至关重要:
6.3.1 功能分层优先级
┌─────────────────────────────────────────────────┐
│ 第0层: 安全关键系统 (0x000-0x0FF) │
│ - 紧急制动请求 │
│ - 碰撞预警 │
│ - 安全气囊触发 │
├─────────────────────────────────────────────────┤
│ 第1层: 动力系统 (0x100-0x1FF) │
│ - 发动机控制 │
│ - 变速箱控制 │
│ - 节气门位置 │
├─────────────────────────────────────────────────┤
│ 第2层: 底盘控制 (0x200-0x2FF) │
│ - ABS控制 │
│ - ESP控制 │
│ - 转向角度 │
├─────────────────────────────────────────────────┤
│ 第3层: 车身控制 (0x300-0x4FF) │
│ - 车门状态 │
│ - 灯光控制 │
│ - 空调控制 │
├─────────────────────────────────────────────────┤
│ 第4层: 信息娱乐 (0x500-0x6FF) │
│ - 音响控制 │
│ - 导航信息 │
│ - 蓝牙通信 │
├─────────────────────────────────────────────────┤
│ 第5层: 诊断和监控 (0x700-0x7FF) │
│ - 故障诊断 │
│ - 系统状态 │
│ - 日志记录 │
└─────────────────────────────────────────────────┘
6.3.2 消息周期与优先级关系
高优先级 + 高频率消息: 安全关键信号
└─→ 如: 制动踏板状态 (10ms周期, ID:0x010)
高优先级 + 低频率消息: 紧急事件
└─→ 如: 碰撞检测 (事件触发, ID:0x001)
低优先级 + 高频率消息: 一般状态信息
└─→ 如: 环境温度 (100ms周期, ID:0x700)
低优先级 + 低频率消息: 诊断信息
└─→ 如: 软件版本查询 (按需, ID:0x7F0)
6.4 总线负载计算
总线负载是评估CAN网络性能的重要指标:
总线负载 = Σ(消息i的位数 × 消息i的频率) / 总线波特率 × 100%
单个消息的位数计算:
标准数据帧 = 1(SOF) + 11(ID) + 1(RTR) + 6(Ctrl) + 64(Data)
+ 15(CRC) + 1(CRC Delim) + 2(ACK) + 7(EOF) + 3(IFS)
+ 填充位开销 (约20%)
≈ 130位 (8字节数据)
示例计算:
消息A: ID=0x100, 8字节, 10ms周期
消息B: ID=0x200, 4字节, 20ms周期
消息C: ID=0x300, 2字节, 50ms周期
假设波特率 500kbps:
消息A位数 ≈ 130位
消息B位数 ≈ 90位
消息C位数 ≈ 70位
负载 = (130×100 + 90×50 + 70×20) / 500000 × 100%
= (13000 + 4500 + 1400) / 500000 × 100%
= 18900 / 500000 × 100%
= 3.78%
建议: 总线负载 < 30% (正常), < 60% (可接受), > 80% (危险)
7. 错误检测与处理
CAN协议具有极强的错误检测能力,使用5种错误检测机制。
7.1 五种错误检测机制
7.1.1 位监视(Bit Monitoring)
发送节点监视总线电平:
发送位 ≠ 总线位 → 位错误
例外: 仲裁段和ACK段允许不同
(这是正常的仲裁和应答过程)
7.1.2 位填充规则检测
接收节点检测位填充违规:
连续6个相同位 (应该在第6位插入填充位) → 位填充错误
正确: 1 1 1 1 1 0 (5个1后插入0)
错误: 1 1 1 1 1 1 (6个连续1)
7.1.3 帧格式检查
检查固定位格式:
以下位必须是隐性:
- CRC界定符
- ACK界定符
- EOF (7位隐性)
如果这些位置出现显性位 → 格式错误
7.1.4 应答检查
发送节点检查ACK时隙:
发送节点在ACK时隙发送隐性位
如果保持隐性 (没有节点应答) → ACK错误
正常: ACK时隙被接收节点置为显性
7.1.5 CRC校验
15位CRC校验:
生成多项式: x¹⁵ + x¹⁴ + x¹⁰ + x⁸ + x⁷ + x⁴ + x³ + 1
计算范围: SOF到数据段结束 (不含填充位)
接收CRC ≠ 计算CRC → CRC错误
7.2 错误帧(Error Frame)
当节点检测到错误时,发送错误帧破坏当前传输:
错误帧结构:
┌──────────────────┬──────────────────────────┐
│ 错误标志 │ 错误界定符 │
│ (Error Flag) │ (Error Delimiter) │
│ 6 位 │ 8 位隐性 │
└──────────────────┴──────────────────────────┘
主动错误标志: 6个连续显性位 (违反位填充规则)
被动错误标志: 6个连续隐性位
错误帧的作用:
通知所有节点当前帧无效触发重传机制更新错误计数器
7.3 错误计数与状态机
每个CAN节点维护两个错误计数器:
┌─────────────────────────────────────────────┐
│ TEC: 发送错误计数器 (Transmit Error Counter) │
│ REC: 接收错误计数器 (Receive Error Counter) │
└─────────────────────────────────────────────┘
错误计数规则:
┌──────────────────────────────────────────────┐
│ 事件 │ TEC │ REC │
├─────────────────────────┼──────┼───────────┤
│ 发送错误 │ +8 │ - │
│ 接收错误 │ - │ +1 │
│ 成功发送后 │ -1 │ - │
│ 成功接收后 │ - │ -1(*0.5) │
│ 发送位错误 (仲裁段) │ +8 │ - │
│ 发送主动错误帧 │ +8 │ - │
└──────────────────────────┴──────┴──────────┘
7.4 节点状态机
┌──────────────────────────┐
│ Error Active │
│ (主动错误状态) │
│ TEC ≤ 127 │
│ REC ≤ 127 │
└──────────────────────────┘
↑ ↓
│ │
TEC或REC │ │ TEC > 127
降到127 │ │ 或REC > 127
│ │
│ ↓
┌──────────────────────────┐
│ Error Passive │
│ (被动错误状态) │
│ 128 ≤ TEC ≤ 255 │
│ 或 128 ≤ REC ≤ 255 │
└──────────────────────────┘
↓
│
TEC > 255 │
│
↓
┌──────────────────────────┐
│ Bus Off │
│ (总线关闭状态) │
│ TEC > 255 │
│ 节点与总线断开 │
└──────────────────────────┘
↓
│
128次11个隐性位
│
↓
┌──────────────────────────┐
│ 恢复到Error Active │
│ TEC = 0, REC = 0 │
└──────────────────────────┘
状态说明:
Error Active(主动错误):
正常工作状态检测到错误时发送主动错误帧(6个显性位)可以正常参与通信
Error Passive(被动错误):
错误较多的节点检测到错误时发送被动错误帧(6个隐性位)影响通信但不会破坏总线
Bus Off(总线关闭):
严重错误节点完全断开与总线的连接需要复位才能重新加入
8. CAN总线硬件实现方案
8.1 典型硬件架构
┌─────────────────────────────────────────────────────────┐
│ 微控制器 │
│ ┌──────────────────────────────────────────────┐ │
│ │ 应用层 │ │
│ ├──────────────────────────────────────────────┤ │
│ │ CAN驱动层 │ │
│ ├──────────────────────────────────────────────┤ │
│ │ CAN控制器 (内置或外置) │ │
│ │ - 消息缓冲区 │ │
│ │ - 验收滤波器 │ │
│ │ - 位时序配置 │ │
│ │ - 错误处理 │ │
│ └──────────────────────────────────────────────┘ │
│ │ CAN_TX CAN_RX │ │
└─────────┼──────────────────────────────┼───────────────┘
│ │
↓ ↑
┌──────────────────────────────────────────┐
│ CAN收发器 (Transceiver) │
│ 如: TJA1050, MCP2551 │
│ ┌────────────────────────────────────┐ │
│ │ 差分驱动器 ←→ 差分接收器 │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
│ CAN_H CAN_L │
└───────┬───────────┬─────┘
│ │
╔═══╪═══════════╪═══╗
║ │ 120Ω │ ║ CAN总线
╚═══╪═══════════╪═══╝
│ │
8.2 主流CAN控制器
8.2.1 STM32系列(内置CAN控制器)
STM32F4xx系列特性:
集成bxCAN控制器(Basic Extended CAN)双CAN通道(CAN1、CAN2)3个发送邮箱2个接收FIFO(每个3层深)28个可配置滤波器支持CAN 2.0A和2.0B
寄存器概览:
// CAN主控制寄存器 (CAN_MCR)
#define CAN_MCR_INRQ 0x0001 // 初始化请求
#define CAN_MCR_SLEEP 0x0002 // 睡眠模式请求
#define CAN_MCR_TXFP 0x0004 // 发送FIFO优先级
#define CAN_MCR_ABOM 0x0040 // 自动离线管理
#define CAN_MCR_AWUM 0x0080 // 自动唤醒模式
// CAN位时序寄存器 (CAN_BTR)
#define CAN_BTR_BRP 0x000003FF // 波特率预分频器
#define CAN_BTR_TS1 0x000F0000 // 时间段1
#define CAN_BTR_TS2 0x00700000 // 时间段2
#define CAN_BTR_SJW 0x03000000 // 重同步跳跃宽度
8.2.2 MCP2515(独立CAN控制器)
特性:
SPI接口连接微控制器支持CAN 2.0B工作速率高达1Mbps3个发送缓冲区2个接收缓冲区6个验收滤波器
典型连接:
MCU MCP2515 TJA1050
┌────┐ ┌────────┐ ┌────────┐
│ SCK├─────────→│SCK │ │ │
│MOSI├─────────→│SI │ │ │
│MISO│←─────────┤SO TX├───────→│TXD │
│ CS ├─────────→│CS RX│←───────┤RXD │
│INT │←─────────┤INT │ │ │
└────┘ └────────┘ │ CANH ├──→ CAN_H
│ CANL ├──→ CAN_L
└────────┘
8.3 CAN收发器选型
8.3.1 常用收发器对比
| 型号 | 速率 | 节点数 | 工作电压 | 特殊功能 | 应用场景 |
|---|---|---|---|---|---|
| TJA1050 | 1Mbps | 110 | 4.75-5.25V | 标准 | 通用汽车CAN |
| TJA1051 | 1Mbps | 110 | 4.5-5.5V | 待机模式 | 低功耗应用 |
| TJA1042 | 5Mbps | 110 | 4.75-5.25V | CAN FD | 高速CAN FD |
| MCP2551 | 1Mbps | 112 | 4.5-5.5V | 斜率控制 | 工业CAN |
| SN65HVD230 | 1Mbps | 120 | 3.3V | 低压 | 3.3V系统 |
8.3.2 TJA1050内部结构
VCC
│
┌───────┴───────┐
│ │
TXD ├──→ 发送器 ────┤
│ ├──→ CANH
│ 差分驱动 │
│ ├──→ CANL
RXD │←── 接收器 ────┤
│ │
│ 温度保护 │
│ 短路保护 │
└───────┬───────┘
│
GND
8.4 电路设计要点
8.4.1 标准CAN节点电路
VCC(5V)
│
├── 0.1μF (去耦电容)
│
┌─────────┴─────────┐
│ TJA1050 │
│ │
│ 1.TXD ←────── TX ├── MCU
│ 2.GND ───┬────── GND
│ 3.VCC ───┘ │
│ 4.RXD ────────→ RX ├── MCU
│ 5.VREF (NC) │
│ 6.CANL ───┬──────┤
│ 7.CANH ───┼──────┤
│ 8.S (GND) │ │
└────────────┘ │
│ │ │
│ └──────┴── 120Ω (终端电阻,仅两端节点)
│ │
═╪═══════════╪═ CAN总线
CANH CANL
8.4.2 EMC防护设计
完整的EMC防护电路:
CAN_H ─────┬──── TVS1 ────┬──── 共模电感 ───┬──── 120Ω ───┬──── CANH (总线)
│ │ │ │
30pF GND ═══ ═══
│ │
CAN_L ─────┴──── TVS2 ────┴──── 共模电感 ───┴──── 0Ω ────┴──── CANL (总线)
保护元件说明:
1. TVS管 (瞬态抑制二极管): 防止过压冲击
2. 共模电感: 抑制共模干扰
3. 差模电容 (30pF): 滤除高频干扰
4. 终端电阻 (120Ω): 仅总线两端需要
8.4.3 接地与布线
PCB布线原则:
差分信号处理
CAN_H和CAN_L等长布线保持固定间距(0.3-0.5mm)差分阻抗控制在120Ω±10%
接地设计
收发器单点接地避免地回路数字地和模拟地分离
走线规范
避免锐角和直角远离高速信号线屏蔽层接地
9. 完整代码实现
9.1 STM32 HAL库实现
9.1.1 CAN初始化代码
/**
* @file can_driver.c
* @brief STM32 CAN驱动完整实现
* @author CSDN技术博客
* @date 2024
*/
#include "stm32f4xx_hal.h"
#include <string.h>
/* CAN句柄 */
CAN_HandleTypeDef hcan1;
/* CAN消息结构体 */
typedef struct {
uint32_t id; // CAN标识符
uint8_t data[8]; // 数据
uint8_t len; // 数据长度
uint8_t format; // 0=标准帧, 1=扩展帧
uint8_t type; // 0=数据帧, 1=远程帧
} CAN_Message_t;
/* 接收缓冲区 */
#define RX_BUFFER_SIZE 10
CAN_Message_t rxBuffer[RX_BUFFER_SIZE];
volatile uint8_t rxWriteIndex = 0;
volatile uint8_t rxReadIndex = 0;
/**
* @brief CAN初始化函数
* @param None
* @retval HAL状态
*/
HAL_StatusTypeDef CAN_Init(void)
{
HAL_StatusTypeDef status;
/* CAN外设时钟配置 */
__HAL_RCC_CAN1_CLK_ENABLE();
/* CAN基本配置 */
hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 4; // 时钟预分频器
hcan1.Init.Mode = CAN_MODE_NORMAL; // 正常模式
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; // 重同步跳跃宽度
hcan1.Init.TimeSeg1 = CAN_BS1_13TQ; // 时间段1
hcan1.Init.TimeSeg2 = CAN_BS2_2TQ; // 时间段2
hcan1.Init.TimeTriggeredMode = DISABLE; // 禁用时间触发模式
hcan1.Init.AutoBusOff = ENABLE; // 自动离线管理
hcan1.Init.AutoWakeUp = ENABLE; // 自动唤醒
hcan1.Init.AutoRetransmission = ENABLE; // 自动重传
hcan1.Init.ReceiveFifoLocked = DISABLE; // 接收FIFO不锁定
hcan1.Init.TransmitFifoPriority = DISABLE; // 发送FIFO优先级
/* 计算波特率
* CAN时钟 = APB1时钟 / Prescaler = 42MHz / 4 = 10.5MHz
* 位时间 = 1 + TimeSeg1 + TimeSeg2 = 1 + 13 + 2 = 16 TQ
* 波特率 = CAN时钟 / 位时间 = 10.5MHz / 16 = 656.25kbps
* 采样点 = (1 + TimeSeg1) / 16 = 87.5%
*/
status = HAL_CAN_Init(&hcan1);
if (status != HAL_OK) {
return status;
}
/* 配置接收滤波器 */
CAN_FilterTypeDef canFilter;
// 滤波器0: 接收所有标准帧
canFilter.FilterBank = 0;
canFilter.FilterMode = CAN_FILTERMODE_IDMASK; // 掩码模式
canFilter.FilterScale = CAN_FILTERSCALE_32BIT; // 32位
canFilter.FilterIdHigh = 0x0000; // 标识符
canFilter.FilterIdLow = 0x0000;
canFilter.FilterMaskIdHigh = 0x0000; // 掩码(0=不关心)
canFilter.FilterMaskIdLow = 0x0000;
canFilter.FilterFIFOAssignment = CAN_RX_FIFO0; // 分配到FIFO0
canFilter.FilterActivation = ENABLE;
canFilter.SlaveStartFilterBank = 14;
status = HAL_CAN_ConfigFilter(&hcan1, &canFilter);
if (status != HAL_OK) {
return status;
}
// 滤波器1: 接收特定ID(示例:0x100-0x10F)
canFilter.FilterBank = 1;
canFilter.FilterIdHigh = 0x100 << 5; // ID左移5位
canFilter.FilterIdLow = 0x0000;
canFilter.FilterMaskIdHigh = 0x7F0 << 5; // 掩码0x7F0
canFilter.FilterMaskIdLow = 0x0000;
canFilter.FilterFIFOAssignment = CAN_RX_FIFO1; // 分配到FIFO1
canFilter.FilterActivation = ENABLE;
status = HAL_CAN_ConfigFilter(&hcan1, &canFilter);
if (status != HAL_OK) {
return status;
}
/* 启动CAN */
status = HAL_CAN_Start(&hcan1);
if (status != HAL_OK) {
return status;
}
/* 使能FIFO接收中断 */
HAL_CAN_ActivateNotification(&hcan1,
CAN_IT_RX_FIFO0_MSG_PENDING |
CAN_IT_RX_FIFO1_MSG_PENDING |
CAN_IT_ERROR |
CAN_IT_BUSOFF);
return HAL_OK;
}
/**
* @brief CAN GPIO配置
* @param hcan: CAN句柄
* @retval None
*/
void HAL_CAN_MspInit(CAN_HandleTypeDef* hcan)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(hcan->Instance == CAN1)
{
/* 使能GPIO时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/* CAN1 GPIO配置
* PA11 ------> CAN1_RX
* PA12 ------> CAN1_TX
*/
GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_CAN1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* CAN1中断配置 */
HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);
HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn);
HAL_NVIC_SetPriority(CAN1_SCE_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(CAN1_SCE_IRQn);
}
}
/**
* @brief CAN发送函数
* @param msg: 要发送的消息
* @retval HAL状态
*/
HAL_StatusTypeDef CAN_Transmit(CAN_Message_t* msg)
{
CAN_TxHeaderTypeDef txHeader;
uint32_t txMailbox;
/* 配置发送头 */
if (msg->format == 0) {
// 标准帧
txHeader.StdId = msg->id;
txHeader.IDE = CAN_ID_STD;
} else {
// 扩展帧
txHeader.ExtId = msg->id;
txHeader.IDE = CAN_ID_EXT;
}
txHeader.RTR = (msg->type == 0) ? CAN_RTR_DATA : CAN_RTR_REMOTE;
txHeader.DLC = msg->len;
txHeader.TransmitGlobalTime = DISABLE;
/* 发送消息 */
return HAL_CAN_AddTxMessage(&hcan1, &txHeader, msg->data, &txMailbox);
}
/**
* @brief CAN接收回调函数
* @param hcan: CAN句柄
* @retval None
*/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
CAN_RxHeaderTypeDef rxHeader;
/* 从FIFO0读取消息 */
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader,
rxBuffer[rxWriteIndex].data) == HAL_OK)
{
/* 保存消息信息 */
if (rxHeader.IDE == CAN_ID_STD) {
rxBuffer[rxWriteIndex].id = rxHeader.StdId;
rxBuffer[rxWriteIndex].format = 0;
} else {
rxBuffer[rxWriteIndex].id = rxHeader.ExtId;
rxBuffer[rxWriteIndex].format = 1;
}
rxBuffer[rxWriteIndex].len = rxHeader.DLC;
rxBuffer[rxWriteIndex].type = (rxHeader.RTR == CAN_RTR_DATA) ? 0 : 1;
/* 更新写指针 */
rxWriteIndex = (rxWriteIndex + 1) % RX_BUFFER_SIZE;
}
}
/**
* @brief CAN错误回调函数
* @param hcan: CAN句柄
* @retval None
*/
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan)
{
uint32_t errorCode = HAL_CAN_GetError(hcan);
if (errorCode & HAL_CAN_ERROR_EWG) {
// 错误警告
}
if (errorCode & HAL_CAN_ERROR_EPV) {
// 被动错误
}
if (errorCode & HAL_CAN_ERROR_BOF) {
// 总线关闭
// 需要复位CAN控制器
HAL_CAN_ResetError(hcan);
}
}
/**
* @brief 从接收缓冲区读取消息
* @param msg: 消息存储指针
* @retval 1=成功, 0=无消息
*/
uint8_t CAN_Receive(CAN_Message_t* msg)
{
if (rxReadIndex == rxWriteIndex) {
return 0; // 缓冲区空
}
/* 复制消息 */
memcpy(msg, &rxBuffer[rxReadIndex], sizeof(CAN_Message_t));
/* 更新读指针 */
rxReadIndex = (rxReadIndex + 1) % RX_BUFFER_SIZE;
return 1;
}
/**
* @brief 获取CAN错误状态
* @param None
* @retval 错误代码
*/
uint32_t CAN_GetErrorStatus(void)
{
uint32_t esr = hcan1.Instance->ESR;
uint8_t tec = (esr >> 16) & 0xFF; // 发送错误计数
uint8_t rec = (esr >> 24) & 0xFF; // 接收错误计数
printf("TEC: %d, REC: %d
", tec, rec);
return esr;
}
9.1.2 应用层使用示例
/**
* @file can_app.c
* @brief CAN应用层示例
*/
#include "can_driver.h"
#include <stdio.h>
/* 车速消息发送 */
void SendVehicleSpeed(uint16_t speed_kph)
{
CAN_Message_t msg;
msg.id = 0x100; // 车速消息ID
msg.format = 0; // 标准帧
msg.type = 0; // 数据帧
msg.len = 2; // 2字节数据
// 车速数据 (0.1 km/h分辨率)
msg.data[0] = (speed_kph * 10) >> 8; // 高字节
msg.data[1] = (speed_kph * 10) & 0xFF; // 低字节
if (CAN_Transmit(&msg) == HAL_OK) {
printf("车速消息发送成功: %d km/h
", speed_kph);
}
}
/* 发动机转速消息发送 */
void SendEngineRPM(uint16_t rpm)
{
CAN_Message_t msg;
msg.id = 0x200;
msg.format = 0;
msg.type = 0;
msg.len = 2;
msg.data[0] = rpm >> 8;
msg.data[1] = rpm & 0xFF;
CAN_Transmit(&msg);
}
/* 多参数消息发送示例 */
void SendVehicleStatus(void)
{
CAN_Message_t msg;
msg.id = 0x300;
msg.format = 0;
msg.type = 0;
msg.len = 8;
// 字节0-1: 车速 (0.01 km/h)
uint16_t speed = 8550; // 85.50 km/h
msg.data[0] = speed >> 8;
msg.data[1] = speed & 0xFF;
// 字节2-3: 发动机转速 (rpm)
uint16_t rpm = 2500;
msg.data[2] = rpm >> 8;
msg.data[3] = rpm & 0xFF;
// 字节4: 油门踏板位置 (0-100%)
msg.data[4] = 45;
// 字节5: 刹车踏板位置 (0-100%)
msg.data[5] = 0;
// 字节6: 状态位
// bit0: 发动机状态 (0=关闭, 1=运行)
// bit1: 车门状态 (0=关闭, 1=打开)
// bit2: 灯光状态
msg.data[6] = 0x01; // 发动机运行
// 字节7: 齿轮位置
// 0=P, 1=R, 2=N, 3=D
msg.data[7] = 3; // D档
CAN_Transmit(&msg);
}
/* 消息接收处理 */
void ProcessCANMessages(void)
{
CAN_Message_t msg;
while (CAN_Receive(&msg)) {
switch (msg.id) {
case 0x100: // 车速消息
{
uint16_t speed = (msg.data[0] << 8) | msg.data[1];
printf("收到车速: %d.%d km/h
", speed/10, speed%10);
break;
}
case 0x200: // 转速消息
{
uint16_t rpm = (msg.data[0] << 8) | msg.data[1];
printf("收到转速: %d rpm
", rpm);
break;
}
case 0x300: // 综合状态
{
uint16_t speed = (msg.data[0] << 8) | msg.data[1];
uint16_t rpm = (msg.data[2] << 8) | msg.data[3];
uint8_t throttle = msg.data[4];
uint8_t brake = msg.data[5];
uint8_t status = msg.data[6];
uint8_t gear = msg.data[7];
printf("车辆状态:
");
printf(" 车速: %d.%02d km/h
", speed/100, speed%100);
printf(" 转速: %d rpm
", rpm);
printf(" 油门: %d%%
", throttle);
printf(" 刹车: %d%%
", brake);
printf(" 发动机: %s
", (status & 0x01) ? "运行" : "停止");
printf(" 档位: %c
", "PRND"[gear]);
break;
}
default:
printf("未知消息ID: 0x%X
", msg.id);
break;
}
}
}
/* 主函数示例 */
int main(void)
{
HAL_Init();
SystemClock_Config();
/* 初始化CAN */
if (CAN_Init() != HAL_OK) {
printf("CAN初始化失败!
");
while(1);
}
printf("CAN初始化成功
");
uint32_t lastSendTime = 0;
while (1) {
/* 每100ms发送一次消息 */
if (HAL_GetTick() - lastSendTime >= 100) {
lastSendTime = HAL_GetTick();
SendVehicleSpeed(85);
SendEngineRPM(2500);
SendVehicleStatus();
}
/* 处理接收消息 */
ProcessCANMessages();
/* 检查错误状态 */
CAN_GetErrorStatus();
HAL_Delay(10);
}
}
9.2 Arduino + MCP2515实现
/**
* @file mcp2515_can.ino
* @brief Arduino MCP2515 CAN库实现
* @note 需要安装 MCP2515库
*/
#include <SPI.h>
#include <mcp2515.h>
/* MCP2515对象 */
MCP2515 mcp2515(10); // CS引脚连接到D10
/* CAN消息结构体 */
struct can_frame {
uint32_t can_id;
uint8_t can_dlc;
uint8_t data[8];
};
/**
* @brief 初始化函数
*/
void setup() {
Serial.begin(115200);
SPI.begin();
/* 复位MCP2515 */
mcp2515.reset();
/* 设置波特率为500kbps */
mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ);
/* 设置为正常模式 */
mcp2515.setNormalMode();
/* 配置滤波器 - 接收所有消息 */
mcp2515.setFilterMask(MCP2515::MASK0, false, 0x00000000);
mcp2515.setFilterMask(MCP2515::MASK1, false, 0x00000000);
Serial.println("CAN初始化完成");
}
/**
* @brief 发送CAN消息
*/
void sendCANMessage(uint32_t id, uint8_t* data, uint8_t len) {
struct can_frame frame;
frame.can_id = id;
frame.can_dlc = len;
memcpy(frame.data, data, len);
if (mcp2515.sendMessage(&frame) == MCP2515::ERROR_OK) {
Serial.print("发送成功 ID: 0x");
Serial.println(id, HEX);
} else {
Serial.println("发送失败");
}
}
/**
* @brief 接收CAN消息
*/
void receiveCANMessage() {
struct can_frame frame;
if (mcp2515.readMessage(&frame) == MCP2515::ERROR_OK) {
Serial.print("收到消息 ID: 0x");
Serial.print(frame.can_id, HEX);
Serial.print(" 数据: ");
for (int i = 0; i < frame.can_dlc; i++) {
Serial.print(frame.data[i], HEX);
Serial.print(" ");
}
Serial.println();
// 解析特定消息
parseCANMessage(&frame);
}
}
/**
* @brief 解析CAN消息
*/
void parseCANMessage(struct can_frame* frame) {
switch (frame->can_id) {
case 0x100: // 车速消息
{
uint16_t speed = (frame->data[0] << 8) | frame->data[1];
float speed_kph = speed / 10.0;
Serial.print("车速: ");
Serial.print(speed_kph);
Serial.println(" km/h");
break;
}
case 0x200: // 转速消息
{
uint16_t rpm = (frame->data[0] << 8) | frame->data[1];
Serial.print("转速: ");
Serial.print(rpm);
Serial.println(" rpm");
break;
}
case 0x300: // 温度消息
{
int8_t temp = frame->data[0] - 40; // 偏移40度
Serial.print("温度: ");
Serial.print(temp);
Serial.println(" °C");
break;
}
}
}
/**
* @brief 发送示例消息
*/
void sendExampleMessages() {
// 发送车速消息
uint8_t speedData[2];
uint16_t speed = 855; // 85.5 km/h
speedData[0] = speed >> 8;
speedData[1] = speed & 0xFF;
sendCANMessage(0x100, speedData, 2);
delay(10);
// 发送转速消息
uint8_t rpmData[2];
uint16_t rpm = 2500;
rpmData[0] = rpm >> 8;
rpmData[1] = rpm & 0xFF;
sendCANMessage(0x200, rpmData, 2);
delay(10);
// 发送温度消息
uint8_t tempData[1];
tempData[0] = 25 + 40; // 25°C (加40度偏移)
sendCANMessage(0x300, tempData, 1);
}
/**
* @brief 主循环
*/
void loop() {
static unsigned long lastSendTime = 0;
/* 每1秒发送一次消息 */
if (millis() - lastSendTime >= 1000) {
lastSendTime = millis();
sendExampleMessages();
}
/* 持续接收消息 */
receiveCANMessage();
delay(10);
}
9.3 Python-CAN库实现(Linux环境)
"""
CAN通信Python实现
使用python-can库
适用于Linux SocketCAN
"""
import can
import time
import struct
from threading import Thread
class CANInterface:
def __init__(self, channel='can0', bitrate=500000):
"""
初始化CAN接口
Args:
channel: CAN设备名称
bitrate: 波特率
"""
self.channel = channel
self.bitrate = bitrate
self.bus = None
self.running = False
def connect(self):
"""连接到CAN总线"""
try:
# 创建SocketCAN接口
self.bus = can.interface.Bus(
channel=self.channel,
bustype='socketcan',
bitrate=self.bitrate
)
print(f"成功连接到 {self.channel} @ {self.bitrate}bps")
return True
except Exception as e:
print(f"连接失败: {e}")
return False
def send_message(self, can_id, data, is_extended=False):
"""
发送CAN消息
Args:
can_id: CAN标识符
data: 数据字节数组
is_extended: 是否为扩展帧
"""
try:
message = can.Message(
arbitration_id=can_id,
data=data,
is_extended_id=is_extended
)
self.bus.send(message)
print(f"发送: ID=0x{can_id:X}, Data={data.hex()}")
return True
except Exception as e:
print(f"发送失败: {e}")
return False
def receive_loop(self):
"""接收消息循环(线程函数)"""
print("开始接收CAN消息...")
while self.running:
try:
message = self.bus.recv(timeout=1.0)
if message:
self.parse_message(message)
except Exception as e:
print(f"接收错误: {e}")
def parse_message(self, message):
"""
解析CAN消息
Args:
message: can.Message对象
"""
can_id = message.arbitration_id
data = message.data
print(f"收到: ID=0x{can_id:X}, DLC={len(data)}, Data={data.hex()}")
# 根据ID解析不同消息
if can_id == 0x100:
self.parse_vehicle_speed(data)
elif can_id == 0x200:
self.parse_engine_rpm(data)
elif can_id == 0x300:
self.parse_vehicle_status(data)
def parse_vehicle_speed(self, data):
"""解析车速消息"""
if len(data) >= 2:
speed = struct.unpack('>H', data[0:2])[0] # 大端序
speed_kph = speed / 10.0
print(f" → 车速: {speed_kph} km/h")
def parse_engine_rpm(self, data):
"""解析转速消息"""
if len(data) >= 2:
rpm = struct.unpack('>H', data[0:2])[0]
print(f" → 转速: {rpm} rpm")
def parse_vehicle_status(self, data):
"""解析综合状态消息"""
if len(data) >= 8:
speed = struct.unpack('>H', data[0:2])[0]
rpm = struct.unpack('>H', data[2:4])[0]
throttle = data[4]
brake = data[5]
status = data[6]
gear = data[7]
print(f" → 车辆状态:")
print(f" 车速: {speed/100.0} km/h")
print(f" 转速: {rpm} rpm")
print(f" 油门: {throttle}%")
print(f" 刹车: {brake}%")
print(f" 发动机: {'运行' if status & 0x01 else '停止'}")
print(f" 档位: {'PRND'[gear]}")
def start_receiving(self):
"""启动接收线程"""
self.running = True
self.rx_thread = Thread(target=self.receive_loop)
self.rx_thread.daemon = True
self.rx_thread.start()
def stop_receiving(self):
"""停止接收"""
self.running = False
if hasattr(self, 'rx_thread'):
self.rx_thread.join()
def send_vehicle_speed(self, speed_kph):
"""发送车速消息"""
speed_raw = int(speed_kph * 10)
data = struct.pack('>H', speed_raw)
self.send_message(0x100, data)
def send_engine_rpm(self, rpm):
"""发送转速消息"""
data = struct.pack('>H', rpm)
self.send_message(0x200, data)
def send_vehicle_status(self, speed, rpm, throttle, brake, engine_on, gear):
"""发送综合状态消息"""
speed_raw = int(speed * 100)
status_byte = 0x01 if engine_on else 0x00
data = struct.pack('>HHBBBB',
speed_raw,
rpm,
throttle,
brake,
status_byte,
gear
)
self.send_message(0x300, data)
def disconnect(self):
"""断开连接"""
if self.bus:
self.bus.shutdown()
print("已断开CAN连接")
# 使用示例
def main():
# 创建CAN接口
can_if = CANInterface('can0', 500000)
# 连接
if not can_if.connect():
return
# 启动接收
can_if.start_receiving()
try:
# 发送测试消息
while True:
# 发送车速
can_if.send_vehicle_speed(85.5)
time.sleep(0.1)
# 发送转速
can_if.send_engine_rpm(2500)
time.sleep(0.1)
# 发送综合状态
can_if.send_vehicle_status(
speed=85.5, # km/h
rpm=2500, # rpm
throttle=45, # %
brake=0, # %
engine_on=True,
gear=3 # D档
)
time.sleep(1.0)
except KeyboardInterrupt:
print("
程序终止")
finally:
can_if.stop_receiving()
can_if.disconnect()
if __name__ == '__main__':
main()
10. 高级应用与优化
10.1 CAN网络诊断
10.1.1 UDS诊断协议(ISO 14229)
/**
* @brief UDS诊断服务
*/
/* UDS服务ID定义 */
#define UDS_SERVICE_DIAG_SESSION_CTRL 0x10
#define UDS_SERVICE_ECU_RESET 0x11
#define UDS_SERVICE_READ_DTC 0x19
#define UDS_SERVICE_READ_DATA_BY_ID 0x22
#define UDS_SERVICE_WRITE_DATA_BY_ID 0x2E
#define UDS_SERVICE_SECURITY_ACCESS 0x27
#define UDS_SERVICE_TESTER_PRESENT 0x3E
/* 诊断会话类型 */
#define SESSION_DEFAULT 0x01
#define SESSION_PROGRAMMING 0x02
#define SESSION_EXTENDED_DIAGNOSTIC 0x03
/**
* @brief 发送UDS请求
*/
void UDS_SendRequest(uint8_t service, uint8_t* data, uint8_t len)
{
CAN_Message_t msg;
msg.id = 0x7DF; // 功能寻址
msg.format = 0;
msg.type = 0;
msg.len = len + 1;
msg.data[0] = service;
memcpy(&msg.data[1], data, len);
CAN_Transmit(&msg);
}
/**
* @brief 读取DTC(故障码)
*/
void UDS_ReadDTC(void)
{
uint8_t data[2];
data[0] = 0x02; // 读取DTC快照
data[1] = 0xFF; // 所有DTC
UDS_SendRequest(UDS_SERVICE_READ_DTC, data, 2);
}
/**
* @brief 读取数据标识符
*/
void UDS_ReadDataByID(uint16_t did)
{
uint8_t data[2];
data[0] = did >> 8;
data[1] = did & 0xFF;
UDS_SendRequest(UDS_SERVICE_READ_DATA_BY_ID, data, 2);
}
10.2 CAN FD(灵活数据速率)
CAN FD是CAN的升级版本,主要特点:
CAN vs CAN FD 对比:
┌─────────────────┬──────────────┬──────────────┐
│ 特性 │ CAN 2.0 │ CAN FD │
├─────────────────┼──────────────┼──────────────┤
│ 最大数据长度 │ 8 字节 │ 64 字节 │
│ 仲裁段速率 │ 最高1Mbps │ 最高1Mbps │
│ 数据段速率 │ 同仲裁段 │ 最高8Mbps │
│ CRC多项式 │ 15位 │ 17/21位 │
│ 位填充 │ 全帧 │ 仅部分 │
│ 错误处理 │ 标准 │ 改进 │
└─────────────────┴──────────────┴──────────────┘
数据长度码(DLC)扩展:
DLC | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Bytes| 0 1 2 3 4 5 6 7 8 12 16 20 24 32 48 64
10.3 性能优化技巧
10.3.1 消息优先级分组
/**
* @brief 基于优先级的消息调度
*/
typedef struct {
uint32_t id;
uint8_t priority; // 0=最高
uint32_t period_ms;
uint32_t last_send_time;
uint8_t data[8];
uint8_t len;
} Periodic_Message_t;
Periodic_Message_t periodicMessages[] = {
{0x010, 0, 10, 0, {0}, 8}, // 制动状态, 10ms
{0x100, 1, 20, 0, {0}, 8}, // 车速, 20ms
{0x200, 2, 50, 0, {0}, 8}, // 转速, 50ms
{0x300, 3, 100, 0, {0}, 8}, // 温度, 100ms
};
void SchedulePeriodicMessages(void)
{
uint32_t now = HAL_GetTick();
for (int i = 0; i < sizeof(periodicMessages)/sizeof(Periodic_Message_t); i++) {
if (now - periodicMessages[i].last_send_time >= periodicMessages[i].period_ms) {
CAN_Message_t msg;
msg.id = periodicMessages[i].id;
msg.len = periodicMessages[i].len;
memcpy(msg.data, periodicMessages[i].data, msg.len);
if (CAN_Transmit(&msg) == HAL_OK) {
periodicMessages[i].last_send_time = now;
}
}
}
}
10.3.2 接收滤波器优化
/**
* @brief 高级滤波器配置
*/
void ConfigureAdvancedFilters(void)
{
CAN_FilterTypeDef filter;
// 滤波器组0: 精确匹配0x100
filter.FilterBank = 0;
filter.FilterMode = CAN_FILTERMODE_IDLIST; // 列表模式
filter.FilterScale = CAN_FILTERSCALE_16BIT;
filter.FilterIdHigh = 0x100 << 5;
filter.FilterIdLow = 0x100 << 5;
filter.FilterMaskIdHigh = 0x100 << 5;
filter.FilterMaskIdLow = 0x100 << 5;
filter.FilterFIFOAssignment = CAN_RX_FIFO0;
filter.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan1, &filter);
// 滤波器组1: 接收0x200-0x2FF范围
filter.FilterBank = 1;
filter.FilterMode = CAN_FILTERMODE_IDMASK; // 掩码模式
filter.FilterScale = CAN_FILTERSCALE_32BIT;
filter.FilterIdHigh = 0x200 << 5;
filter.FilterIdLow = 0x0000;
filter.FilterMaskIdHigh = 0x700 << 5; // 掩码0x700
filter.FilterMaskIdLow = 0x0000;
filter.FilterFIFOAssignment = CAN_RX_FIFO1;
filter.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan1, &filter);
}
10.4 故障排查指南
常见CAN总线问题诊断:
┌──────────────────────────────────────────────────┐
│ 问题现象 │ 可能原因 │ 解决方案 │
├──────────────────────────────────────────────────┤
│ 无法通信 │ 波特率不匹配 │ 检查配置 │
│ │ 终端电阻缺失 │ 添加120Ω │
│ │ 接线错误 │ 检查接线 │
├──────────────────────────────────────────────────┤
│ 通信不稳定 │ 干扰严重 │ 增加滤波 │
│ │ 线缆质量差 │ 更换线缆 │
│ │ 接地不良 │ 改善接地 │
├──────────────────────────────────────────────────┤
│ 错误计数增加 │ 总线负载过高 │ 优化消息 │
│ │ 某节点故障 │ 隔离测试 │
│ │ 位时序不正确 │ 重新配置 │
├──────────────────────────────────────────────────┤
│ Bus Off频繁 │ 硬件故障 │ 检查硬件 │
│ │ 短路 │ 检查线路 │
│ │ 驱动能力不足 │ 检查收发器 │
└──────────────────────────────────────────────────┘
10.5 测试工具推荐
1. 硬件工具:
- PCAN-USB: Peak Systems CAN分析仪
- CANalyzer: Vector公司专业工具
- USB-CAN: 周立功CAN分析仪
2. 软件工具:
- CANoe: Vector网络仿真工具
- Busmaster: 开源CAN监控工具
- PCAN-View: Peak Systems免费软件
3. 调试技巧:
- 使用示波器观察波形
- 监控错误计数器
- 分析总线负载
- 记录通信日志
总结
本文深入讲解了CAN总线系统的工作原理,从物理层的差分信号传输、数据链路层的帧格式和仲裁机制,到完整的软硬件实现方案。主要内容包括:
物理层原理: 差分信号、终端电阻、阻抗匹配协议机制: 非破坏性仲裁、位时序、错误处理硬件设计: 收发器选型、EMC防护、PCB布线软件实现: STM32、Arduino、Python多平台代码高级应用: UDS诊断、CAN FD、性能优化
CAN总线凭借其可靠性、实时性和灵活性,已成为汽车和工业控制领域的标准通信协议。掌握CAN总线技术对于从事嵌入式开发、汽车电子、工业自动化的工程师至关重要。
















暂无评论内容