跳转至

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位

各字段说明

  1. SOF(Start of Frame):帧起始,1位显性位
  2. 标志一帧的开始
  3. 用于总线同步

  4. ID(Identifier):标识符,11位

  5. 定义消息的优先级和内容
  6. 数值越小,优先级越高
  7. 用于消息过滤

  8. RTR(Remote Transmission Request):远程传输请求,1位

  9. 0(显性):数据帧
  10. 1(隐性):远程帧

  11. IDE(Identifier Extension):标识符扩展,1位

  12. 0(显性):标准帧
  13. 1(隐性):扩展帧

  14. r0(Reserved):保留位,1位

  15. 必须为显性位

  16. DLC(Data Length Code):数据长度码,4位

  17. 指示数据字段的字节数(0-8)

  18. DATA:数据字段,0-8字节

  19. 实际传输的数据

  20. CRC(Cyclic Redundancy Check):循环冗余校验,15位+1位界定符

  21. 用于错误检测
  22. CRC界定符为隐性位

  23. ACK(Acknowledge):应答字段,1位+1位界定符

  24. 发送节点发送隐性位
  25. 接收节点正确接收后发送显性位
  26. ACK界定符为隐性位

  27. EOF(End of Frame):帧结束,7位隐性位

    • 标志帧的结束
  28. 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的数据帧:

特点:
- RTR位为隐性(1)
- 无数据字段
- DLC指示请求的数据长度

错误帧

当节点检测到错误时发送错误帧:

结构:
┌──────────────┬──────────────┐
│ 错误标志     │ 错误界定符   │
│ 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获胜)

仲裁过程

  1. 所有节点同时开始发送
  2. 逐位比较标识符
  3. 发送隐性位但检测到显性位的节点停止发送
  4. 标识符最小(优先级最高)的节点获得总线访问权
  5. 失败节点等待下一次机会

关键特点: - 无数据丢失:失败节点自动重传 - 确定性延迟:高优先级消息延迟可预测 - 实时性好:适合实时控制系统

优先级设计

在实际应用中,标识符的分配需要考虑优先级:

// 汽车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使用位填充技术:

原始数据:  1 1 1 1 1 0 0 0 0 0 1
填充后:    1 1 1 1 1 0 0 0 0 0 0 1
                     ↑         ↑
                   填充位    填充位

规则:连续5个相同位后插入一个相反位

位填充范围: - 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 → 匹配,接收

过滤规则

if ((received_id & mask) == (filter_id & mask)) {
    // 接收消息
} else {
    // 丢弃消息
}

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

filter.FilterIdHigh = 0x123 << 5;
filter.FilterMaskIdHigh = 0x7FF << 5;  // 掩码全1,精确匹配

策略3:接收ID范围

// 接收0x100-0x10F
filter.FilterIdHigh = 0x100 << 5;
filter.FilterMaskIdHigh = 0x7F0 << 5;  // 只比较高8位

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│        │车门控制 │
    │变速箱 │          │转向   │        │车窗控制 │
    │       │          │悬架   │        │座椅控制 │
    └───────┘          └───────┘        │灯光控制 │
                                        └─────────┘

网络分类

  1. 动力总成CAN(Powertrain CAN)
  2. 波特率:500 kbps
  3. 节点:发动机ECU、变速箱ECU、混合动力控制器
  4. 特点:高实时性、高优先级

  5. 底盘CAN(Chassis CAN)

  6. 波特率:500 kbps
  7. 节点:ABS、ESP、转向、悬架
  8. 特点:安全关键、实时性要求高

  9. 车身CAN(Body CAN)

  10. 波特率:125 kbps
  11. 节点:车门、车窗、座椅、灯光、空调
  12. 特点:节点多、实时性要求低

  13. 信息娱乐CAN(Infotainment CAN)

  14. 波特率:125 kbps
  15. 节点:仪表、中控屏、音响、导航
  16. 特点:数据量大、非安全关键

典型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网络设计原则

  1. 合理分配标识符
  2. 按优先级分配ID(安全>控制>状态>诊断)
  3. 预留ID空间用于扩展
  4. 避免ID冲突

  5. 控制总线负载

  6. 保持负载<50%
  7. 优化消息周期
  8. 合并相关数据

  9. 正确配置终端电阻

  10. 总线两端各一个120Ω
  11. 使用高质量电阻
  12. 避免多个终端电阻

  13. 使用屏蔽双绞线

  14. 减少电磁干扰
  15. 提高信号质量
  16. 延长通信距离

  17. 实现错误处理

  18. 监控错误计数器
  19. 实现总线关闭恢复
  20. 记录错误日志

软件开发建议

// 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是汽车和工业领域最重要的现场总线 - 多主总线架构,无主从之分 - 基于优先级的非破坏性仲裁 - 强大的错误检测和处理能力 - 广泛的应用和成熟的生态系统

延伸阅读

推荐进一步学习的资源:

参考资料

  1. ISO 11898-1:2015 - Road vehicles — Controller area network (CAN)
  2. CiA 301 - CANopen application layer and communication profile
  3. Bosch CAN Specification Version 2.0 (1991)
  4. SAE J1939 - Serial Control and Communications Heavy Duty Vehicle Network
  5. ISO 15765-2 - Diagnostic communication over Controller Area Network (DoCAN)

练习题

  1. 计算一个CAN网络的总线负载,已知:波特率500kbps,有3个消息,ID分别为0x100(8字节,10ms)、0x200(4字节,20ms)、0x300(2字节,50ms)

  2. 设计一个CAN过滤器配置,要求接收ID范围0x100-0x1FF的所有消息

  3. 解释为什么CAN总线在汽车中使用差分信号而不是单端信号

下一步:建议学习 OPC UA工业互联协议Modbus工业协议实战