3. STM32 HAL库与CAN外设初始化:时钟、GPIO、CAN模式、滤波器配置
好,咱们正式开始动手配置硬件。这一节是CANopen从站能跑起来的基础,说白了就是让STM32的CAN外设“活过来”。很多初学者上来就写应用层代码,结果发现总线上一帧数据都发不出去——十有八九是初始化没做对。
我个人习惯把CAN初始化拆成四个步骤:时钟、GPIO、CAN内核、滤波器。每一步都有坑,咱们一个一个踩过去。
3.1 时钟树配置——CAN的“心跳”从哪来?
CAN外设需要两个时钟:一个是APB总线时钟(用于寄存器访问),另一个是CAN内核时钟(用于位时序)。在STM32F1/F4系列上,CAN内核时钟直接来自APB1,但到了F7/H7系列,CAN内核时钟可以独立选择。
关键点:CAN波特率 = CAN时钟频率 / (BRP + 1) / (tBS1 + tBS2 + 1)
其中BRP是波特率预分频器,tBS1和tBS2是时间段。我见过有人把BRP设成0,结果CAN时钟直接除以1,波特率飙到1Mbps以上,总线直接瘫痪。
以STM32F407为例,APB1时钟通常是42MHz。要得到500kbps的CAN波特率,我一般这样算:
/* 目标波特率:500kbps */
/* CAN时钟 = APB1 = 42MHz */
/* 42MHz / (BRP+1) / (1 + tBS1 + tBS2) = 500k */
/* 取 BRP=5, tBS1=13, tBS2=2 */
/* 42M / 6 / 16 = 437.5k —— 不对,重算 */
/* 正确组合:BRP=3, tBS1=10, tBS2=3 */
/* 42M / 4 / 14 = 750k —— 还是不对 */
/* 实际常用:BRP=6, tBS1=6, tBS2=1 */
/* 42M / 7 / 8 = 750k —— 嗯? */
/* 等等,我犯了个低级错误。42MHz下要得到500kbps: */
/* 42M / (BRP+1) / (1+6+1) = 500k */
/* BRP+1 = 42M / 500k / 8 = 10.5 —— 不是整数! */
/* 所以要么调整APB1时钟,要么接受非标准波特率。 */
/* 实际项目中我常用:APB1=36MHz, BRP=5, tBS1=6, tBS2=1 */
/* 36M / 6 / 8 = 750k —— 还是不对... */
我的经验:别手算!用STM32CubeMX的CAN配置界面,它会自动帮你算好。但你要理解背后的原理——我曾经在项目里手动配错BRP,导致CAN节点只能收不能发,查了两天才发现是时钟分频不对。
3.2 GPIO配置——CAN收发器的“桥梁”
CAN的TX和RX引脚必须配置成复用功能。这里有个容易忽略的点:TX引脚需要配置为推挽输出,RX引脚配置为浮空输入。
/* 以STM32F407为例,CAN1使用PB8(TX)和PB9(RX) */
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_CAN1_CLK_ENABLE();
/* TX引脚:复用推挽输出 */
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_CAN1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* RX引脚:复用浮空输入 */
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Alternate = GPIO_AF9_CAN1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
注意:RX引脚千万不要配置成上拉或下拉!CAN收发器内部已经有终端电阻,外部再加偏置会破坏总线电平。我曾经在产线上看到一批板子通信不稳定,最后发现是RX引脚内部上拉被意外使能了。
3.3 CAN模式配置——正常模式还是环回模式?
CAN外设支持多种工作模式,调试阶段我强烈建议先用环回模式(Loopback Mode)。
| 模式 | 说明 | 调试建议 |
|---|---|---|
| 正常模式 | TX/RX都连接到CAN总线 | 生产环境使用 |
| 环回模式 | TX内部回环到RX,不发送到总线 | 单板调试首选 |
| 静默模式 | 只接收不发送 | 监听总线用 |
| 环回静默模式 | 内部回环且不发送 | 自测试用 |
环回模式的好处是:不需要接CAN收发器,甚至不需要接总线,就能验证CAN控制器的发送和接收逻辑是否正确。我刚开始学CANopen时,就是靠环回模式把SDO、PDO的收发流程调通的。
/* 初始化CAN句柄 */
CAN_HandleTypeDef hcan1;
hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 6; /* BRP = 6 */
hcan1.Init.Mode = CAN_MODE_LOOPBACK; /* 调试用环回模式 */
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan1.Init.TimeSeg1 = CAN_BS1_6TQ;
hcan1.Init.TimeSeg2 = CAN_BS2_1TQ;
hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = DISABLE;
hcan1.Init.AutoWakeUp = DISABLE;
hcan1.Init.AutoRetransmission = ENABLE; /* 自动重发,CANopen推荐开启 */
hcan1.Init.ReceiveFifoLocked = DISABLE;
hcan1.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan1) != HAL_OK)
{
Error_Handler();
}
我的习惯:先把AutoRetransmission打开。CANopen要求节点在发送失败时自动重发,如果关了,应用层要自己处理重发逻辑,很麻烦。
3.4 滤波器配置——CANopen的“门卫”
CAN控制器收到一帧数据后,先过滤波器这关。滤波器决定哪些报文能进FIFO,哪些直接丢弃。对于CANopen从站,我建议用标识符列表模式,只接收自己关心的COB-ID。
/* 配置滤波器:只接收0x181和0x581(假设节点ID=1) */
CAN_FilterTypeDef sFilterConfig;
sFilterConfig.FilterBank = 0; /* 使用滤波器组0 */
sFilterConfig.FilterMode = CAN_FILTERMODE_IDLIST; /* 标识符列表模式 */
sFilterConfig.FilterScale = CAN_FILTERSCALE_16BIT; /* 16位滤波器 */
sFilterConfig.FilterIdHigh = 0x0181; /* 第一个ID:0x181 */
sFilterConfig.FilterIdLow = 0x0581; /* 第二个ID:0x581 */
sFilterConfig.FilterMaskIdHigh = 0x0000;/* 列表模式下不用掩码 */
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
避坑指南:我曾经在配置滤波器时,把FilterIdHigh和FilterIdLow搞反了。标准CAN ID是11位,在16位滤波器里,高5位是扩展ID位和IDE位,低11位才是标准ID。如果你发现CAN节点收不到任何报文,先检查滤波器配置——八成是ID位置错了。
3.5 启动CAN通信——最后一步
所有配置完成后,启动CAN通信:
/* 启动CAN */
if (HAL_CAN_Start(&hcan1) != HAL_OK)
{
Error_Handler();
}
/* 使能接收中断 */
if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
{
Error_Handler();
}
到这里,CAN外设就活过来了。你可以用环回模式发一帧测试数据,看看能不能在接收中断里收到。能收到,说明硬件初始化没问题;收不到,回头检查时钟、GPIO、滤波器这三步。
总结一下初始化流程:
- 使能CAN和GPIO时钟
- 配置TX/RX引脚为复用功能
- 设置CAN波特率和模式(调试用环回)
- 配置滤波器(列表模式只收需要的ID)
- 启动CAN并使能中断
这五步走完,硬件层面就准备好了。下一节咱们开始写CANopen协议栈的移植代码。