3. STM32 HAL库与CAN外设初始化:从CubeMX到实战
好,咱们进入第三章。这一章我打算手把手带你走一遍STM32的CAN外设初始化流程。说白了,就是让芯片的CAN控制器能正常收发报文。很多初学者卡在这一步,要么波特率算不对,要么过滤器设置错了导致收不到数据。别急,咱们一个一个来拆。
3.1 STM32CubeMX配置CAN外设
我个人习惯先用CubeMX生成工程框架,再手动微调。这样省事,也不容易漏掉时钟使能这类基础配置。
打开CubeMX,选好你的芯片型号(比如STM32F103C8T6或STM32F407VET6)。在Pinout & Configuration界面,找到Connectivity分类下的CAN1(或CAN2)。勾选激活,界面会变成这样:
- Mode:选Normal模式。Loopback是回环测试用,Silent是只听不发,咱们做从站NMT通信,用Normal。
- Basic Parameters:这里设置波特率预分频器、时间段1、时间段2和同步跳转宽度。具体怎么算,我后面单独讲。
- Advanced Parameters:自动重传、自动唤醒这些,保持默认就行。我一般只关掉自动重传,方便调试时看到总线错误。
3.2 CAN过滤器设置——别让无关报文打扰你
CAN总线上的报文很多,但你的从站只关心特定ID的报文。过滤器就是干这个的——把不想要的报文挡在门外,减轻CPU负担。
STM32的CAN控制器有28个过滤器组(F1系列是14个)。每个过滤器组可以配置为:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 标识符列表模式 | 只接收与预设ID完全匹配的报文 | 从站只监听自己的NMT、PDO、SCO对象 |
| 标识符掩码模式 | 接收与预设ID按掩码匹配的报文 | 需要接收一组连续ID的报文(如多个PDO) |
| 32位模式 | 一个过滤器组处理一个32位ID(标准+扩展) | 标准CAN 2.0A/B混合使用 |
| 16位模式 | 一个过滤器组拆成两个16位过滤器 | 只使用标准ID(11位)时更高效 |
我在项目中做CANopen从站时,通常这样配:
- 用32位掩码模式,一个过滤器组就够了。
- 掩码设为0xFFFFFFF0,意思是只看高28位,低4位忽略。
- 这样就能同时接收NMT(ID 0x000)、SYNC(ID 0x080)、EMCY(ID 0x080 + 节点ID)等关键报文。
3.3 中断优先级配置——别让CAN中断被其他任务饿死
CAN通信对实时性有要求,尤其是NMT状态机切换和紧急报文处理。中断优先级配不好,轻则丢帧,重则总线离线。
在CubeMX的NVIC设置里,找到CAN1 RX0中断(或CAN1 RX1中断,看你用哪个FIFO)。我建议:
- 抢占优先级:设为1或2(数值越小优先级越高)。别设成0,因为0通常留给系统滴答定时器或硬故障处理。
- 子优先级:设为0。
- 如果同时用了CAN的TX中断和SCE(状态改变)中断,也一并使能,优先级保持一致。
为什么会这样配?你想想看,CAN中断来了,你要在中断服务函数里把报文读到FIFO,然后置一个标志位,让主循环去处理。如果中断被更高优先级的任务长时间阻塞,FIFO可能溢出,报文就丢了。CANopen的NMT报文丢了,从站可能一直卡在Pre-operational状态上不来。
3.4 CAN波特率计算——算不对就通不了
波特率是CAN通信的基础。同一总线上所有节点必须用相同的波特率,否则谁也收不到谁的数据。
STM32的CAN波特率计算公式:
波特率 = APB时钟频率 / (预分频器 × (时间段1 + 时间段2 + 1))
其中:
- APB时钟频率:F1系列通常是36MHz(APB1),F4系列是42MHz或84MHz。
- 预分频器:BRP寄存器值,范围1~1024。
- 时间段1:BS1,范围1~16个时间量子。
- 时间段2:BS2,范围1~8个时间量子。
- 同步段:固定为1个时间量子,公式里已经隐含了。
举个例子,我要配250kbps的波特率,APB1时钟是36MHz:
36MHz / (预分频器 × (BS1 + BS2 + 1)) = 250kbps
36,000,000 / (预分频器 × (BS1 + BS2 + 1)) = 250,000
预分频器 × (BS1 + BS2 + 1) = 144
我常用的组合:
| 目标波特率 | APB1时钟 | 预分频器 | BS1 | BS2 | 采样点位置 |
|---|---|---|---|---|---|
| 1 Mbps | 36 MHz | 4 | 6 | 2 | 87.5% |
| 500 kbps | 36 MHz | 8 | 6 | 2 | 87.5% |
| 250 kbps | 36 MHz | 16 | 6 | 2 | 87.5% |
| 125 kbps | 36 MHz | 32 | 6 | 2 | 87.5% |
3.5 核心逻辑:CAN初始化流程总览
说了这么多,咱们用一张图把整个初始化流程串起来。这张图我画了好几次才满意,你一看就明白每一步在干什么。
这张图把6个步骤串起来了。你按这个顺序走,基本不会漏。我刚开始做CAN开发时,经常忘了使能CAN时钟就急着配GPIO,结果引脚死活拉不出波形。后来养成了习惯:先看时钟树,再动外设。
3.6 实战代码片段:HAL库初始化示例
下面是我在一个实际项目中用过的初始化代码,基于STM32F103,APB1时钟36MHz,目标波特率250kbps。你直接参考这个结构改参数就行。
/* CAN句柄定义 */
CAN_HandleTypeDef hcan1;
/* CAN过滤器配置结构体 */
CAN_FilterTypeDef sFilterConfig;
/* CAN初始化函数 */
void MX_CAN1_Init(void)
{
hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 16; /* 预分频器 */
hcan1.Init.Mode = CAN_MODE_NORMAL; /* 正常模式 */
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; /* 同步跳转宽度 */
hcan1.Init.TimeSeg1 = CAN_BS1_6TQ; /* BS1 = 6个时间量子 */
hcan1.Init.TimeSeg2 = CAN_BS2_2TQ; /* BS2 = 2个时间量子 */
hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = DISABLE;
hcan1.Init.AutoWakeUp = DISABLE;
hcan1.Init.AutoRetransmission = DISABLE; /* 关掉自动重传,方便调试 */
hcan1.Init.ReceiveFifoLocked = DISABLE;
hcan1.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan1) != HAL_OK)
{
Error_Handler(); /* 初始化失败,进错误处理 */
}
/* 配置过滤器:32位掩码模式,接收所有ID */
sFilterConfig.FilterBank = 0;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000; /* 掩码全0,接收所有报文 */
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
sFilterConfig.SlaveStartFilterBank = 14; /* 双CAN时使用 */
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
/* 使能CAN中断 */
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
/* 启动CAN */
HAL_CAN_Start(&hcan1);
}