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);
注意:不同型号的STM32,CAN挂载的总线可能不同。比如F1系列CAN挂在APB1,F4/H7系列也是APB1。但有些新系列可能变了,务必查阅数据手册。

我在项目中遇到过一个问题:明明代码逻辑没问题,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);
小技巧:复用功能编号(AF号)因芯片而异。F4系列CAN1是AF9,F1系列不需要这个步骤。建议直接查参考手册的“Alternate function mapping”表格。

我曾经帮一个同事排查问题,他配置了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
避坑指南:我曾经在一个项目里,把BS1和BS2设反了。结果CAN通信时好时坏,偶尔还会报错。后来用示波器一看,采样点位置不对。记住,BS1通常大于等于BS2,采样点在BS1和BS2的交界处。

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通信的灵魂所在。