导读:实际项目调试中,90% 的 CAN 通讯故障来源于硬件问题,仅有小部分是代码配置错误。很多开发者直接上手写收发代码,忽略硬件基础,遇到通讯无响应、高速丢包、波形异常、总线离线等问题无从下手。本章完整讲解 CAN 三层硬件架构、差分电平原理、终端电阻的作用与坑点,并提供可直接移植的硬件故障检测代码。

2.1 CAN 总线三层硬件架构

一套可以稳定运行的 CAN 通讯系统分为三层:MCU‑CAN 控制器层、CAN 收发器层、CAN 总线传输线缆层,三层缺一不可。

2.1.1 MCU 内置 CAN 控制器(协议逻辑层)

STM32、GD32、HC32 等工业 MCU 内部集成硬件 CAN 控制器,不需要外接协议芯片。CAN 控制器负责协议层面全部逻辑工作,由硬件自动完成,CPU 只需要读写寄存器:

  1. 自动组装帧起始、仲裁段、DLC、CRC、应答位等帧结构;
  2. 硬件完成总线仲裁,多节点同时发包时自动优先级竞争;
  3. CRC 校验、错误统计、错误帧处理、报文自动重发;
  4. 提供发送邮箱、接收 FIFO 缓冲区存放报文。

注意:MCU 输出的是 3.3V 单端逻辑信号(CAN_TX、CAN_RX 引脚),驱动能力弱,不能直接长距离走线,必须外接 CAN 收发器芯片

工程实例:STM32F103 的 CAN1,对应引脚 PA11 (CAN_RX)、PA12 (CAN_TX)。MCU 的 CAN_TX 输出逻辑电平给到收发器,收发器把差分信号送到总线上;总线回来的差分信号,经过收发器转换为单端信号送到 MCU 的 CAN_RX。

2.1.2 CAN 收发器芯片(电平转换层)

常见型号:TJA1050、TJA1042、SN65HVD230。 收发器是 MCU 和总线之间的桥梁,两大核心作用:

  1. 电平转换:把 MCU 单端逻辑信号转换成 CAN_H / CAN_L 差分总线电平;反过来,把总线上差分信号转换成 MCU 识别的单端 RX 信号。
  2. 总线驱动与保护:提高总线驱动能力,具备过压保护,防止总线上高压浪涌击穿单片机 IO。

典型接线关系:

  • 收发器 TX 引脚 → MCU CAN_TX
  • 收发器 RX 引脚 → MCU CAN_RX
  • 收发器 CAN_H、CAN_L 引出到总线双绞线
  • VCC 接 5V(TJA1050)或者 3.3V(SN65HVD230)

实操坑点:收发器电源不稳会出现偶发丢包;收发器待机模式(STB 引脚拉高)芯片进入睡眠,会完全无法收发报文,很多新手踩这个坑。

2.1.3 CAN 总线传输线缆层

CAN 总线信号必须使用屏蔽双绞线,两根线命名 CAN_H 和 CAN_L。双绞线互相缠绕可以抵消外界共模电磁干扰;屏蔽层建议单点接地,不要两端同时接地,避免地环路引入干扰。

禁止直接使用普通杜邦线平行走线做长距离 CAN 通讯。平行导线没有抵消干扰能力,在 500K/1M 高速波特率下,极易乱码、丢包。

2.2 CAN 差分电平原理

CAN 总线不看单根导线对地电压,而是看 CAN_H 和 CAN_L 之间的电压差值,分为隐性电平(逻辑 1)显性电平(逻辑 0)。这是抗干扰和硬件仲裁的物理基础。

2.2.1 隐性电平(逻辑 1,总线空闲)

总线空闲时,收发器内部电路将 CAN_H、CAN_L 钳位在约 2.5V。

  • CAN_H ≈ 2.5V
  • CAN_L ≈ 2.5V
  • 差值 CAN_H - CAN_L ≈ 0V 代表逻辑 1,总线处于空闲状态。

隐性电平有一个关键特性:只要任意一个节点输出显性电平,总线就会被强制覆盖为显性电平

2.2.2 显性电平(逻辑 0,总线占用)

当节点发送逻辑 0 时,收发器驱动:

  • CAN_H ≈ 3.5V
  • CAN_L ≈ 1.5V
  • 差值 CAN_H - CAN_L ≈ 2V

代表逻辑 0,显性电平优先级高于隐性电平,这就是 CAN 硬件仲裁的物理根源。

2.2.3 硬件仲裁完整实例

场景:两个节点 A (ID=0x200,优先级低)、节点 B (ID=0x100,优先级高) 同时检测总线空闲,同时启动发送报文。

  1. 总线初始状态:隐性电平(空闲);
  2. 两个节点同时发送 SOF 帧起始位(显性 0),总线被拉到显性,无冲突;
  3. 开始发送 ID 最高位:节点 A 发出逻辑 1(隐性),节点 B 发出逻辑 0(显性);
  4. 总线物理上变成显性电平。节点 A 一边发送,一边监听总线电平,发现自己发的是 1,但总线实际是 0;
  5. 节点 A 立刻停止发送,退出竞争,等待总线下一次空闲;节点 B 不受影响,继续完整发送自己的报文。

