第3章 时钟配置与引脚映射:FDCAN的“心跳”与“手脚”
各位好,欢迎来到第三章。
上一章我们把FDCAN的硬件模块结构理清了。但模块本身不会跑,你得给它“心跳”——时钟,还得告诉它用哪只“手脚”去收发数据——引脚。这两件事搞不定,后面代码写得再漂亮也是白搭。
我个人习惯,做任何外设驱动,第一步永远是先看时钟树。为什么?因为时钟错了,时序全乱,你连调试的机会都没有。FDCAN对时钟的要求尤其严格,选错了源,波特率怎么配都配不准。
3.1 FDCAN时钟源选择:PLL1Q、PLL2Q还是HSE?
STM32的FDCAN模块,时钟源主要有三个选项:
| 时钟源 | 典型频率 | 特点 | 适用场景 |
|---|---|---|---|
| HSE | 8MHz / 25MHz(外部晶振) | 精度高,但频率固定 | 对时间同步要求高的应用 |
| PLL1Q | 可编程(如 80MHz) | 与系统时钟同源,灵活 | 大多数常规项目 |
| PLL2Q | 可编程(独立分频) | 独立于系统总线时钟 | 需要FDCAN跑独立频率的场景 |
说白了,选哪个取决于你的系统时钟怎么配。我一般这么选:
- 如果项目对CAN总线时间同步要求不高,直接用PLL1Q,省事。
- 如果系统主频很高(比如400MHz),但FDCAN只需要跑40MHz,那就用PLL2Q单独分频,避免大分频比带来的抖动。
- 如果外部有高精度晶振(比如25MHz),且需要做TTCAN或时间触发,那就直接上HSE。
嗯,这里有个小细节。FDCAN的时钟频率,直接决定了你后面能配出的波特率。比如你想跑5Mbps的CAN FD,那FDCAN时钟至少得是40MHz(8个时间份额)。如果时钟只有20MHz,那5Mbps就别想了。
3.2 时钟配置实战:以STM32H743为例
我们拿STM32H743来演示。假设系统时钟配到400MHz,我想让FDCAN跑在40MHz。
第一步,打开时钟树,找到FDCAN的时钟源选择寄存器——RCC_D1CCIPR里的FDCANSEL位段。
// 选择 PLL1Q 作为 FDCAN 时钟源
RCC->D1CCIPR &= ~(RCC_D1CCIPR_FDCANSEL_Msk);
RCC->D1CCIPR |= (0x01 << RCC_D1CCIPR_FDCANSEL_Pos); // 01: PLL1Q
第二步,确保PLL1Q确实输出了40MHz。这需要在配置PLL时就规划好。
// 假设 HSE=25MHz, 主PLL配置:
// PLLM=25, PLLN=160, PLLP=2, PLLQ=4
// 则 PLL1Q = 25/25 * 160 / 4 = 40MHz
RCC->PLLCKSELR = RCC_PLLCKSELR_PLLSRC_HSE; // 源选HSE
RCC->PLL1DIVR = (25-1) | (160-1)<<8 | (4-1)<<24; // M, N, Q
RCC->CR |= RCC_CR_PLL1ON;
while(!(RCC->CR & RCC_CR_PLL1RDY)); // 等待锁定
3.3 FDCAN引脚映射:TX和RX的“选位”艺术
时钟有了,接下来得告诉芯片,FDCAN的TX和RX信号走哪两个引脚出去。
STM32的引脚映射,说白了就是GPIO的复用功能选择。FDCAN的TX和RX,通常有多个可选引脚。比如STM32H743上,FDCAN1的TX可以是PB9、PD1、PH13等。
选引脚时,我一般遵循几个原则:
- PCB走线优先: 选离CAN收发器最近的引脚,减少走线长度。
- 避免冲突: 确保该引脚没有被其他外设占用(比如UART、SPI)。
- 电平兼容: FDCAN的TX/RX是3.3V逻辑,如果收发器是5V的,注意电平转换。
GPIOx_AFRL/AFRH寄存器,确认没有其他外设配置了同一个引脚。
3.4 GPIO配置为FDCAN复用功能
选好引脚后,就是GPIO的初始化了。这一步其实很固定,但细节容易出错。
以PB9(FDCAN1_TX)和PB8(FDCAN1_RX)为例:
// 使能GPIOB时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
// 配置PB9为FDCAN1_TX (AF9)
GPIOB->MODER &= ~(GPIO_MODER_MODE9_Msk);
GPIOB->MODER |= (0x02 << GPIO_MODER_MODE9_Pos); // 复用功能
GPIOB->AFR[1] &= ~(GPIO_AFRH_AFSEL9_Msk);
GPIOB->AFR[1] |= (0x09 << GPIO_AFRH_AFSEL9_Pos); // AF9
// 配置PB8为FDCAN1_RX (AF9)
GPIOB->MODER &= ~(GPIO_MODER_MODE8_Msk);
GPIOB->MODER |= (0x02 << GPIO_MODER_MODE8_Pos);
GPIOB->AFR[1] &= ~(GPIO_AFRH_AFSEL8_Msk);
GPIOB->AFR[1] |= (0x09 << GPIO_AFRH_AFSEL8_Pos);
// 配置速度与上下拉(TX推挽,RX浮空或上拉)
GPIOB->OSPEEDR |= (0x03 << GPIO_OSPEEDR_OSPEED9_Pos); // 高速
GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPD9_Msk); // TX无上下拉
GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPD8_Msk);
GPIOB->PUPDR |= (0x01 << GPIO_PUPDR_PUPD8_Pos); // RX上拉
这里有个关键点:TX引脚要配成推挽输出,RX引脚配成浮空输入或带上拉。为什么RX要上拉?因为CAN总线在隐性状态时,RX引脚应该是高电平。如果浮空,噪声可能让RX误判。
3.5 知识结构总览
为了让你对整个流程有个直观印象,我画了张图。这张图把时钟源选择、引脚映射、GPIO配置串在了一起。
3.6 常见问题与避坑
最后,分享几个我实际项目中踩过的坑:
- 时钟源没使能: 选了PLL1Q,但忘了把PLL1打开。FDCAN初始化时直接卡死。解决办法:配完时钟后,检查
RCC_CR里的对应就绪位。 - 引脚复用号配错: 把AF9写成了AF8。TX引脚死活没波形。解决办法:养成查数据手册的习惯,别凭记忆写。
- RX引脚没上拉: 总线空闲时,RX引脚电平不确定,导致FDCAN误判总线状态。解决办法:RX引脚配成内部上拉,或者外部加10kΩ上拉电阻。
FDCANx->CCCR寄存器的INIT位,确认FDCAN是否退出了初始化模式。如果一直卡在初始化状态,八成是时钟或引脚配置有问题。