汽车里的“神经网络”——CAN总线科普

一、CAN 是什么?

1986 年由博世发明,最初是为了解决汽车里线束太多太乱的问题。后来扩展到机器人、工业、医疗等领域。

一句话总结:让多个单片机用两根线互相聊天的协议

CAN = 两根线 + 差分信号 + ID 仲裁 + 8 字节数据 + 超强抗干扰

二、CAN 总线特征

CAN总线的特征从硬件和软件层来说可以简要分为以下几点:

|----------------------------------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 层面 | 特征 | 解释 |
| 硬件层(电气传输、抗干扰、布线与终端、速率距离边界、保护/隔离/低功耗) | 差分信号 | 抗干扰能力强,CAN_H 和 CAN_L 两根线,电压一高一低,信号就是CAN_H 和CAN_L的电压差,外界干扰同时影响两根线,相减后干扰抵消,所以汽车/工厂这种电磁环境恶劣的地方也能稳定通讯 |
| 硬件层(电气传输、抗干扰、布线与终端、速率距离边界、保护/隔离/低功耗) | 速率与距离/线缆电容存在硬约束 | 速率越高允许的线长越短;线缆阻抗、电容、节点数、支线长度都会影响波形与采样点可靠性 |
| 硬件层(电气传输、抗干扰、布线与终端、速率距离边界、保护/隔离/低功耗) | 总线拓扑与终端匹配要求明确 * | 典型为线型总线(干线+短支线),两端各 120Ω 终端电阻进行阻抗匹配,减少反射,保证信号完整性 |
| 硬件层(电气传输、抗干扰、布线与终端、速率距离边界、保护/隔离/低功耗) | 收发器决定电气性能与保护能力 | 包括显性/隐性电平驱动能力、共模范围、ESD/浪涌保护、短路保护、欠压保护等 |
| 硬件层(电气传输、抗干扰、布线与终端、速率距离边界、保护/隔离/低功耗) | 可选的低功耗与唤醒能力(取决于器件) | 一些收发器支持 Sleep/Standby、总线唤醒,适合车载低功耗系统 |
| 软件层(消息组织与优先级(ID)、仲裁、错误处理、过滤、位时序配置、上层协议与应用语义) | 多主结构 | 任何节点都能主动发,不需要"主机" |
| 软件层(消息组织与优先级(ID)、仲裁、错误处理、过滤、位时序配置、上层协议与应用语义) | 非破坏式仲裁(优先级抢占) | 多节点同时发送时按位仲裁:ID 数值小的优先级高,优先级高的帧赢得总线,其它节点自动停止并稍后重发;获胜帧不被破坏,实时性好 |
| 软件层(消息组织与优先级(ID)、仲裁、错误处理、过滤、位时序配置、上层协议与应用语义) | 广播通信 + 基于标识符的"内容寻址" | 报文按 ID(标识符)来定义"这是什么数据/优先级",而不是"发给谁"。所有节点都能接收,再由硬件滤波决定是否处理 |
| 软件层(消息组织与优先级(ID)、仲裁、错误处理、过滤、位时序配置、上层协议与应用语义) | 硬件过滤与接收队列(提高效率) | 控制器通常提供 Acceptance Filter/Mask,把不关心的 ID 在硬件层丢掉,可减少 MCU 中断负担 |
| 软件层(消息组织与优先级(ID)、仲裁、错误处理、过滤、位时序配置、上层协议与应用语义) | 位时序可配置但对一致性敏感 | 软件需要配置波特率与采样点(如 Prop/Phase 段、SJW 等概念在控制器里体现);网络中各节点必须匹配,否则误码上升 |
| 软件层(消息组织与优先级(ID)、仲裁、错误处理、过滤、位时序配置、上层协议与应用语义) | 帧类型与载荷限制由协议定义 | 经典 CAN:数据域最多 8 Byte;CAN FD:可到 64 Byte,并可在数据段切换更高比特率(前提是全网支持)。 |
| 软件层(消息组织与优先级(ID)、仲裁、错误处理、过滤、位时序配置、上层协议与应用语义) | 上层协议多样(应用层"怎么用"由软件决定) | 常见如 CANopen、J1939、UDS、自定义信号协议等;这些决定了"分包、心跳、网络管理、诊断服务"等行为。 |
| 软件层(消息组织与优先级(ID)、仲裁、错误处理、过滤、位时序配置、上层协议与应用语义) | 完备的错误检测 | 内置 5 种错误检测机制(CRC校验、帧格式检查、ACK检查、位填充检查、位监控),理论错误率:< 10-11 |
| 软件层(消息组织与优先级(ID)、仲裁、错误处理、过滤、位时序配置、上层协议与应用语义) | 故障隔离 | 每个节点有两个错误计数器: TEC(发送错误计数)、 REC(接收错误计数)、状态机:正常(Error Active) → 错误多了 → 被动(Error Passive) → 再多 → 离线(Bus Off)进入 Bus Off 后: 该节点停止发送,不会拖累整条总线,其他节点正常工作,一个坏节点不会搞垮整个系统 |

