4. STM32 CAN初始化配置:时钟使能、GPIO配置、初始化结构体与位时间
好,咱们进入CAN通信的实操环节了。说实话,很多初学者一上来就对着寄存器猛写,结果板子一点反应都没有。我当年也干过这事,折腾了两天才发现是时钟没开。所以这一章,咱们把初始化流程拆开揉碎了讲清楚。
4.1 时钟使能——别让CAN模块睡大觉
STM32的外设要工作,第一步就是给时钟。CAN挂在APB1总线上,所以我们要使能CAN的时钟。同时,CAN使用的是GPIO的复用功能,对应的GPIO端口时钟也得打开。
我个人习惯把时钟使能放在初始化函数的最前面。你想想看,如果时钟都没开,后面配置寄存器全是白搭。
// 使能CAN1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
// 使能GPIO时钟(以PA11、PA12为例)
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
我在项目中遇到过一个问题:明明代码逻辑没问题,CAN就是收不到数据。查了半天,发现是GPIO时钟没使能。嗯,这种低级错误,犯过一次就记住了。
4.2 GPIO配置——复用功能要选对
CAN的TX和RX引脚,必须配置为复用功能模式。以STM32F4为例,CAN1的默认引脚是PA11(RX)和PA12(TX)。
配置要点有三个:
- 模式设为复用功能(AF)
- 输出类型推挽
- 上拉/下拉一般选无,或者根据硬件设计来
GPIO_InitTypeDef GPIO_InitStructure;
// 配置PA11为CAN1_RX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置PA12为CAN1_TX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 将PA11、PA12连接到CAN的复用功能AF9
GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_CAN1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource12, GPIO_AF_CAN1);
我曾经帮一个同事排查问题,他配置了GPIO但忘了调用GPIO_PinAFConfig。结果TX引脚一直输出高电平,CAN总线直接报错。说白了,复用功能没连上,外设和引脚之间是断开的。
4.3 CAN初始化结构体——核心配置都在这里
STM32的CAN外设初始化,是通过一个结构体来完成的。这个结构体叫CAN_InitTypeDef,里面包含了CAN工作模式、位时间参数、过滤器设置等。
咱们先看一个标准的初始化代码:
CAN_InitTypeDef CAN_InitStructure;
CAN_InitStructure.CAN_TTCM = DISABLE; // 时间触发通信模式,一般不用
CAN_InitStructure.CAN_ABOM = ENABLE; // 自动离线管理,建议开启
CAN_InitStructure.CAN_AWUM = ENABLE; // 自动唤醒模式
CAN_InitStructure.CAN_NART = DISABLE; // 非自动重传,关闭表示自动重传
CAN_InitStructure.CAN_RFLM = DISABLE; // 接收FIFO锁定模式
CAN_InitStructure.CAN_TXFP = DISABLE; // 发送优先级由标识符决定
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; // 正常模式
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq; // 同步跳转宽度
CAN_InitStructure.CAN_BS1 = CAN_BS1_8tq; // 时间段1
CAN_InitStructure.CAN_BS2 = CAN_BS2_7tq; // 时间段2
CAN_InitStructure.CAN_Prescaler = 6; // 分频系数
CAN_Init(CAN1, &CAN_InitStructure);
这里有几个关键点,我挨个说一下:
- CAN_ABOM:自动离线管理。当CAN控制器因为错误太多进入离线状态时,硬件会自动恢复。我建议开启,省得你手动处理。
- CAN_NART:非自动重传。如果关闭(即允许自动重传),当发送失败时硬件会自动重发。一般保持关闭。
- CAN_Mode:有Normal、Loopback、Silent、Silent_Loopback四种。调试时常用Loopback模式,自收自发。
重点:CAN_Mode在调试阶段建议先用Loopback模式。这样不需要接其他节点,自己发自己收,能快速验证硬件和配置是否正确。
4.4 位时间配置——决定通信速率的关键
CAN的通信速率由位时间决定。位时间 = 1个Tq(时间量子)的时长 × 位时间长度(Tq个数)。而Tq的时长 = CAN时钟周期 × 分频系数。
公式其实很简单:
波特率 = CAN时钟频率 / (分频系数 × (1 + BS1 + BS2))
举个例子,假设CAN时钟是36MHz(APB1时钟),目标波特率500kbps:
- 分频系数 = 6
- BS1 = 8 Tq
- BS2 = 7 Tq
- 位时间总长度 = 1 (同步段) + 8 + 7 = 16 Tq
- 波特率 = 36MHz / (6 × 16) = 375kbps
嗯?算出来不对?别急,我故意写了个错误示例。实际计算要保证结果等于目标值。咱们重新算:
目标500kbps,CAN时钟36MHz:
500000 = 36000000 / (Prescaler × (1 + BS1 + BS2))
Prescaler × (1 + BS1 + BS2) = 72
常见的组合:Prescaler=4, BS1=8, BS2=7 → 4 × 16 = 64,不对。Prescaler=6, BS1=8, BS2=3 → 6 × 12 = 72,对了!
| 目标波特率 | CAN时钟 | 分频系数 | BS1 | BS2 | SJW |
|---|---|---|---|---|---|
| 1 Mbps | 36 MHz | 3 | 8 Tq | 3 Tq | 1 Tq |
| 500 kbps | 36 MHz | 6 | 8 Tq | 3 Tq | 1 Tq |
| 250 kbps | 36 MHz | 12 | 8 Tq | 3 Tq | 1 Tq |
| 125 kbps | 36 MHz | 24 | 8 Tq | 3 Tq | 1 Tq |
4.5 完整的初始化流程
把上面几部分串起来,就是一个完整的CAN初始化函数:
void CAN_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
// 2. 配置GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_CAN1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource12, GPIO_AF_CAN1);
// 3. 配置CAN
CAN_DeInit(CAN1);
CAN_StructInit(&CAN_InitStructure);
CAN_InitStructure.CAN_TTCM = DISABLE;
CAN_InitStructure.CAN_ABOM = ENABLE;
CAN_InitStructure.CAN_AWUM = ENABLE;
CAN_InitStructure.CAN_NART = DISABLE;
CAN_InitStructure.CAN_RFLM = DISABLE;
CAN_InitStructure.CAN_TXFP = DISABLE;
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_8tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
CAN_InitStructure.CAN_Prescaler = 6;
CAN_Init(CAN1, &CAN_InitStructure);
}
个人经验:初始化完成后,建议先读一下CAN的寄存器状态。比如检查CAN_MSR寄存器的INAK位,确认CAN已经退出初始化模式。如果卡在初始化模式出不来,八成是时钟或GPIO配置有问题。
好了,这一章的内容就到这里。说白了,CAN初始化就是三步走:开时钟、配引脚、设参数。每一步都有坑,但踩过一次就记住了。下一章咱们聊过滤器配置,那个才是CAN通信的灵魂所在。