CAN总线协议从入门到实践:手把手教你玩转STM32的CAN通信
2026/8/29 9:16:26 网站建设 项目流程

1. CAN总线协议基础:从物理层到协议层

第一次接触STM32的CAN通信时,我和大多数初学者一样感到一头雾水。这个在汽车电子和工业控制中广泛使用的协议,确实比I2C、SPI这些同步通信复杂得多。但别担心,我会用最接地气的方式带你理解它。

CAN总线最特别的地方在于它采用差分信号传输。想象一下两个人抬轿子——CAN_HIGH和CAN_LOW就像轿子的两根杆子,只有当两根线电压差达到2V时才表示逻辑0(显性电平),0V时表示逻辑1(隐性电平)。这种设计让CAN总线天生具备超强抗干扰能力,我在工厂环境测试时,即便旁边有大型电机干扰,通信依然稳定。

物理层有两种典型拓扑:

  • 闭环总线:像地铁环线,首尾相连。适合短距离(≤40m)高速通信(1Mbps)
  • 开环总线:像公交线路,终端需要120Ω电阻。适合长距离(≤1km)低速通信(125kbps)

协议层才是真正的精髓所在。CAN节点没有主从之分,所有设备平等接入总线。当多个节点同时发送时,通过ID仲裁机制决定谁先说——就像一群人开会,重要的事情(ID值小的帧)会打断不重要的发言。这里有个实用技巧:规划项目时,紧急消息要分配较小的ID值。

2. STM32的CAN外设实战配置

拿到STM32开发板后,我建议先用CubeMX工具快速搭建工程。记得勾选CAN外设后,会自动配置PA11(CAN_RX)和PA12(CAN_TX)引脚。不过有次我忘了接120Ω终端电阻,结果通信距离超过3米就丢包,这个坑大家一定要避开。

工作模式选择很有讲究:

CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack; // 环回模式(自测试) // CAN_Mode_Normal // 正常模式 // CAN_Mode_Silent // 静默模式(只听不发)

波特率配置是新手最容易出错的地方。STM32的位时序分为三段:

  • 同步段(SYNC_SEG):固定1个时间单位(Tq)
  • BS1段:包含传播段和相位缓冲段1
  • BS2段:相位缓冲段2

假设我们使用APB1时钟36MHz,配置如下:

CAN_InitStructure.CAN_SJW = CAN_SJW_1tq; // 同步跳转宽度 CAN_InitStructure.CAN_BS1 = CAN_BS1_9tq; // BS1时间段 CAN_InitStructure.CAN_BS2 = CAN_BS2_8tq; // BS2时间段 CAN_InitStructure.CAN_Prescaler = 4; // 分频系数

此时波特率 = 36MHz / [(1+9+8)*4] = 500Kbps。我在汽车电子项目中常用这个速率,兼顾速度和稳定性。

3. 验收筛选器的妙用

筛选器是CAN通信的"门卫",决定哪些消息能进入FIFO。STM32提供两种工作模式:

  • 标识符列表模式:像白名单,只接收特定ID的消息
  • 掩码模式:像模糊匹配,可设置ID的某些位必须匹配

配置示例(接收标准ID 0x123的消息):

CAN_FilterInitStructure.CAN_FilterNumber = 0; // 使用筛选器0 CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; CAN_FilterInitStructure.CAN_FilterIdHigh = 0x123 << 5; // STDID[10:0]对齐到高位 CAN_FilterInitStructure.CAN_FilterIdLow = 0; CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0xFFFF; // 必须完全匹配 CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;

有个实用技巧:工业设备通常需要接收多个ID,这时可以启用多个筛选器组。我在一个纺织机控制项目中,就用筛选器实现了同时接收电机状态(0x201)和传感器数据(0x301)。

4. 从发送到接收的完整代码实现

先看发送函数,注意标准帧和扩展帧的区别:

u8 Can_Send_Msg(u8* msg, u8 len) { CanTxMsg TxMessage; TxMessage.StdId = 0x123; // 标准ID(11位) TxMessage.ExtId = 0x12345678; // 扩展ID(29位) TxMessage.IDE = CAN_Id_Standard; // 使用标准帧 TxMessage.RTR = CAN_RTR_Data; // 数据帧(非远程帧) TxMessage.DLC = len; // 数据长度(0-8) for(u8 i=0; i<len; i++) TxMessage.Data[i] = msg[i]; u8 mailbox = CAN_Transmit(CAN1, &TxMessage); while(CAN_TransmitStatus(CAN1, mailbox) != CAN_TxStatus_Ok); return 0; }

接收端建议使用中断方式,避免轮询消耗CPU资源:

void USB_LP_CAN1_RX0_IRQHandler(void) { CanRxMsg RxMessage; CAN_Receive(CAN1, CAN_FIFO0, &RxMessage); // 打印接收到的数据 for(u8 i=0; i<RxMessage.DLC; i++){ printf("Data[%d]: %02X\n", i, RxMessage.Data[i]); } }

在main函数中初始化后,就可以测试环回通信了:

int main(void) { u8 canbuf[8] = {0x11, 0x22, 0x33, 0x44}; CAN_Mode_Init(CAN_SJW_1tq, CAN_BS2_8tq, CAN_BS1_9tq, 4, CAN_Mode_LoopBack); while(1){ if(KEY0_Pressed){ // 按下按键发送 Can_Send_Msg(canbuf, 4); } // 中断会自动处理接收 } }

当切换到正常模式时,需要两块开发板互联。记得:

  1. 用双绞线连接CAN_H和CAN_L
  2. 两端都必须接120Ω终端电阻
  3. 确保波特率设置完全一致

我在调试多节点通信时,曾因为一个节点的BS2参数配置不同导致持续出错。后来用逻辑分析仪抓取波形才发现这个问题,所以建议大家在复杂系统中加入错误回调函数:

CAN_ITConfig(CAN1, CAN_IT_ERR, ENABLE); void CAN1_ERR_IRQHandler(void) { if(CAN_GetITStatus(CAN1, CAN_IT_BOF)){ printf("Bus-off error detected!\n"); CAN_ClearITPendingBit(CAN1, CAN_IT_BOF); } }

5. 常见问题排查指南

根据我的项目经验,这些问题最常出现:

通信完全失败:

  • 检查终端电阻(用万用表测量CAN_H-CAN_L间阻值应为60Ω左右)
  • 确认GPIO模式设置正确(PA11:上拉输入,PA12:复用推挽输出)
  • 验证时钟配置(APB1时钟必须使能)

能发送但收不到回复:

  • 检查筛选器配置(先用掩码模式0x0000接收所有消息测试)
  • 确认工作模式(正常模式需要两个节点)
  • 查看CAN状态寄存器(CAN_ESR)

偶发性数据错误:

  • 降低波特率测试(建议从125Kbps开始)
  • 检查总线负载(用CAN分析仪监控错误帧)
  • 优化布线(双绞线长度尽量短,避免星型拓扑)

有个特别隐蔽的坑:STM32的CAN时钟来自APB1,如果系统时钟配置错误,实际波特率会和计算值不符。我建议用这个公式校验:

实际波特率 = APB1时钟 / [(SYNC_SEG + BS1 + BS2) * Prescaler]

最后分享一个高级技巧:在工业现场,可以通过设置自动重传禁用(CAN_NART)来确保关键消息的实时性,但需要配合应用层确认机制。我在数控机床项目中就用这种方法实现了毫秒级的状态同步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询