简单来说,CAN 总线最重要的特点就是多主广播通信 :任何节点都能发消息,消息用 ID 表达"内容与优先级",全网接收后按过滤处理。同时它通过非破坏性仲裁 + 完整的错误检测/隔离机制保证在复杂电磁环境下仍具备较好的实时性与可靠性,这几个特点正是它在汽车电子领域长期长盛不衰的关键原因。

三、 硬件怎么接?

原理图来源正点原子用户手册

基本组成:

  • MCU/控制器:带 CAN 控制器外设(或外置 CAN 控制器如 MCP2515)
  • CAN 收发器(Transceiver) :把 MCU 的 TXD/RXD 转成总线差分 CANH/CANL(如 TJA1050、SN65HVD230、TJA1042 等)
  • 总线线缆:双绞线(CANH/CANL),必要时带屏蔽

硬件布线关键点:

  • 总线"物理两端"各并一个120Ω****, 跨接在 CANH 和 CANL 之间,尽量不要每个节点都上 120Ω,断电后用万用表量 CANH-CANL:两端都正确上 120Ω 时,整网等效约 60Ω
  • CANH/CANL 不要对调(对调通常直接不通或错误率极高)
  • 优先用干线型(线型)拓扑支线(stub)尽量短双绞线走 CANH/CANL,必要时屏蔽层按规范接地(看整车/工控接地策略)
  • 收发器常有 STB(待机/使能)、SILENTRS(斜率控制)等脚,确保处于正常工作模式,否则会"收得到发不出/发不出去"

四、 软件怎么通信?

硬件是 CAN 通信的物理基础(控制器、收发器、总线布线),但要实现数据交互,核心是通过代码配置 MCU 的 CAN 控制器外设,让其与总线的通信规则(波特率、帧格式等)匹配。简单来说:

  • 硬件解决 "信号怎么传" 的物理层问题;
  • 软件解决 "传什么数据、怎么识别数据" 的协议层 / 应用层问题;
  • 软件配置的前提是:硬件布线正确(120Ω 终端、CANH/CANL 极性、拓扑结构),收发器处于正常工作模式(STB 脚不待机)

我们以最常用的GD32F303 MCU 为例 提供一份完整的 CAN 基础配置代码,覆盖 "GPIO 初始化→CAN 控制器配置→过滤器配置→报文收发" 核心流程,新手可直接复用

复制代码
void can0_init(void)
{
    can_parameter_struct can_param;

    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_CAN0);
    rcu_periph_clock_enable(RCU_AF);

    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_12);
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_11);

    can_deinit(CAN0);
    can_struct_para_init(CAN_INIT_STRUCT, &can_param);

    //参数设置
    can_param.time_triggered        = DISABLE;
    can_param.auto_bus_off_recovery = ENABLE;  //自动总线离线恢复
    can_param.auto_wake_up          = DISABLE;
    can_param.no_auto_retrans       = DISABLE;  //启用自动重传
    can_param.rec_fifo_overwrite    = DISABLE;
    can_param.trans_fifo_order      = ENABLE; // 按先入先出(FIFO) 顺序发送

    //CAN通信模式:静默(Silent);回环(Loopback);回环静默(Loopback and Silent);正常(Normal)
    can_param.working_mode = CAN_NORMAL_MODE; 

    //波特率设置  波特率 = CAN时钟 / (预分频器 × (1 + BS1 + BS2)) 
    //采样点设置  采样点位置 = (1 + BS1) / (1 + BS1 + BS2) 对应本篇配置就是 (1+8)/(1+8+3) = 75% (推荐在75%-87.5%之间)
    can_param.resync_jump_width = CAN_BT_SJW_1TQ;
    can_param.time_segment_1    = CAN_BT_BS1_8TQ; 
    can_param.time_segment_2    = CAN_BT_BS2_3TQ;
    can_param.prescaler         = 10; //分频系数 //60MHz / 10 / (1+8+3) = 500kbps

    can_init(CAN0, &can_param);

    //硬件过滤器设置
    can_filter_parameter_struct filter;

    filter.filter_number      = 0;
    filter.filter_mode        = CAN_FILTERMODE_MASK;
    filter.filter_bits        = CAN_FILTERBITS_32BIT;
    filter.filter_list_high   = 0x0000; 
    filter.filter_list_low    = 0x0000;
    filter.filter_mask_high   = 0x0000;
    filter.filter_mask_low    = 0x0000;
    filter.filter_fifo_number = CAN_FIFO0;
    filter.filter_enable      = ENABLE;

    can_filter_init(&filter);

    can_interrupt_enable(CAN0, CAN_INTEN_RFNEIE0);
    nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
    nvic_irq_enable(CAN0_RX0_IRQn, 1U, 0U);
}

