4. 案例4
任务目标
本案例基于STM32F103微控制器,实现Bootloader启动阶段必需的硬件初始化流程:配置系统时钟树(72MHz主频)、初始化启动状态指示LED的GPIO、初始化用于调试输出的USART1串口,并通过LED闪烁验证初始化流程正确执行。该初始化代码是Bootloader进入主程序前的硬件准备阶段。
硬件环境
- MCU:STM32F103C8T6(Blue Pill开发板)
- LED:PC13(板载,低电平点亮)
- USART1:PA9(TX) / PA10(RX),波特率115200
- 外部晶振:8MHz(HSE)
关键完整代码
/* main.c - Bootloader硬件初始化 */
#include "stm32f1xx.h"
/* 系统时钟树配置:HSE 8MHz → PLL ×9 → SYSCLK 72MHz */
void SystemClock_Config(void) {
/* 使能 HSE */
RCC->CR |= RCC_CR_HSEON;
while (!(RCC->CR & RCC_CR_HSERDY));
/* FLASH 等待周期:72MHz 需 2 个等待周期 */
FLASH->ACR = FLASH_ACR_LATENCY_2;
/* 配置 PLL:HSE 作为输入,倍频系数 ×9 */
RCC->CFGR = RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9;
/* APB1 分频 /2(36MHz),APB2 不分频(72MHz) */
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
/* 使能 PLL 并等待锁定 */
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY));
/* 切换系统时钟到 PLL */
RCC->CFGR |= RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}
/* GPIO 初始化:PC13 推挽输出(LED) */
void GPIO_Init(void) {
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; /* 使能 GPIOC 时钟 */
GPIOC->CRH &= ~(0xF << 20); /* 清除 CNF13/ MODE13 */
GPIOC->CRH |= (0x2 << 20); /* MODE13=10: 输出模式, 2MHz */
/* CNF13=00: 推挽输出 */
}
/* USART1 初始化:115200-8-N-1,TX 复用推挽输出 */
void USART1_Init(void) {
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_USART1EN;
/* PA9(TX) 复用推挽输出 50MHz */
GPIOA->CRH &= ~(0xF << 4);
GPIOA->CRH |= (0xB << 4); /* MODE9=11, CNF9=10 */
/* PA10(RX) 浮空输入 */
GPIOA->CRH &= ~(0xF << 8);
GPIOA->CRH |= (0x4 << 8); /* MODE10=00, CNF10=01 */
/* 波特率计算:72MHz / (16 × 115200) ≈ 39.06 → 取整 39 */
USART1->BRR = 0x271; /* 0x271 = 625 = 39.0625 的定点表示 */
USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
}
/* 简单字符发送 */
void USART1_SendChar(char c) {
while (!(USART1->SR & USART_SR_TXE));
USART1->DR = c;
}
/* 字符串发送 */
void USART1_SendString(const char *str) {
while (*str) {
USART1_SendChar(*str++);
}
}
/* 毫秒级延时(简单循环,非精确) */
void Delay_ms(uint32_t ms) {
for (uint32_t i = 0; i < ms * 4000; i++) {
__NOP();
}
}
int main(void) {
/* 1. 时钟树配置:SYSCLK = 72MHz */
SystemClock_Config();
/* 2. GPIO 初始化:LED 引脚 */
GPIO_Init();
/* 3. 串口初始化 */
USART1_Init();
/* 4. 启动信息输出 */
USART1_SendString("Bootloader HW Init OK\r\n");
/* 5. LED 闪烁验证:亮 200ms,灭 200ms,循环 5 次 */
for (int i = 0; i < 5; i++) {
GPIOC->BSRR = (1 << 13); /* PC13 = 0 → LED 亮 */
Delay_ms(200);
GPIOC->BRR = (1 << 13); /* PC13 = 1 → LED 灭 */
Delay_ms(200);
}
/* Bootloader 初始化完成,可跳转至主程序 */
while (1) {
/* 停留在此处,实际 Bootloader 可在此跳转 */
}
}
编译与烧录
# 使用 arm-none-eabi-gcc 编译(以 Makefile 方式为例)
arm-none-eabi-gcc -c -mcpu=cortex-m3 -mthumb main.c -o main.o
arm-none-eabi-ld -T stm32f103c8t6.ld main.o -o bootloader.elf
arm-none-eabi-objcopy -O binary bootloader.elf bootloader.bin
# 使用 ST-Link 烧录
st-flash write bootloader.bin 0x08000000
验证方法与判定标准
- LED 闪烁验证:上电后板载 LED(PC13)应以 200ms 间隔闪烁 5 次(亮-灭交替),随后保持常灭。若观察到该现象,说明时钟树配置正确(否则延时异常)、GPIO 初始化成功。
- 串口输出验证:将 PA9(TX) 连接至 USB-TTL 模块,PC 端以 115200-8-N-1 打开串口。上电后应收到字符串
Bootloader HW Init OK。若收到该字符串,说明 USART1 初始化及波特率配置正确。 - 时钟频率验证:若 LED 闪烁周期明显偏离 200ms(如过快或过慢),说明 PLL 配置有误,系统时钟未达到 72MHz。
常见问题
- LED 不亮:检查 GPIOC 时钟是否使能(
RCC->APB2ENR),以及 PC13 引脚模式配置是否正确(推挽输出)。 - 串口无输出:确认 PA9 复用推挽配置正确,且 BRR 值计算无误。若使用 8MHz HSE 但未正确配置 PLL,实际波特率会偏移导致乱码。
- 程序跑飞:检查 FLASH 等待周期是否设置为 2(72MHz 必需),否则高速访问 Flash 会导致异常。