4. 硬件初始化实战:时钟树配置、GPIO初始化、串口外设初始化、滴答定时器配置
硬件初始化,说白了就是给芯片“通电开窍”。
很多刚入行的朋友,拿到一块新板子,上来就写应用逻辑。结果程序跑飞了,连个打印都看不到。我早期也犯过这个错——折腾了半天,最后发现是时钟没配好,CPU 压根没跑在预期频率上。
这一章,咱们就把 Bootloader 启动阶段最核心的四个硬件模块捋清楚。你跟着我的节奏走一遍,以后移植任何芯片心里都有底。
4.1 时钟树配置:芯片的心脏起搏器
时钟是芯片的“心跳”。没有时钟,外设不工作,CPU 也不干活。我习惯把时钟树想象成一个自来水系统:晶振是水源,PLL 是增压泵,分频器是阀门,最终流向各个外设。
4.1.1 时钟源的选择
常见的时钟源有这几种:
| 时钟源 | 典型频率 | 特点 | Bootloader 常用场景 |
|---|---|---|---|
| HSI(内部高速振荡器) | 8/16 MHz | 上电即用,精度一般 | 初始启动、备份时钟 |
| HSE(外部高速晶振) | 4~25 MHz | 精度高,需要起振时间 | 主系统时钟 |
| LSI(内部低速振荡器) | 32 kHz | 低功耗,精度差 | 独立看门狗、RTC |
| LSE(外部低速晶振) | 32.768 kHz | 高精度,RTC 专用 | 精准计时 |
| PLL(锁相环) | 倍频后可达数百 MHz | 高频,需配置倍频系数 | 高性能运行 |
我个人习惯在 Bootloader 里优先使用 HSE + PLL 的组合。为什么?因为 Bootloader 要快速完成初始化,然后跳转到应用。HSE 精度高,PLL 能拉到芯片最高主频,这样 Flash 读取、CRC 校验都能跑得快。
4.1.2 典型时钟配置流程
以 STM32F4 为例,配置 168 MHz 系统时钟的步骤大致如下:
- 复位 RCC 寄存器
- 使能 HSE,等待就绪
- 配置 PLL 参数(倍频、分频)
- 使能 PLL,等待锁定
- 配置 AHB、APB1、APB2 总线分频
- 切换系统时钟到 PLL 输出
- 更新系统滴答定时器的时钟源
代码示例:
void SystemClock_Config(void)
{
// 1. 使能 HSE
RCC->CR |= RCC_CR_HSEON;
while(!(RCC->CR & RCC_CR_HSERDY))
{
// 超时处理,防止死等
if(timeout-- == 0) break;
}
// 2. 配置 PLL: HSE 8MHz -> PLL_M=8 -> PLL_N=336 -> PLL_P=2 -> 168MHz
RCC->PLLCFGR = (8 << 0) | // PLL_M
(336 << 6) | // PLL_N
(0 << 16) | // PLL_P = 2
(RCC_PLLCFGR_PLLSRC_HSE);
// 3. 使能 PLL
RCC->CR |= RCC_CR_PLLON;
while(!(RCC->CR & RCC_CR_PLLRDY));
// 4. 配置总线分频
RCC->CFGR = RCC_CFGR_HPRE_DIV1 | // AHB = 168MHz
RCC_CFGR_PPRE1_DIV4 | // APB1 = 42MHz
RCC_CFGR_PPRE2_DIV2; // APB2 = 84MHz
// 5. 切换系统时钟
RCC->CFGR |= RCC_CFGR_SW_PLL;
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}
4.2 GPIO 初始化:让芯片的“手脚”动起来
GPIO 是芯片和外界交互的“手脚”。在 Bootloader 里,GPIO 主要用来控制状态指示灯、检测启动模式引脚、或者做简单的调试输出。
4.2.1 GPIO 模式选择
每个 GPIO 引脚通常有这几种模式:
- 输入模式: 读取外部电平。用于检测按键、跳线帽。
- 输出模式: 推挽输出(驱动能力强)或开漏输出(用于 I2C 等总线)。
- 复用功能: 引脚交给片上外设(如串口、SPI)控制。
- 模拟模式: 用于 ADC、DAC。
我个人习惯在 Bootloader 里把调试串口的 TX/RX 配置为复用功能推挽输出/浮空输入,把状态 LED 配置为推挽输出。其他不用的引脚,我通常会设为模拟输入或带上拉输入,这样最省电、最稳定。
4.2.2 初始化代码示例
void GPIO_Init(void)
{
// 使能 GPIOA 和 GPIOC 时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOCEN;
// PA9: USART1_TX, 复用功能推挽输出
GPIOA->MODER &= ~(3 << 18); // 清除原配置
GPIOA->MODER |= (2 << 18); // 复用功能
GPIOA->AFR[1] |= (7 << 4); // AF7 = USART1
// PA10: USART1_RX, 复用功能浮空输入
GPIOA->MODER &= ~(3 << 20);
GPIOA->MODER |= (2 << 20);
GPIOA->AFR[1] |= (7 << 8);
// PC13: LED, 推挽输出
GPIOC->MODER &= ~(3 << 26);
GPIOC->MODER |= (1 << 26); // 输出模式
GPIOC->OTYPER &= ~(1 << 13); // 推挽
GPIOC->OSPEEDR |= (3 << 26); // 高速
}
4.3 串口外设初始化:Bootloader 的“嘴巴”
串口是 Bootloader 最重要的调试和通信接口。你想想看,没有串口打印,你怎么知道程序跑到哪一步了?
4.3.1 串口参数配置
标准配置一般是:115200 波特率、8 位数据、1 位停止位、无校验。但要注意,有些芯片的串口时钟来自 APB 总线,如果 APB 时钟变了,波特率也要重新算。
波特率计算公式:
USART_BRR = (APB_Clock + (Baudrate / 2)) / Baudrate
举个例子,APB2 时钟是 84 MHz,目标波特率 115200:
USART_BRR = (84000000 + 57600) / 115200 = 729.5 ≈ 730
4.3.2 串口初始化代码
void USART1_Init(void)
{
// 使能 USART1 时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
// 配置波特率 115200 @ 84MHz
USART1->BRR = 730;
// 使能发送、接收、以及整个 USART
USART1->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
// 8 位数据,1 位停止位(默认配置,无需额外设置)
}
4.4 滴答定时器配置:系统的“心跳节拍”
SysTick 是 Cortex-M 内核自带的 24 位递减定时器。它最大的用处就是提供系统滴答,用来做延时、超时判断、或者 RTOS 的时基。
4.4.1 SysTick 配置要点
- 重装载值: 等于 (系统时钟频率 / 期望滴答频率) - 1。比如 168 MHz 时钟,想要 1 ms 滴答一次,重装载值就是 168000 - 1。
- 时钟源: 可以选择处理器时钟(AHB)或 AHB/8。我一般选 AHB,这样精度最高。
- 中断使能: 如果需要定时回调,就使能 SysTick 中断。
4.4.2 配置代码
void SysTick_Init(void)
{
// 重装载值: 168MHz / 1000Hz = 168000 - 1
SysTick->LOAD = 168000 - 1;
// 清零当前值
SysTick->VAL = 0;
// 使能 SysTick,使用处理器时钟,使能中断
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
}
// 中断处理函数
void SysTick_Handler(void)
{
static uint32_t tick = 0;
tick++;
// 可以在这里做 1ms 周期的任务,比如 LED 闪烁
}
4.5 本章知识结构图
下面这张图把四个模块的依赖关系画清楚了。时钟是基础,GPIO 和串口依赖时钟,SysTick 依赖系统时钟。你按这个顺序初始化,基本不会出问题。
4.6 常见问题与调试技巧
硬件初始化阶段最容易出问题。我总结了几条经验,你遇到了可以直接对号入座:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 程序跑不起来,全速运行后无反应 | 时钟配置错误,芯片未启动 | 用示波器测晶振引脚,看有无波形;检查 PLL 锁定标志 |
| 串口输出乱码 | 波特率不对,或 GPIO 复用配置错误 | 核对 APB 时钟频率与 BRR 计算值;检查 AF 映射表 |
| LED 不亮 | GPIO 模式或时钟未使能 | 检查 RCC 外设时钟使能位;用万用表测引脚电平 |
| 延时不准 | SysTick 重装载值计算错误 | 确认 SysTick 时钟源是 AHB 还是 AHB/8;核对系统时钟频率 |
| 进入 HardFault | 外设时钟未使能就操作寄存器 | 检查所有外设的 RCC 时钟使能位是否已置位 |
嗯,硬件初始化这块,说白了就是细心活。时钟配错了,后面全白搭。GPIO 搞反了,串口就哑巴。你只要按我上面说的顺序来,再配合串口日志输出,基本一次过。