int can_send_standard_frame(uint32_t can_id, uint8_t* data, uint8_t data_len)
{
    can_trasnmit_message_struct tx_message = { can_id, 0, CAN_FF_STANDARD, CAN_FT_DATA, 0, { 0 } };

    if (data == NULL || data_len > sizeof(tx_message.tx_data)) {
        return -1;
    }

    can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx_message);
    tx_message.tx_sfid = can_id;
    tx_message.tx_dlen = data_len;
    memcpy(tx_message.tx_data, data, data_len);
    
    return can_message_transmit(CAN0, &tx_message);
}

/* 中断接收 */
void CAN0_RX_IRQHANDLER(void)
{
    can_receive_message_struct rx_message;
    
    // 检查FIFO0是否有消息
    if (can_receive_message_length_get(CAN0, CAN_FIFO0) > 0) {
        
        // 从FIFO0读取消息
        can_message_receive(CAN0, CAN_FIFO0, &rx_message);
        
        // 处理接收到的数据
        can_receive_handler(&rx_message);
    }
}

/* 发送示例 */
void can_send_example(void)
{
    uint8_t data[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
    
    // 发送ID=0x123的消息,数据长度8字节
    can_send_standard_frame(0x123, data, 8);
}

/* 接收处理示例 */
void can_receive_handler(can_receive_message_struct *rx_msg)
{
    uint32_t id;
    uint8_t  len;
    uint8_t  data[8];
    
    // 提取ID
    if (rx_msg->rx_ff == CAN_FF_STANDARD) {
        id = rx_msg->rx_sfid;  // 标准帧ID
    } else {
        id = rx_msg->rx_efid;  // 扩展帧ID
    }
    len = rx_msg->rx_dlen;
    
    // 提取数据
    for (uint8_t i = 0; i < len; i++) {
        data[i] = rx_msg->rx_data[i];
    }
    // 根据ID处理不同的消息
    switch (id) {
    case 0x100:
        // 处理ID=0x100的消息
        motor_control_handler(data, len); 根据实际情况换成自己的函数
        break;
    case 0x200:
        // 处理ID=0x200的消息
        break;
    default:
        break;
    }
}

五、 常见踩坑&调试技巧

常见踩坑:

硬件层:

  • 终端电阻配置错误------通信时断时续、错误帧频繁出现、波形反射严重
  • CANH/CANL接反 ------ 完全无法通信 、示波器看到反向
  • 收发器供电及MCU供电问题------节点掉线、通信不稳定
  • 收发器未使能(STB引脚)------ 能接收,不能发送或者完全不工作

软件层:

  • 波特率不一致------ 完全收不到
  • 滤波器配置错误------发送正常,接收不到数据或者只能收到部分消息
  • 中断未配置或优先级过低------中断函数不执行、丢帧严重
  • ID冲突------错误计数飙升,进Bus off
  • 发送邮箱满------can_message_transmit() 返回 CAN_NOMAILBOX,发送失败
  • FIFO溢出------丢失接收数据、FIFO溢出标志置位

🛠️调试技巧:

  • 环回模式自测:不需要外部总线,MCU自己发自己收
  • 示波器:看差分波形(正常 2~3V 摆幅)
  • CAN 分析仪:抓包看 ID 和数据(PCAN、CANoe)
  • 错误计数器:TEC/REC 超过 128 就要注意了
相关推荐
爱编码的小八嘎2 小时前
C语言对话-19.新的起点,第一部分
c语言
草莓熊Lotso4 小时前
Linux 基础 IO 初步解析:从 C 库函数到系统调用,理解文件操作本质
linux·运维·服务器·c语言·数据库·c++·人工智能
梵刹古音4 小时前
【C语言】 字符数组相关库函数
c语言·开发语言·算法
2601_9491465310 小时前
C语言语音通知API示例代码:基于标准C的语音接口开发与底层调用实践
c语言·开发语言
Amrzs_hp10 小时前
stm32温度采集
stm32·单片机·嵌入式硬件
学嵌入式的小杨同学10 小时前
从零打造 Linux 终端 MP3 播放器!用 C 语言实现音乐自由
linux·c语言·开发语言·前端·vscode·ci/cd·vim
Aaron158811 小时前
基于RFSOC的数字射频存储技术应用分析
c语言·人工智能·驱动开发·算法·fpga开发·硬件工程·信号处理
爱编码的小八嘎12 小时前
C语言对话-21.模板特化,缺省参数和其他一些有趣的事情
c语言
yueyuexiaokeai113 小时前
linux kernel常用函数整理
linux·c语言