第二章:CAN 总线硬件底层原理
导读:实际项目调试中,90% 的 CAN 通讯故障来源于硬件问题,仅有小部分是代码配置错误。很多开发者直接上手写收发代码,忽略硬件基础,遇到通讯无响应、高速丢包、波形异常、总线离线等问题无从下手。本章完整讲解 CAN 三层硬件架构、差分电平原理、终端电阻的作用与坑点,并提供可直接移植的硬件故障检测代码。
2.1 CAN 总线三层硬件架构
一套可以稳定运行的 CAN 通讯系统分为三层:MCU‑CAN 控制器层、CAN 收发器层、CAN 总线传输线缆层,三层缺一不可。
2.1.1 MCU 内置 CAN 控制器(协议逻辑层)
STM32、GD32、HC32 等工业 MCU 内部集成硬件 CAN 控制器,不需要外接协议芯片。CAN 控制器负责协议层面全部逻辑工作,由硬件自动完成,CPU 只需要读写寄存器:
- 自动组装帧起始、仲裁段、DLC、CRC、应答位等帧结构;
- 硬件完成总线仲裁,多节点同时发包时自动优先级竞争;
- CRC 校验、错误统计、错误帧处理、报文自动重发;
- 提供发送邮箱、接收 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 和总线之间的桥梁,两大核心作用:
- 电平转换:把 MCU 单端逻辑信号转换成 CAN_H / CAN_L 差分总线电平;反过来,把总线上差分信号转换成 MCU 识别的单端 RX 信号。
- 总线驱动与保护:提高总线驱动能力,具备过压保护,防止总线上高压浪涌击穿单片机 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,优先级高) 同时检测总线空闲,同时启动发送报文。
- 总线初始状态:隐性电平(空闲);
- 两个节点同时发送 SOF 帧起始位(显性 0),总线被拉到显性,无冲突;
- 开始发送 ID 最高位:节点 A 发出逻辑 1(隐性),节点 B 发出逻辑 0(显性);
- 总线物理上变成显性电平。节点 A 一边发送,一边监听总线电平,发现自己发的是 1,但总线实际是 0;
- 节点 A 立刻停止发送,退出竞争,等待总线下一次空闲;节点 B 不受影响,继续完整发送自己的报文。
整个仲裁全部硬件自动完成,微秒级,软件完全不需要干预。
面试考点:为什么 CAN 可以实现无冲突通讯?本质就是显性电平可以覆盖隐性电平,发送方边发边回读总线电平进行比对。
2.3 终端电阻深度解析
2.3.1 终端电阻原理
高频数字信号在线缆传输末端,如果阻抗不匹配,信号会发生信号反射。反射波叠加原始波形,波形变形、抖动,接收端识别出错,出现乱码丢包。
CAN 总线双绞线标称阻抗 120Ω,因此需要在总线物理两端并联 120Ω 电阻,实现阻抗匹配,吸收反射信号,还原干净波形。
2.3.2 终端电阻硬性规则
- 阻值固定为 120Ω,不要随意用 100Ω、150Ω 替代;
- 只允许接在整条总线物理最两端设备的 CAN_H 和 CAN_L 之间;
- 总线中间挂载的设备,禁止焊接 120Ω 终端电阻;
- 一条 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;
}
使用说明
- 主循环周期调用
CAN_Hardware_Detect(); - 业务发送优先调用
CAN_Send_Safe(),避免总线异常状态下反复调用底层发送函数,加重报错; - 出现 BusOff 时,硬件上优先排查接线、终端电阻、收发器供电。
第二章到此结束。
更多推荐


所有评论(0)