一、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(待机/使能)、SILENT、RS(斜率控制)等脚,确保处于正常工作模式,否则会"收得到发不出/发不出去"
四、 软件怎么通信?
硬件是 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 就要注意了