3. Bootloader最小系统设计
Bootloader 的最小系统并不是“能跑起来就行”的临时工程,而是一个必须长期稳定驻留在 Flash 起始区域、能够独立完成通信与写入、并在合适时机把控制权交给应用程序的可靠底座。它和普通应用工程最大的区别在于:它不能依赖应用程序的任何初始化结果,必须自己建立时钟、自己配置引脚、自己驱动串口、自己操作 Flash,最后还要干净地跳转。本节围绕这五个环节展开,说明每个环节在 Bootloader 语境下的具体做法与判断依据。
时钟初始化:先确定系统节拍
Bootloader 上电后首先面对的是复位默认时钟。以 STM32F103 为例,复位后默认使用内部 8MHz HSI,若直接用它驱动 USART,波特率误差会偏大,长时间接收容易出错。因此第一步应把系统时钟切到外部晶振并配置 PLL。典型做法是:使能 HSE,等待 HSERDY 置位;配置 Flash 预取与等待周期;设置 PLL 倍频系数,使 SYSCLK 达到 72MHz;切换系统时钟源到 PLL,并等待 SW 状态位确认。下面是一段可参考的配置骨架:
RCC->CR |= RCC_CR_HSEON;
while(!(RCC->CR & RCC_CR_HSERDY));
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY_2;
RCC->CFGR |= RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL9;
RCC->CR |= RCC_CR_PLLON;
while(!(RCC->CR & RCC_CR_PLLRDY));
RCC->CFGR |= RCC_CFGR_SW_PLL;
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
这里的关键判断是:切换完成后读回 SYSCLK 频率,确认与预期一致;若 HSE 起振失败,应有超时退出并回退到 HSI,避免 Bootloader 卡死在等待循环里。时钟稳定后,再使能 USART 与 GPIO 所需的外设时钟,顺序上先时钟后外设,避免寄存器写入无效。
GPIO 配置:串口引脚与状态指示
串口通信需要把 USART 的 TX、RX 引脚配置为复用推挽输出和浮空/上拉输入。以 USART1 为例,PA9 为 TX,PA10 为 RX。配置时应先使能 GPIOA 与 USART1 时钟,再设置引脚模式与速度。若使用 115200 波特率,GPIO 速度可设为 50MHz。除通信引脚外,建议再保留一个普通 GPIO 作为状态指示,例如 PC13 接 LED,用于在进入升级、写入成功、跳转前给出可见反馈。这个指示在排障时非常有用:如果 LED 不亮,说明程序可能没跑到 GPIO 初始化;如果常亮不闪,说明卡在某个等待循环。
串口通信基础:帧格式与接收策略
Bootloader 的串口不需要复杂协议,但必须明确帧边界。常见做法是:上位机先发一个帧头,例如 0xAA 0x55,再发命令字、长度、数据与校验。Bootloader 侧采用“状态机 + 超时”的接收方式,而不是简单阻塞等待。状态机依次识别帧头、命令、长度、数据和校验;每收到一个字节重置超时计数,若超过设定时间仍未收齐,则丢弃当前帧并回到等待帧头状态。这样即使上位机中途断开,Bootloader 也不会永久卡死。波特率建议固定为 115200,8 位数据位、1 位停止位、无校验,双方一致即可。接收缓冲区大小按最大帧长设定,例如 256 字节,避免溢出。
Flash 读写驱动:解锁、擦除、写入与校验
STM32 的 Flash 写入必须遵循“先解锁、再擦除、后写入”的顺序。以页擦除为例,先检查 BSY 位,确认空闲后设置 KEYR 解锁;然后设置 CR 的 PER 位与 AR 地址,触发 STRT;等待 BSY 清零后检查 EOP。写入时以半字为单位,每次写入前确认目标地址已擦除为 0xFFFF。写入完成后建议回读校验,逐半字比较,若不一致则标记该页写入失败。Bootloader 自身通常放在 0x08000000 起始的若干页,应用程序从 0x08008000 开始,参数区放在两者之间,避免升级时误擦 Bootloader。擦除与写入期间应关闭中断或确保中断向量不落在被擦除页内,否则可能取指异常。
跳转到应用程序:栈顶、向量表与入口
跳转的本质是把 CPU 的控制权交给应用程序的复位处理流程。应用程序的起始地址处存放的是初始 MSP 值,紧接着是复位向量。跳转前先做合法性检查:读取 App 地址处的第一个字作为栈顶,判断它是否落在 RAM 范围内;读取第二个字作为复位向量,判断它是否落在应用程序 Flash 区间内。检查通过后,关闭全局中断,设置主栈指针为读取到的栈顶值,设置向量表偏移寄存器 VTOR 指向应用程序起始地址,最后通过函数指针跳转到复位向量。跳转后不应再返回,因此跳转前应确保串口、定时器等外设已复位或关闭,避免应用程序初始化时出现冲突。若跳转后 LED 不闪、串口无输出,应优先检查栈顶与复位向量是否合法,以及 VTOR 是否设置正确。
可参考的地址与参数示例
| 项目 | 示例值 | 说明 |
|---|---|---|
| Bootloader 起始 | 0x08000000 | 复位后首先执行 |
| Bootloader 大小 | 16KB | 占用 0~3 页(每页 4KB) |
| 参数区 | 0x08004000 | 存放升级标志、版本号 |
| 应用程序起始 | 0x08008000 | 跳转目标地址 |
| 串口波特率 | 115200 | 8N1,双方一致 |
| 接收超时 | 500ms | 超时丢弃当前帧 |
把以上五个环节串起来,Bootloader 最小系统就具备了独立运行、接收升级数据、写入 Flash 并移交控制权的能力。后续更复杂的双区升级、校验与回滚,都是在这个底座上叠加的。