CAN总线协议应用开发¶
概述¶
CAN(Controller Area Network,控制器局域网)是一种串行通信协议,由德国Bosch公司在1980年代为汽车电子系统开发。如今,CAN总线已成为汽车电子、工业自动化、医疗设备等领域最重要的现场总线标准之一。
完成本文学习后,你将能够:
- 理解CAN总线的工作原理和技术特点
- 掌握CAN报文的帧格式和标识符机制
- 了解CAN总线的仲裁和错误处理机制
- 理解CAN过滤器的配置和使用
- 认识CANopen等高层协议
- 了解CAN在汽车和工业领域的典型应用
背景知识¶
为什么需要CAN总线?¶
在CAN总线出现之前,汽车电子系统使用点对点的线束连接各个ECU(电子控制单元)。随着汽车电子功能的增加,这种方式导致:
- 线束复杂:数百根线缆,重量可达50kg以上
- 成本高昂:线束成本占整车成本的3-5%
- 可靠性差:连接点多,故障率高
- 维护困难:故障诊断和维修复杂
CAN总线通过多主总线架构解决了这些问题:
传统点对点连接:
ECU1 -------- ECU2
| |
| |
ECU3 -------- ECU4
(需要6根线缆)
CAN总线连接:
ECU1 ---|
ECU2 ---|--- CAN总线 (2根线)
ECU3 ---|
ECU4 ---|
CAN总线的核心特点¶
| 特性 | 说明 |
|---|---|
| 多主总线 | 所有节点地位平等,无主从之分 |
| 非破坏性仲裁 | 基于优先级的总线访问,高优先级消息优先发送 |
| 广播通信 | 一个节点发送,所有节点接收 |
| 错误检测 | 5种错误检测机制,错误率极低 |
| 自动重传 | 发送失败自动重传 |
| 故障界定 | 自动隔离故障节点 |
CAN总线基础¶
CAN总线物理层¶
总线拓扑¶
CAN总线采用线性总线拓扑结构:
120Ω 120Ω
┌─┐ ┌─┐
│ │ │ │
└─┘ └─┘
│ │
CAN_H ├──────┬──────┬──────┬──────┬──────┬──────┬──────┤
│ │ │ │ │ │ │
ECU1 ECU2 ECU3 ECU4 ECU5 ECU6 ECU7
│ │ │ │ │ │ │
CAN_L ├──────┴──────┴──────┴──────┴──────┴──────┴──────┤
│ │
┌─┐ ┌─┐
│ │ │ │
└─┘ └─┘
120Ω 120Ω
关键要点: - 使用双绞线(CAN_H和CAN_L) - 总线两端需要120Ω终端电阻 - 支线长度应尽量短(<0.3m) - 总线长度与波特率相关
电气特性¶
CAN总线使用差分信号传输:
| 状态 | CAN_H电压 | CAN_L电压 | 差分电压 |
|---|---|---|---|
| 显性(0) | 3.5V | 1.5V | 2.0V |
| 隐性(1) | 2.5V | 2.5V | 0V |
显性和隐性: - 显性(Dominant):逻辑0,电压差大,能覆盖隐性 - 隐性(Recessive):逻辑1,电压差小,被显性覆盖 - 仲裁时,显性位优先级高于隐性位
波特率与总线长度¶
| 波特率 | 最大总线长度 | 典型应用 |
|---|---|---|
| 1 Mbps | 40m | 高速CAN,车身网络 |
| 500 kbps | 100m | 动力系统 |
| 250 kbps | 250m | 底盘系统 |
| 125 kbps | 500m | 舒适系统 |
| 50 kbps | 1000m | 工业控制 |
CAN协议层次¶
CAN协议遵循ISO 11898标准,分为两层:
┌─────────────────────────────┐
│ 应用层(用户定义) │
├─────────────────────────────┤
│ 高层协议(CANopen等) │
├─────────────────────────────┤
│ CAN数据链路层 │
│ ┌─────────────────────┐ │
│ │ 逻辑链路控制LLC │ │
│ ├─────────────────────┤ │
│ │ 媒体访问控制MAC │ │
│ └─────────────────────┘ │
├─────────────────────────────┤
│ CAN物理层 │
│ ┌─────────────────────┐ │
│ │ 物理信令PLS │ │
│ ├─────────────────────┤ │
│ │ 物理媒体连接PMA │ │
│ └─────────────────────┘ │
└─────────────────────────────┘
CAN报文格式¶
标准帧与扩展帧¶
CAN协议定义了两种帧格式:
| 特性 | 标准帧(CAN 2.0A) | 扩展帧(CAN 2.0B) |
|---|---|---|
| 标识符长度 | 11位 | 29位 |
| 标识符数量 | 2048个 | 536,870,912个 |
| 应用场景 | 汽车电子 | 工业控制、复杂系统 |
标准数据帧结构¶
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│SOF │ ID │RTR │IDE │r0 │DLC │DATA│CRC │ACK │EOF │IFS │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
1位 11位 1位 1位 1位 4位 0-8B 16位 2位 7位 3位
各字段说明:
- SOF(Start of Frame):帧起始,1位显性位
- 标志一帧的开始
-
用于总线同步
-
ID(Identifier):标识符,11位
- 定义消息的优先级和内容
- 数值越小,优先级越高
-
用于消息过滤
-
RTR(Remote Transmission Request):远程传输请求,1位
- 0(显性):数据帧
-
1(隐性):远程帧
-
IDE(Identifier Extension):标识符扩展,1位
- 0(显性):标准帧
-
1(隐性):扩展帧
-
r0(Reserved):保留位,1位
-
必须为显性位
-
DLC(Data Length Code):数据长度码,4位
-
指示数据字段的字节数(0-8)
-
DATA:数据字段,0-8字节
-
实际传输的数据
-
CRC(Cyclic Redundancy Check):循环冗余校验,15位+1位界定符
- 用于错误检测
-
CRC界定符为隐性位
-
ACK(Acknowledge):应答字段,1位+1位界定符
- 发送节点发送隐性位
- 接收节点正确接收后发送显性位
-
ACK界定符为隐性位
-
EOF(End of Frame):帧结束,7位隐性位
- 标志帧的结束
-
IFS(Inter Frame Space):帧间隔,3位隐性位
- 帧与帧之间的最小间隔
扩展数据帧结构¶
扩展帧在标准帧基础上增加了18位标识符:
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│SOF │ID │SRR │IDE │ID │RTR │r1 │r0 │DLC │DATA│CRC │ACK │EOF │IFS│
│ │(11)│ │ │(18)│ │ │ │ │ │ │ │ │ │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴───┘
新增字段: - SRR(Substitute Remote Request):替代远程请求,1位隐性位 - ID扩展:额外18位标识符 - r1:保留位,1位显性位
其他帧类型¶
远程帧¶
远程帧用于请求其他节点发送特定ID的数据帧:
错误帧¶
当节点检测到错误时发送错误帧:
结构:
┌──────────────┬──────────────┐
│ 错误标志 │ 错误界定符 │
│ 6位显性/隐性 │ 8位隐性 │
└──────────────┴──────────────┘
过载帧¶
节点需要延迟下一帧时发送过载帧:
结构:
┌──────────────┬──────────────┐
│ 过载标志 │ 过载界定符 │
│ 6位显性 │ 8位隐性 │
└──────────────┴──────────────┘
CAN总线仲裁机制¶
非破坏性仲裁¶
CAN总线使用CSMA/CA(Carrier Sense Multiple Access with Collision Avoidance)机制:
时间轴:
节点A (ID=0x100): 0 0 0 1 0 0 0 0 0 0 0 0
节点B (ID=0x200): 0 0 1 ← 检测到冲突,停止发送
节点C (ID=0x300): 0 0 1 ← 检测到冲突,停止发送
↑
仲裁点
总线状态: 0 0 0 1 0 0 0 0 0 0 0 0 (节点A获胜)
仲裁过程:
- 所有节点同时开始发送
- 逐位比较标识符
- 发送隐性位但检测到显性位的节点停止发送
- 标识符最小(优先级最高)的节点获得总线访问权
- 失败节点等待下一次机会
关键特点: - 无数据丢失:失败节点自动重传 - 确定性延迟:高优先级消息延迟可预测 - 实时性好:适合实时控制系统
优先级设计¶
在实际应用中,标识符的分配需要考虑优先级:
// 汽车CAN网络标识符分配示例
#define CAN_ID_ENGINE_RPM 0x100 // 发动机转速(高优先级)
#define CAN_ID_VEHICLE_SPEED 0x110 // 车速
#define CAN_ID_BRAKE_PRESSURE 0x120 // 制动压力
#define CAN_ID_THROTTLE_POS 0x130 // 油门位置
#define CAN_ID_COOLANT_TEMP 0x200 // 冷却液温度(中优先级)
#define CAN_ID_FUEL_LEVEL 0x300 // 油量
#define CAN_ID_DOOR_STATUS 0x400 // 车门状态(低优先级)
#define CAN_ID_WINDOW_STATUS 0x500 // 车窗状态
优先级原则: - 安全相关:最高优先级(如制动、转向) - 动力系统:高优先级(如发动机、变速箱) - 车身控制:中优先级(如灯光、雨刷) - 舒适功能:低优先级(如空调、音响)
CAN错误处理¶
错误检测机制¶
CAN协议提供5种错误检测机制:
| 错误类型 | 检测方法 | 说明 |
|---|---|---|
| 位错误 | 发送节点监听总线 | 发送的位与总线上的位不一致 |
| 填充错误 | 位填充规则检查 | 违反位填充规则 |
| CRC错误 | CRC校验 | CRC校验失败 |
| 格式错误 | 帧格式检查 | 固定格式位错误 |
| 应答错误 | ACK检查 | 无节点应答 |
位填充规则¶
为了保证同步,CAN使用位填充技术:
位填充范围: - SOF到CRC序列(包括CRC) - CRC界定符之后不进行位填充
错误计数与节点状态¶
每个CAN节点维护两个错误计数器:
typedef struct {
uint8_t TEC; // 发送错误计数器 (Transmit Error Counter)
uint8_t REC; // 接收错误计数器 (Receive Error Counter)
} CAN_ErrorCounter_t;
节点状态:
| 状态 | 条件 | 行为 |
|---|---|---|
| 主动错误 | TEC < 128 且 REC < 128 | 正常通信,发送主动错误帧 |
| 被动错误 | TEC ≥ 128 或 REC ≥ 128 | 限制通信,发送被动错误帧 |
| 总线关闭 | TEC > 255 | 断开总线连接 |
错误计数规则: - 发送错误:TEC += 8 - 接收错误:REC += 1 - 成功发送:TEC -= 1 - 成功接收:REC -= 1
节点状态转换:
TEC或REC ≥ 128
主动错误 ──────────────────────→ 被动错误
↑ │
│ TEC和REC < 128 │ TEC > 255
└───────────────────────────────┴→ 总线关闭
│
│ 128次11位隐性
↓
主动错误
CAN过滤器机制¶
为什么需要过滤器?¶
CAN总线是广播通信,所有节点都能接收到所有消息。过滤器用于:
- 减少CPU中断次数
- 降低软件处理负担
- 只接收感兴趣的消息
- 提高系统效率
过滤器工作原理¶
CAN控制器使用标识符过滤器和掩码来筛选消息:
接收到的ID: 0x123 = 0001 0010 0011
过滤器ID: 0x120 = 0001 0010 0000
掩码: 0x7F0 = 0111 1111 0000
↓↓↓↓ ↓↓↓↓ ↓↓↓↓
比较位: 111 1111 0
(只比较掩码为1的位)
结果:0x123 & 0x7F0 = 0x120 → 匹配,接收
过滤规则:
STM32 CAN过滤器配置示例¶
STM32的CAN控制器支持多种过滤器模式:
// 标识符列表模式:精确匹配多个ID
void CAN_FilterConfig_IdList(void)
{
CAN_FilterTypeDef filter;
filter.FilterBank = 0;
filter.FilterMode = CAN_FILTERMODE_IDLIST; // 列表模式
filter.FilterScale = CAN_FILTERSCALE_32BIT;
filter.FilterIdHigh = 0x100 << 5; // ID1 = 0x100
filter.FilterIdLow = 0x200 << 5; // ID2 = 0x200
filter.FilterMaskIdHigh = 0x300 << 5; // ID3 = 0x300
filter.FilterMaskIdLow = 0x400 << 5; // ID4 = 0x400
filter.FilterFIFOAssignment = CAN_RX_FIFO0;
filter.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan, &filter);
}
// 标识符掩码模式:范围匹配
void CAN_FilterConfig_IdMask(void)
{
CAN_FilterTypeDef filter;
filter.FilterBank = 1;
filter.FilterMode = CAN_FILTERMODE_IDMASK; // 掩码模式
filter.FilterScale = CAN_FILTERSCALE_32BIT;
filter.FilterIdHigh = 0x100 << 5; // 过滤器ID
filter.FilterIdLow = 0;
filter.FilterMaskIdHigh = 0x700 << 5; // 掩码(比较高3位)
filter.FilterMaskIdLow = 0;
filter.FilterFIFOAssignment = CAN_RX_FIFO0;
filter.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan, &filter);
// 此配置接收ID范围:0x100-0x1FF
}
过滤器配置策略¶
策略1:接收所有消息
filter.FilterIdHigh = 0x0000;
filter.FilterIdLow = 0x0000;
filter.FilterMaskIdHigh = 0x0000; // 掩码全0,不比较任何位
filter.FilterMaskIdLow = 0x0000;
策略2:接收特定ID
策略3:接收ID范围
CANopen协议¶
CANopen简介¶
CANopen是基于CAN总线的高层应用协议,由CiA(CAN in Automation)组织制定,广泛应用于工业自动化领域。
核心特性: - 标准化的设备描述(EDS文件) - 对象字典(Object Dictionary) - 预定义的通信对象 - 网络管理功能 - 设备配置工具
CANopen通信对象¶
CANopen定义了4种通信对象(COB):
| 通信对象 | 功能 | COB-ID范围 | 传输方式 |
|---|---|---|---|
| NMT | 网络管理 | 0x000 | 主站→从站 |
| SYNC | 同步对象 | 0x080 | 主站→所有 |
| EMCY | 紧急对象 | 0x081-0x0FF | 从站→主站 |
| PDO | 过程数据对象 | 0x180-0x57F | 点对点/广播 |
| SDO | 服务数据对象 | 0x580-0x5FF | 点对点 |
| NMT Error Control | 心跳/节点监控 | 0x700-0x77F | 从站→主站 |
对象字典¶
对象字典是CANopen设备的核心,定义了设备的所有参数和功能:
对象字典结构:
┌─────────────┬──────────────────────────┐
│ 索引范围 │ 用途 │
├─────────────┼──────────────────────────┤
│ 0x0000 │ 未使用 │
│ 0x0001-0x009F│ 数据类型 │
│ 0x1000-0x1FFF│ 通信参数 │
│ 0x2000-0x5FFF│ 制造商特定参数 │
│ 0x6000-0x9FFF│ 标准化设备配置参数 │
│ 0xA000-0xFFFF│ 保留 │
└─────────────┴──────────────────────────┘
常用对象示例:
// 设备类型
#define OD_DEVICE_TYPE 0x1000
// 错误寄存器
#define OD_ERROR_REGISTER 0x1001
// 制造商设备名称
#define OD_MANUFACTURER_NAME 0x1008
// 心跳生产者时间
#define OD_HEARTBEAT_TIME 0x1017
// 接收PDO通信参数
#define OD_RPDO1_COMM_PARAM 0x1400
#define OD_RPDO1_MAPPING 0x1600
// 发送PDO通信参数
#define OD_TPDO1_COMM_PARAM 0x1800
#define OD_TPDO1_MAPPING 0x1A00
PDO(过程数据对象)¶
PDO用于实时数据传输,分为TPDO(发送)和RPDO(接收):
PDO映射示例:
TPDO1 (COB-ID: 0x180 + Node-ID):
┌────────────────────────────────┐
│ Byte 0-1: 电机转速 (RPM) │
│ Byte 2-3: 电机电流 (mA) │
│ Byte 4-5: 电机温度 (0.1°C) │
│ Byte 6-7: 状态字 │
└────────────────────────────────┘
PDO配置:
// TPDO1映射配置
uint32_t tpdo1_mapping[] = {
0x60410010, // 状态字,16位
0x60640020, // 位置实际值,32位
0x60770010, // 转矩实际值,16位
};
CAN在汽车中的应用¶
汽车CAN网络架构¶
现代汽车通常使用多条CAN总线:
┌──────────────┐
│ 网关ECU │
└──────┬───────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌───┴───┐ ┌───┴───┐ ┌────┴────┐
│动力CAN│ │底盘CAN│ │车身CAN │
│500kbps│ │500kbps│ │125kbps │
└───┬───┘ └───┬───┘ └────┬────┘
│ │ │
┌───┴───┐ ┌───┴───┐ ┌────┴────┐
│发动机 │ │ABS/ESP│ │车门控制 │
│变速箱 │ │转向 │ │车窗控制 │
│ │ │悬架 │ │座椅控制 │
└───────┘ └───────┘ │灯光控制 │
└─────────┘
网络分类:
- 动力总成CAN(Powertrain CAN)
- 波特率:500 kbps
- 节点:发动机ECU、变速箱ECU、混合动力控制器
-
特点:高实时性、高优先级
-
底盘CAN(Chassis CAN)
- 波特率:500 kbps
- 节点:ABS、ESP、转向、悬架
-
特点:安全关键、实时性要求高
-
车身CAN(Body CAN)
- 波特率:125 kbps
- 节点:车门、车窗、座椅、灯光、空调
-
特点:节点多、实时性要求低
-
信息娱乐CAN(Infotainment CAN)
- 波特率:125 kbps
- 节点:仪表、中控屏、音响、导航
- 特点:数据量大、非安全关键
典型CAN消息示例¶
发动机转速消息¶
// CAN ID: 0x100
// 数据长度: 8字节
// 发送周期: 10ms
typedef struct {
uint16_t engine_rpm; // 字节0-1: 发动机转速 (rpm)
uint16_t vehicle_speed; // 字节2-3: 车速 (0.01 km/h)
uint8_t throttle_pos; // 字节4: 油门位置 (0-100%)
uint8_t coolant_temp; // 字节5: 冷却液温度 (°C - 40)
uint8_t fuel_level; // 字节6: 油量 (0-100%)
uint8_t status; // 字节7: 状态位
} EngineData_t;
// 发送函数
void CAN_SendEngineData(EngineData_t *data)
{
CAN_TxHeaderTypeDef header;
uint8_t txData[8];
uint32_t mailbox;
header.StdId = 0x100;
header.IDE = CAN_ID_STD;
header.RTR = CAN_RTR_DATA;
header.DLC = 8;
// 打包数据
txData[0] = (data->engine_rpm >> 8) & 0xFF;
txData[1] = data->engine_rpm & 0xFF;
txData[2] = (data->vehicle_speed >> 8) & 0xFF;
txData[3] = data->vehicle_speed & 0xFF;
txData[4] = data->throttle_pos;
txData[5] = data->coolant_temp;
txData[6] = data->fuel_level;
txData[7] = data->status;
HAL_CAN_AddTxMessage(&hcan, &header, txData, &mailbox);
}
制动系统消息¶
// CAN ID: 0x120
// 数据长度: 4字节
// 发送周期: 20ms
typedef struct {
uint16_t brake_pressure; // 字节0-1: 制动压力 (0.1 bar)
uint8_t abs_active; // 字节2: ABS激活状态
uint8_t wheel_speed[4]; // 字节3: 车轮速度标志
} BrakeData_t;
OBD-II诊断¶
OBD-II(On-Board Diagnostics II)使用CAN总线进行车辆诊断:
OBD-II CAN标准: - ISO 15765-4(CAN) - 波特率:500 kbps - 标识符:11位标准帧 - 诊断ID:0x7DF(请求),0x7E8-0x7EF(响应)
诊断消息格式:
// 请求发动机转速
// ID: 0x7DF, DLC: 8
uint8_t request[] = {
0x02, // 数据长度
0x01, // 服务01:显示当前数据
0x0C, // PID 0x0C:发动机转速
0x00, 0x00, 0x00, 0x00, 0x00 // 填充
};
// 响应
// ID: 0x7E8, DLC: 8
uint8_t response[] = {
0x04, // 数据长度
0x41, // 服务01响应
0x0C, // PID 0x0C
0x1A, // 数据高字节
0xF8, // 数据低字节
0x00, 0x00, 0x00 // 填充
};
// 计算转速:((0x1A << 8) | 0xF8) / 4 = 1726 rpm
CAN在工业控制中的应用¶
DeviceNet¶
DeviceNet是基于CAN的工业现场总线:
特点: - 基于CAN 2.0A(11位标识符) - 波特率:125/250/500 kbps - 主从和对等通信 - 即插即用设备配置
消息类型:
┌──────────────┬──────────────┬──────────────┐
│ 消息组 │ CAN ID范围 │ 用途 │
├──────────────┼──────────────┼──────────────┤
│ Group 1 │ 0x000-0x3FF │ 高优先级I/O │
│ Group 2 │ 0x400-0x5FF │ 显式消息 │
│ Group 3 │ 0x600-0x7EF │ 轮询/状态 │
│ Group 4 │ 0x7F0-0x7FF │ 重复MAC ID │
└──────────────┴──────────────┴──────────────┘
CANopen在工业中的应用¶
典型应用场景: - 运动控制系统 - 机器人控制 - 楼宇自动化 - 医疗设备
设备配置示例:
// 伺服驱动器CANopen配置
typedef struct {
uint8_t node_id; // 节点ID (1-127)
uint32_t baudrate; // 波特率
uint16_t heartbeat_time; // 心跳时间 (ms)
uint8_t pdo_mapping[8]; // PDO映射
} CANopenConfig_t;
// 初始化伺服驱动器
void ServoInit(CANopenConfig_t *config)
{
// 1. 进入预操作状态
NMT_SendCommand(NMT_ENTER_PRE_OPERATIONAL, config->node_id);
// 2. 配置TPDO1(发送位置和速度)
SDO_Write(config->node_id, 0x1800, 1, 0x180 + config->node_id);
SDO_Write(config->node_id, 0x1A00, 0, 0); // 清除映射
SDO_Write(config->node_id, 0x1A00, 1, 0x60640020); // 位置
SDO_Write(config->node_id, 0x1A00, 2, 0x606C0020); // 速度
SDO_Write(config->node_id, 0x1A00, 0, 2); // 映射2个对象
// 3. 配置RPDO1(接收目标位置)
SDO_Write(config->node_id, 0x1400, 1, 0x200 + config->node_id);
SDO_Write(config->node_id, 0x1600, 0, 0);
SDO_Write(config->node_id, 0x1600, 1, 0x607A0020); // 目标位置
SDO_Write(config->node_id, 0x1600, 0, 1);
// 4. 启动节点
NMT_SendCommand(NMT_START_REMOTE_NODE, config->node_id);
}
深入理解¶
CAN FD(CAN with Flexible Data-Rate)¶
CAN FD是CAN协议的扩展版本,提供更高的数据传输速率:
主要改进:
| 特性 | 经典CAN | CAN FD |
|---|---|---|
| 最大数据长度 | 8字节 | 64字节 |
| 仲裁阶段速率 | 固定 | 固定(如500kbps) |
| 数据阶段速率 | 固定 | 可变(如2-5Mbps) |
| CRC | 15位 | 17/21位 |
| 错误检测 | 增强 | 进一步增强 |
CAN FD帧格式:
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│SOF │ ID │r1 │FDF │res │BRS │ESI │DLC │DATA│CRC │ACK │EOF│
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴───┘
新增位: - FDF(FD Format):CAN FD标志 - BRS(Bit Rate Switch):速率切换标志 - ESI(Error State Indicator):错误状态指示
应用场景: - 汽车以太网网关 - 高速数据记录 - 固件更新(OTA) - 高分辨率传感器数据
CAN总线负载计算¶
总线负载是评估CAN网络性能的重要指标:
总线负载 = (所有消息的总位数 / 总线带宽) × 100%
单个消息的位数 = 帧开销 + 数据位数 + 填充位数
标准数据帧开销:
- 最小:47位(0字节数据)
- 最大:111位(8字节数据)
计算示例:
// 假设:
// - 波特率:500 kbps
// - 消息1:ID=0x100, DLC=8, 周期=10ms
// - 消息2:ID=0x200, DLC=4, 周期=20ms
// - 消息3:ID=0x300, DLC=2, 周期=50ms
// 消息1位数:47 + 64 + 约10个填充位 = 121位
// 消息2位数:47 + 32 + 约6个填充位 = 85位
// 消息3位数:47 + 16 + 约4个填充位 = 67位
// 1秒内发送次数:
// 消息1:1000/10 = 100次
// 消息2:1000/20 = 50次
// 消息3:1000/50 = 20次
// 总位数 = 121×100 + 85×50 + 67×20 = 17690位
// 总线负载 = 17690 / 500000 × 100% = 3.54%
负载建议: - < 30%:良好 - 30-50%:可接受 - 50-70%:需要优化 - > 70%:存在风险
CAN总线测试与调试¶
使用CAN分析仪¶
常用CAN分析工具: - PCAN-USB:Peak System的USB-CAN适配器 - CANoe:Vector的专业CAN开发工具 - Kvaser:Kvaser的CAN接口和软件 - SocketCAN:Linux下的CAN驱动
常见问题诊断¶
问题1:总线关闭(Bus-Off)
症状:节点无法通信,错误计数器超过255
可能原因: - 硬件故障(收发器损坏) - 终端电阻缺失或错误 - 波特率不匹配 - 线缆断路或短路
诊断方法:
// 检查错误计数器
uint32_t error = HAL_CAN_GetError(&hcan);
if (error & HAL_CAN_ERROR_BOF) {
// 总线关闭
// 检查TEC和REC
uint32_t tec = (hcan.Instance->ESR >> 16) & 0xFF;
uint32_t rec = (hcan.Instance->ESR >> 24) & 0xFF;
printf("TEC=%d, REC=%d\n", tec, rec);
}
问题2:消息丢失
症状:部分消息未被接收
可能原因: - 过滤器配置错误 - 接收FIFO溢出 - 总线负载过高 - 优先级被抢占
诊断方法:
// 检查FIFO溢出
if (HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0) == 3) {
// FIFO已满,可能丢失消息
}
// 启用FIFO溢出中断
HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_OVERRUN);
问题3:通信延迟
症状:消息延迟大于预期
可能原因: - 总线负载过高 - 低优先级消息被抢占 - 错误帧导致重传
优化方法: - 降低非关键消息的发送频率 - 调整消息优先级(ID分配) - 增加总线速率 - 使用CAN FD
最佳实践¶
CAN网络设计原则¶
- 合理分配标识符
- 按优先级分配ID(安全>控制>状态>诊断)
- 预留ID空间用于扩展
-
避免ID冲突
-
控制总线负载
- 保持负载<50%
- 优化消息周期
-
合并相关数据
-
正确配置终端电阻
- 总线两端各一个120Ω
- 使用高质量电阻
-
避免多个终端电阻
-
使用屏蔽双绞线
- 减少电磁干扰
- 提高信号质量
-
延长通信距离
-
实现错误处理
- 监控错误计数器
- 实现总线关闭恢复
- 记录错误日志
软件开发建议¶
// 1. 使用结构化的消息定义
typedef struct {
uint32_t id;
uint8_t dlc;
uint8_t data[8];
uint32_t timestamp;
} CAN_Message_t;
// 2. 实现消息队列
#define CAN_RX_QUEUE_SIZE 32
CAN_Message_t rx_queue[CAN_RX_QUEUE_SIZE];
uint8_t rx_head = 0;
uint8_t rx_tail = 0;
// 3. 使用超时机制
#define CAN_RX_TIMEOUT_MS 100
uint32_t last_rx_time[256]; // 每个ID的最后接收时间
void CAN_CheckTimeout(void)
{
uint32_t now = HAL_GetTick();
for (int i = 0; i < 256; i++) {
if (last_rx_time[i] > 0 &&
(now - last_rx_time[i]) > CAN_RX_TIMEOUT_MS) {
// 消息超时
HandleMessageTimeout(i);
}
}
}
// 4. 实现错误恢复
void CAN_ErrorHandler(CAN_HandleTypeDef *hcan)
{
uint32_t error = HAL_CAN_GetError(hcan);
if (error & HAL_CAN_ERROR_BOF) {
// 总线关闭,尝试恢复
HAL_CAN_ResetError(hcan);
HAL_CAN_Start(hcan);
}
}
常见问题¶
Q1: CAN总线最多可以连接多少个节点?¶
A: 理论上CAN总线可以连接127个节点(标识符0x00保留),但实际应用中建议不超过30-40个节点,以保证信号质量和总线负载。节点数量还受到以下因素限制: - 总线长度 - 波特率 - 收发器驱动能力 - 总线负载
Q2: 为什么需要终端电阻?¶
A: 终端电阻的作用是: - 匹配传输线阻抗(120Ω) - 防止信号反射 - 提高信号质量 - 减少电磁干扰
没有终端电阻会导致信号反射,造成通信错误。
Q3: CAN总线如何实现点对点通信?¶
A: CAN总线本质上是广播通信,但可以通过以下方式实现"点对点": - 使用唯一的标识符 - 配置接收过滤器只接收特定ID - 在数据中包含目标节点地址 - 使用CANopen的SDO通信
Q4: CAN和CAN FD可以共存吗?¶
A: 可以,但需要注意: - CAN FD节点可以接收经典CAN消息 - 经典CAN节点会将CAN FD消息视为错误帧 - 混合网络中,CAN FD节点需要配置为兼容模式 - 建议使用独立的CAN FD网络
Q5: 如何选择CAN波特率?¶
A: 选择波特率需要考虑: - 总线长度(长度越长,波特率越低) - 实时性要求(实时性高,波特率高) - 节点数量(节点多,建议降低波特率) - 应用场景(汽车动力系统500kbps,车身系统125kbps)
总结¶
通过本文,你学习了:
- ✅ CAN总线的基本原理和技术特点
- ✅ CAN报文的帧格式和标识符机制
- ✅ 非破坏性仲裁和优先级管理
- ✅ CAN错误检测和处理机制
- ✅ CAN过滤器的配置和使用
- ✅ CANopen高层协议
- ✅ CAN在汽车和工业中的应用
关键要点: - CAN是汽车和工业领域最重要的现场总线 - 多主总线架构,无主从之分 - 基于优先级的非破坏性仲裁 - 强大的错误检测和处理能力 - 广泛的应用和成熟的生态系统
延伸阅读¶
推荐进一步学习的资源:
- CAN Specification 2.0 - Bosch官方规范
- ISO 11898标准 - CAN国际标准
- CANopen协议规范 - CiA官方文档
- Vector CANoe教程 - 专业CAN开发工具
参考资料¶
- ISO 11898-1:2015 - Road vehicles — Controller area network (CAN)
- CiA 301 - CANopen application layer and communication profile
- Bosch CAN Specification Version 2.0 (1991)
- SAE J1939 - Serial Control and Communications Heavy Duty Vehicle Network
- ISO 15765-2 - Diagnostic communication over Controller Area Network (DoCAN)
练习题:
-
计算一个CAN网络的总线负载,已知:波特率500kbps,有3个消息,ID分别为0x100(8字节,10ms)、0x200(4字节,20ms)、0x300(2字节,50ms)
-
设计一个CAN过滤器配置,要求接收ID范围0x100-0x1FF的所有消息
-
解释为什么CAN总线在汽车中使用差分信号而不是单端信号
下一步:建议学习 OPC UA工业互联协议 或 Modbus工业协议实战