整个仲裁全部硬件自动完成,微秒级,软件完全不需要干预。

面试考点:为什么 CAN 可以实现无冲突通讯?本质就是显性电平可以覆盖隐性电平,发送方边发边回读总线电平进行比对。

2.3 终端电阻深度解析

2.3.1 终端电阻原理

高频数字信号在线缆传输末端,如果阻抗不匹配,信号会发生信号反射。反射波叠加原始波形,波形变形、抖动,接收端识别出错,出现乱码丢包。

CAN 总线双绞线标称阻抗 120Ω,因此需要在总线物理两端并联 120Ω 电阻,实现阻抗匹配,吸收反射信号,还原干净波形。

2.3.2 终端电阻硬性规则

  1. 阻值固定为 120Ω,不要随意用 100Ω、150Ω 替代;
  2. 只允许接在整条总线物理最两端设备的 CAN_H 和 CAN_L 之间
  3. 总线中间挂载的设备,禁止焊接 120Ω 终端电阻;
  4. 一条 CAN 总线,最多只能有两个 120Ω 终端电阻。

断电测量总线 CAN_H、CAN_L 之间电阻:两端都接 120Ω 时,并联后测量值约 60Ω,这是判断电阻是否正确的简易手段。

2.3.3 多场景案例

场景 1:点对点两个设备通讯。两个设备分别是总线两端,各焊 120Ω 电阻,测得总线约 60Ω,通讯稳定。 场景 2:多节点组网,一共 5 个节点。1 号节点和 5 号节点在总线首尾,接 120Ω;2、3、4 中间节点不接。 场景 3:临时调试,只有两个设备,手头只有一个 120Ω 电阻,接任意一端也可以短暂通讯,但是高速波特率抗干扰能力下降。

2.3.4 典型故障现象

故障现象:125K 低速波特率通讯正常;切换 500K、1M 波特率大量丢包、偶发通讯失败。 故障根因:缺少终端电阻,低速下信号反射影响小,高速时波形畸变严重。 解决方案:总线两端补上 120Ω 电阻。

拓展坑:接了三个或者四个 120Ω 电阻,总线等效电阻过小,收发器负载过重,同样通讯异常。

2.4 CAN 硬件状态检测完整代码(STM32 HAL 库)

下面代码可以直接放进工程,用于调试阶段实时获取总线错误状态,快速定位硬件故障。

#include "can.h"
#include "stdio.h"

//总线状态 0正常 1总线离线BusOff 2应答错误 3信号/时序错误
uint8_t g_can_bus_state = 0;

/**
 * @brief CAN硬件故障检测,循环调用
 */
void CAN_Hardware_Detect(void)
{
    uint32_t err_code = HAL_CAN_GetError(&hcan);
    HAL_CAN_ClearError(&hcan);

    if(err_code == HAL_CAN_ERROR_NONE)
    {
        g_can_bus_state = 0;
        return;
    }

    if(err_code & HAL_CAN_ERROR_BUSOFF)
    {
        g_can_bus_state = 1;
        printf("[CAN ERR] BusOff总线离线!检查:120Ω电阻、CAN_H/L接线、收发器供电\r\n");
    }
    else if(err_code & HAL_CAN_ERROR_ACK)
    {
        g_can_bus_state = 2;
        printf("[CAN ERR] ACK应答错误!总线上没有接收节点在线\r\n");
    }
    else if((err_code & HAL_CAN_ERROR_BIT) || (err_code & HAL_CAN_ERROR_TIMEOUT))
    {
        g_can_bus_state = 3;
        printf("[CAN ERR]信号异常!检查波特率匹配、线路接触、电磁干扰\r\n");
    }
}

/**
 * @brief 判断CAN发送邮箱是否空闲
 * @retval 1空闲,0繁忙
 */
uint8_t CAN_Check_Bus_Idle(void)
{
    if((hcan.State == HAL_CAN_STATE_READY) && (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) > 0))
    {
        return 1;
    }
    return 0;
}

/**
 * @brief 安全发送封装,发送前检测总线状态
 * @param id 标准ID
 * @param buf 数据缓存
 * @param len 长度0~8
 * @retval 0成功,1总线繁忙,2总线异常
 */
uint8_t CAN_Send_Safe(uint16_t id, uint8_t *buf, uint8_t len)
{
    if(g_can_bus_state != 0)
    {
        return 2;
    }
    if(!CAN_Check_Bus_Idle())
    {
        return 1;
    }

    CAN_TxHeaderTypeDef tx_header;
    uint32_t tx_mailbox;
    tx_header.IDE = CAN_ID_STD;
    tx_header.StdId = id;
    tx_header.RTR = CAN_RTR_DATA;
    tx_header.DLC = len;
    tx_header.TransmitGlobalTime = DISABLE;

    if(HAL_CAN_AddTxMessage(&hcan, &tx_header, buf, &tx_mailbox) == HAL_OK)
    {
        return 0;
    }
    return 1;
}

使用说明

  1. 主循环周期调用CAN_Hardware_Detect()
  2. 业务发送优先调用CAN_Send_Safe(),避免总线异常状态下反复调用底层发送函数,加重报错;
  3. 出现 BusOff 时,硬件上优先排查接线、终端电阻、收发器供电。

第二章到此结束。

 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