3. STM32 HAL库与CAN外设初始化:时钟、GPIO、CAN滤波器配置

好,咱们正式开始动手配置硬件。这一节是后面所有调试工作的地基——CAN外设初始化。很多新手上来就写应用层代码,结果收发都不通,最后发现是时钟分频配错了,或者滤波器把报文全拦了。嗯,咱们一步步来,把底打牢。

3.1 时钟树:CAN外设的“心跳”

CAN外设需要两个时钟:一个是APB总线时钟(用于寄存器访问),另一个是CAN内核时钟(用于位时序)。在STM32上,CAN内核时钟通常来自APB1,但具体路径要看芯片型号。

关键点:CAN波特率 = CAN时钟频率 / (BRP + 1) / (tBS1 + tBS2 + 1)

其中BRP是波特率预分频器,tBS1和tBS2是时间段。配错了,总线就起不来。

我个人习惯用STM32CubeMX先把时钟树搭好。比如F103系列,APB1最高36MHz,CAN挂在这上面。如果你用F4或H7,APB1可以到42MHz甚至更高。但注意——CAN外设本身有最大时钟限制,F103的CAN最高36MHz,F4的CAN最高42MHz,别超了。

我在项目中遇到过一个问题:同事把APB1配到72MHz,CAN分频器设了2,结果BRP算出来是小数,CAN怎么都同步不上。后来查了半天才发现是时钟源选错了。所以,配时钟时一定要确认CAN的时钟源路径。

小技巧:用CubeMX的“Clock Configuration”页面,点一下CAN外设的时钟树分支,它会自动显示有效范围。别手写寄存器去配,容易漏。

3.2 GPIO:CAN收发器的“桥梁”

CAN的TX和RX引脚必须映射到正确的AF(复用功能)上。以F103为例,CAN1的TX在PA12,RX在PA11。但很多开发板会重新映射到PB8/PB9或PD0/PD1,你得看原理图。

配置GPIO时,有几点容易踩坑:

  • TX引脚:推挽输出,复用功能,速度建议设为High(50MHz)。
  • RX引脚:浮空输入或上拉输入。我习惯用上拉,防止浮空时误触发。
  • 别忘了使能GPIO时钟和AFIO时钟:很多人只配了CAN时钟,忘了开GPIO时钟,结果引脚没反应。

我曾经踩过的坑:有一次用F407,CAN2的TX/RX在PB5/PB6,但我没注意PB6同时也是I2C1的SCL。I2C外设还开着,结果CAN的RX引脚被I2C拉死了,怎么都收不到数据。所以配GPIO前,先检查引脚有没有被其他外设占用。

代码示例(HAL库):

// 使能GPIO时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_AFIO_CLK_ENABLE();

GPIO_InitTypeDef GPIO_InitStruct = {0};

// TX引脚配置
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// RX引脚配置
GPIO_InitStruct.Pin = GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

3.3 CAN滤波器:报文“守门员”

滤波器是CAN外设里最容易配错的地方。它的作用很简单:只让符合规则的报文进入FIFO,其他的直接丢弃。但配置方式有讲究。

STM32的CAN滤波器有两种模式:

模式 说明 适用场景
标识符列表模式 只接收指定ID的报文 节点数量少,ID固定
标识符掩码模式 接收ID匹配掩码范围的报文 需要接收一组ID,比如CANopen的SDO

对于CANopen从站,我建议用掩码模式。因为CANopen的PDO和SDO有固定的ID范围,用掩码可以一次性覆盖。比如,CANopen的TPDO通常从0x180开始,用掩码0x7F8就能匹配所有TPDO。

配置要点:

  • 滤波器组编号:CAN1有14个滤波器组(0-13),CAN2有8个(14-21)。
  • FIFO关联:每个滤波器组可以关联到FIFO0或FIFO1。CANopenNode通常只用FIFO0。
  • 位宽:标准帧用32位,扩展帧用32位或16位。我统一用32位,省心。

代码示例(配置为接收所有标准帧):

CAN_FilterTypeDef sFilterConfig;

sFilterConfig.FilterBank = 0;          // 使用滤波器组0
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;  // 掩码模式
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32位
sFilterConfig.FilterIdHigh = 0x0000;   // ID高16位
sFilterConfig.FilterIdLow = 0x0000;    // ID低16位
sFilterConfig.FilterMaskIdHigh = 0x0000; // 掩码高16位
sFilterConfig.FilterMaskIdLow = 0x0000;  // 掩码低16位(全0表示接收所有)
sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 关联到FIFO0
sFilterConfig.FilterActivation = CAN_FILTER_ENABLE;
sFilterConfig.SlaveStartFilterBank = 14; // 双CAN时从机起始组

HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);

注意:掩码为0表示“不关心任何位”,即接收所有报文。这在调试阶段很有用。但正式产品里,建议把掩码收紧,减少CPU负担。

3.4 整体初始化流程

把上面三块串起来,完整的CAN初始化顺序是这样的:

  1. 使能CAN时钟(APB1)和GPIO时钟。
  2. 配置TX/RX引脚为复用功能。
  3. 调用HAL_CAN_Init(),传入位时序参数。
  4. 配置滤波器。
  5. 调用HAL_CAN_Start()启动CAN外设。
  6. 调用HAL_CAN_ActivateNotification()使能中断(如果需要)。

下面这张图把整个流程串起来了:

CAN外设初始化流程 1. 时钟配置 2. GPIO配置 3. HAL_CAN_Init() 4. 滤波器配置 5. HAL_CAN_Start() 6. 使能中断(可选) ⚠️ 常见错误 • 忘记使能GPIO时钟 • 滤波器掩码配错 • 位时序参数越界 💡 调试建议 • 先用逻辑分析仪看TX/RX • 检查CAN_H/CAN_L电平

3.5 调试时的检查清单

如果你配完发现CAN收发不正常,别急着怀疑硬件。先对照这个清单查一遍:

  • 时钟:APB1时钟是否在CAN允许范围内?分频后波特率是否准确?
  • GPIO:TX/RX引脚是否配置为复用功能?有没有被其他外设抢占?
  • 滤波器:掩码是否设成了全0(接收所有)?如果设了列表模式,ID写对了没?
  • 中断:如果用了中断接收,NVIC使能了没?优先级有没有被其他中断抢占?

我曾经遇到一个特别隐蔽的问题:在F103上,CAN1的滤波器组0-13默认归CAN1用,但如果你同时使能了CAN2,且没设置SlaveStartFilterBank,CAN2的滤波器会覆盖CAN1的配置。结果CAN1收不到任何报文。所以双CAN项目里,一定要显式设置这个参数。