2. STM32 CAN外设入门:bxCAN控制器介绍、CAN引脚配置、波特率计算、过滤器配置、中断与轮询模式
好,咱们今天来啃一块硬骨头——STM32的CAN外设。说实话,我刚接触CAN总线那会儿,最头疼的就是这个bxCAN控制器。名字听着挺唬人,什么"基本扩展CAN",其实就是ST在F1/F4系列上标配的CAN模块。你把它想成一个独立的通信小管家就行,你只管把数据丢给它,它自己搞定总线仲裁、错误重发这些脏活累活。
2.1 bxCAN控制器长什么样?
bxCAN的核心结构,我习惯把它拆成三块来看:
- 协议控制器:负责物理层到数据链路层的转换,说白了就是处理CAN帧的收发时序
- 报文缓冲区:3个发送邮箱 + 2个接收FIFO(每个FIFO深3级),这是数据的中转站
- 过滤器组:最多28个(F103系列),用来决定哪些报文能进你的接收缓冲区
我个人觉得,理解bxCAN的关键在于"邮箱"这个概念。发送邮箱就像三个并排的信箱,你把信塞进去,硬件自动帮你寄出去。接收FIFO则像两个收件箱,硬件收到信后按顺序放好,你按时去取就行。
核心要点:bxCAN支持CAN 2.0A和2.0B协议,标准帧11位ID,扩展帧29位ID。速率最高1Mbps,但实际项目中我一般用到500kbps或250kbps,稳定性更好。
2.2 CAN引脚配置——别接错了
STM32的CAN引脚是固定的,不能随便映射。以F103为例:
- CAN1_TX:PA12(默认),也可重映射到PB9、PD1
- CAN1_RX:PA11(默认),也可重映射到PB8、PD0
我曾经犯过一个低级错误:把CAN_TX和CAN_RX接反了。结果总线死活没反应,查了半天才发现。记住:TX接收发器的TX,RX接收发器的RX,别交叉!
配置代码其实很简单,我一般这样写:
// CAN GPIO 初始化
void CAN_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
// TX - 复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// RX - 浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
嗯,这里要注意:TX必须是复用推挽输出,RX必须是浮空输入。别问我为什么,CAN收发器就是这么要求的。
2.3 波特率计算——算不对就白搭
CAN的波特率计算,说白了就是分频。STM32的CAN挂在APB1总线上,时钟一般是36MHz(F103)或42MHz(F4系列)。
计算公式:
波特率 = CAN时钟 / (BRP + 1) / (tBS1 + tBS2 + 1)
其中:
- BRP:波特率预分频器(0~127)
- tBS1:时间段1(1~16)
- tBS2:时间段2(1~8)
- 同步段固定为1个时间量子
举个例子,我要配500kbps:
CAN时钟 = 36MHz
目标波特率 = 500kbps
36MHz / 500kbps = 72 个时间量子
72 = (BRP+1) * (tBS1 + tBS2 + 1)
取 BRP = 3,则 (BRP+1) = 4
72 / 4 = 18 = tBS1 + tBS2 + 1
取 tBS1 = 13, tBS2 = 4,则 13+4+1=18 ✓
我个人习惯:采样点设置在87.5%左右。公式是:采样点 = (1 + tBS1) / (1 + tBS1 + tBS2)。上面例子中采样点 = (1+13)/(1+13+4) = 14/18 ≈ 77.8%,其实偏低。我会调整成tBS1=14, tBS2=3,采样点=15/18=83.3%,更稳妥。
| 目标波特率 | CAN时钟 | BRP | tBS1 | tBS2 | 实际波特率 | 采样点 |
|---|---|---|---|---|---|---|
| 1Mbps | 36MHz | 1 | 8 | 8 | 1.0Mbps | 50.0% |
| 500kbps | 36MHz | 3 | 14 | 3 | 500kbps | 83.3% |
| 250kbps | 36MHz | 7 | 14 | 3 | 250kbps | 83.3% |
| 125kbps | 36MHz | 15 | 14 | 3 | 125kbps | 83.3% |
避坑指南:同一个CAN网络上的所有节点,波特率必须完全一致。我曾经调试一个系统,两个设备都配了500kbps,但一个采样点设成80%,另一个设成87.5%,结果偶尔丢帧。折腾了两天才发现是采样点不匹配。
2.4 过滤器配置——别让垃圾数据进来
过滤器是bxCAN最强大的功能之一。说白了,就是硬件级别的"白名单"。只有符合过滤条件的报文,才能进入接收FIFO。
过滤器有两种工作模式:
- 标识符列表模式:只接收指定ID的报文,精确匹配
- 标识符掩码模式:接收符合掩码范围的报文,模糊匹配
举个例子,我只想接收ID为0x321和0x322的报文:
// 过滤器配置 - 列表模式,接收0x321和0x322
void CAN_Filter_Config(void)
{
CAN_FilterInitTypeDef CAN_FilterInitStructure;
CAN_FilterInitStructure.CAN_FilterNumber = 0; // 使用过滤器组0
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList; // 列表模式
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; // 32位
// 标准帧ID存储在[28:18]位
CAN_FilterInitStructure.CAN_FilterIdHigh = (0x321 << 5) >> 16; // 高16位
CAN_FilterInitStructure.CAN_FilterIdLow = (0x321 << 5) | CAN_ID_STD;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = (0x322 << 5) >> 16;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = (0x322 << 5) | CAN_ID_STD;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0; // 关联到FIFO0
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
}
我建议:在项目初期,先用掩码模式把过滤器设成"全通"(掩码全0),等通信调通了再细化过滤规则。这样可以先排除过滤器配置错误导致的通信问题。
2.5 中断与轮询模式——什么时候用哪个?
这个问题,说白了就是"你愿不愿意等"。轮询模式就是CPU一直问"有消息吗?有消息吗?",中断模式就是"有消息了你叫我"。
轮询模式:
// 轮询接收
uint8_t CAN_Polling_Receive(CAN_TypeDef* CANx, CanRxMsg* RxMessage)
{
uint32_t timeout = 0xFFFF;
// 等待FIFO非空
while (CAN_MessagePending(CANx, CAN_FIFO0) == 0)
{
timeout--;
if (timeout == 0) return 0; // 超时返回
}
CAN_Receive(CANx, CAN_FIFO0, RxMessage);
return 1;
}
中断模式:
// 使能接收中断
CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE); // FIFO0消息挂起中断
// 中断服务函数
void USB_LP_CAN1_RX0_IRQHandler(void)
{
CanRxMsg RxMessage;
if (CAN_GetITStatus(CAN1, CAN_IT_FMP0) != RESET)
{
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
// 处理接收到的数据
CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);
}
}
我的经验:运动控制场景下,我强烈建议用中断模式。轮询会占用CPU时间,导致控制周期抖动。特别是当CAN报文频率高时(比如1ms发送一次),轮询模式基本不可用。中断模式虽然配置麻烦点,但实时性好太多。
不过话说回来,如果你只是做简单的数据采集,比如温度传感器每秒上报一次,轮询模式完全够用,代码还简单。别为了用中断而用中断,合适才是最好的。
好了,bxCAN的基础配置就这些。你把这些搞明白了,CAN通信的基本骨架就有了。下一节咱们开始写真正的CANopen协议栈,那才是重头戏。
公众号:蓝海资料掘金营,微信deep3321