4. 硬件初始化:时钟系统配置、PLL锁相环设置、外设时钟使能
时钟,是芯片的“心跳”。
没有时钟,CPU不干活,外设不响应,整个系统就是一摊死硅。所以BootLoader启动后的第一件正事,就是先把时钟系统搭好。我见过不少新手,上来就写应用逻辑,结果板子点不亮,查半天发现是时钟没配——嗯,这种坑我自己也踩过。
4.1 时钟源的选择:从哪儿来?
现代MCU通常有多个时钟源。你得先想清楚,BootLoader阶段用哪个。
| 时钟源 | 典型频率 | 特点 | BootLoader常用场景 |
|---|---|---|---|
| HSI(内部高速振荡器) | 8/16 MHz | 上电即用,精度一般 | 初始启动、应急模式 |
| HSE(外部高速晶振) | 4~25 MHz | 精度高,需要起振时间 | 稳定运行、OTA升级 |
| LSI(内部低速振荡器) | 32 kHz | 低功耗,精度差 | 看门狗、RTC备份 |
| PLL输出 | 几十~几百MHz | 倍频得到高频,需配置 | 高性能运行、Flash加速 |
我个人习惯,BootLoader刚复位时先用HSI。为什么?因为HSI不需要等待外部晶振起振,芯片一上电就能跑。等我把基础环境(比如堆栈、中断向量表)初始化好了,再切到HSE+PLL,把主频提上去。
4.2 PLL锁相环:怎么把频率“翻”上去?
说白了,PLL就是一个倍频器。你给它一个低频输入,它通过内部压控振荡器(VCO)和分频器,输出一个高频时钟。
配置PLL,通常要算三个参数:
- 输入分频(M / PREDIV):把外部时钟降到PLL能接受的范围
- 倍频系数(N / MUL):核心乘法因子,决定VCO频率
- 输出分频(P / Q / R):把VCO高频降到目标主频
举个例子,STM32F4系列,HSE=8MHz,目标主频168MHz:
- 输入分频 M = 8 → 8/8 = 1 MHz(PLL输入)
- 倍频 N = 336 → VCO = 1 × 336 = 336 MHz
- 输出分频 P = 2 → SYSCLK = 336 / 2 = 168 MHz
你看,就这么简单。但实际写代码时,有个坑:PLL配置必须在使能之前完成。一旦你把PLLON位置1,PLL就开始锁定,这时候再改参数,轻则配置无效,重则系统挂掉。
4.3 外设时钟使能:别让外设“睡着”
时钟配好了,CPU能跑了。但你想用GPIO、UART、SPI?对不起,它们还睡着呢。
现代MCU为了省电,每个外设的时钟默认都是关闭的。你得通过时钟使能寄存器(比如RCC->AHB1ENR、RCC->APB1ENR)把对应外设的时钟打开。
我一般把外设时钟使能分成两步:
- 系统外设:Flash接口、DMA、CRC等,在系统初始化阶段使能
- 通信外设:UART、SPI、I2C等,在具体驱动初始化时使能
这样做的好处是,BootLoader里如果只用UART下载固件,就没必要把SPI和I2C的时钟都打开,省电也省配置时间。
// 示例:STM32F4 使能UART2时钟
// 注意:必须先使能GPIO时钟,再使能UART时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; // 使能GPIOD时钟
RCC->APB1ENR |= RCC_APB1ENR_USART2EN; // 使能USART2时钟
// 配置GPIO复用功能
GPIOD->MODER |= GPIO_MODER_MODER5_1; // PD5: AF模式
GPIOD->AFR[0] |= 7 << 20; // PD5: AF7 = USART2_TX
4.4 时钟树:一张图看懂全局
说了这么多,不如一张SVG图来得直观。下面是我画的典型MCU时钟树结构,BootLoader的时钟配置就是沿着这条路径走的。
4.5 实战:BootLoader时钟配置的典型步骤
好了,理论说完了,咱们直接上代码流程。这是我个人在多个项目里验证过的套路:
- 复位后默认使用HSI,保证CPU能跑
- 配置Flash等待周期(根据目标频率调整,否则读Flash会出错)
- 配置HSE:打开外部晶振,等待HSE稳定
- 配置PLL参数:M、N、P,但先别使能
- 使能PLL,等待PLL锁定
- 切换系统时钟:从HSI切到PLL输出
- 配置AHB/APB分频:确保外设总线频率不超限
- 使能需要用到的外设时钟:UART、GPIO等
void SystemClock_Config(void)
{
// 1. 使能HSE
RCC->CR |= RCC_CR_HSEON;
while(!(RCC->CR & RCC_CR_HSERDY)); // 等待HSE就绪
// 2. 配置Flash等待周期(168MHz需要5个等待周期)
FLASH->ACR = FLASH_ACR_LATENCY_5WS | FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN;
// 3. 配置PLL: HSE=8MHz, M=8, N=336, P=2 → SYSCLK=168MHz
RCC->PLLCFGR = (8 << 0) | // PLLM
(336 << 6) | // PLLN
(0 << 16) | // PLLP = 2
RCC_PLLCFGR_PLLSRC_HSE;
// 4. 使能PLL
RCC->CR |= RCC_CR_PLLON;
while(!(RCC->CR & RCC_CR_PLLRDY)); // 等待PLL锁定
// 5. 切换系统时钟到PLL
RCC->CFGR |= RCC_CFGR_SW_PLL;
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
// 6. 配置AHB/APB分频(AHB不分频,APB1=42MHz, APB2=84MHz)
RCC->CFGR |= RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE1_DIV4 | RCC_CFGR_PPRE2_DIV2;
// 7. 使能UART和GPIO时钟(BootLoader需要串口通信)
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
}
4.6 避坑指南:时钟配置的常见问题
- PLL锁定超时:外部晶振没焊好、电容不匹配、频率超出芯片范围。我建议加一个超时循环,比如等待100ms,超时则切回HSI并报错。
- Flash等待周期不够:频率高了,Flash读取跟不上,程序会跑飞。记住:频率每提高一档,等待周期也要加一档。
- 外设时钟顺序颠倒:先开外设时钟再配GPIO?不行。必须先开GPIO时钟,再开外设时钟,最后配置复用功能。
- OTA升级时切频:如果在升级过程中切换时钟源,要确保通信外设(比如UART)的波特率重新计算。否则数据会乱码。
嗯,时钟配置这块,说难不难,说简单也不简单。你只要记住一条:按顺序来,等标志位,加超时。做到这三点,BootLoader的时钟系统基本稳了。