Flash引导过程
200.2.8 Flash引导过程
UART_HandleTypeDef huart1; /* usart.c:定义 USART1 的 HAL 句柄,保存硬件实例、配置和运行状态。 */
void MX_USART1_UART_Init(void) /* CubeMX 生成的 USART1 参数初始化函数。 */
{ /* USART1 初始化函数开始。 */
huart1.Instance = USART1; /* 让句柄指向 USART1 寄存器组,其基地址最终为 0x40013800。 */
huart1.Init.BaudRate = 115200; /* 设置波特率为 115200 bit/s。 */
huart1.Init.WordLength = UART_WORDLENGTH_8B; /* 设置数据字长为 8 位。 */
huart1.Init.StopBits = UART_STOPBITS_1; /* 设置 1 个停止位。 */
huart1.Init.Parity = UART_PARITY_NONE; /* 不使用奇偶校验。 */
huart1.Init.Mode = UART_MODE_TX_RX; /* 同时使能发送和接收。 */
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 不使用 RTS/CTS 硬件流控制。 */
huart1.Init.OverSampling = UART_OVERSAMPLING_16; /* 使用 16 倍过采样。 */
if (HAL_UART_Init(&huart1) != HAL_OK) /* 把句柄地址交给 HAL,并检查初始化是否成功。 */
{ /* HAL 初始化失败分支开始。 */
Error_Handler(); /* 进入 CubeMX 工程统一的错误处理函数。 */
} /* HAL 初始化失败分支结束。 */
} /* USART1 初始化函数结束。 */
#define FLASH_BASE 0x08000000UL /* stm32f103xe.h:主 Flash 的物理基地址。 */
#define SRAM_BASE 0x20000000UL /* stm32f103xe.h:片上 SRAM 的基地址。 */
#define PERIPH_BASE 0x40000000UL /* stm32f103xe.h:外设地址空间的基地址。 */
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL) /* APB2 外设区基地址,计算结果为 0x40010000。 */
#define USART1_BASE (APB2PERIPH_BASE + 0x00003800UL) /* USART1 基地址,计算结果为 0x40013800。 */
#define USART1 ((USART_TypeDef *)USART1_BASE) /* 把数值基地址转换成 USART 寄存器结构体指针。 */
typedef struct /* stm32f103xe.h:用结构体成员偏移描述 USART 寄存器布局。 */
{ /* USART 寄存器结构体开始。 */
__IO uint32_t SR; /* 偏移 0x00:状态寄存器。 */
__IO uint32_t DR; /* 偏移 0x04:数据寄存器。 */
__IO uint32_t BRR; /* 偏移 0x08:波特率寄存器。 */
__IO uint32_t CR1; /* 偏移 0x0C:控制寄存器 1。 */
__IO uint32_t CR2; /* 偏移 0x10:控制寄存器 2。 */
__IO uint32_t CR3; /* 偏移 0x14:控制寄存器 3。 */
__IO uint32_t GTPR; /* 偏移 0x18:保护时间与预分频寄存器。 */
} USART_TypeDef; /* 定义 USART 寄存器映射类型。 */
void USART1_IRQHandler(void) /* stm32f1xx_it.c:USART1 的芯片中断入口。 */
{ /* USART1 中断服务函数开始。 */
HAL_UART_IRQHandler(&huart1); /* 交给 HAL 根据寄存器标志和句柄状态处理本次中断。 */
} /* USART1 中断服务函数结束。 */
void USART2_IRQHandler(void) /* stm32f1xx_it.c:USART2 的芯片中断入口。 */
{ /* USART2 中断服务函数开始。 */
HAL_UART_IRQHandler(&huart2); /* 交给 HAL 处理 USART2 的发送、接收或错误事件。 */
} /* USART2 中断服务函数结束。 */
本课补充两个基础问题:STM32F103ZE 复位后为什么能从主 Flash、System Memory 或 SRAM 启动,以及 HAL 如何把 huart1.Instance = USART1最终落实为对 0x40013800附近硬件寄存器的访问。
最需要纠正的概念是:STM32 不是先在 0x00000000执行一段隐藏代码,再“跳转”到 0x08000000。 复位时,BOOT 引脚决定哪个存储区域成为启动空间;处理器从启动地址 0x00000000读取初始 MSP,从 0x00000004读取复位入口。主 Flash 或 System Memory 启动时,硬件通过地址别名让相应存储内容出现在启动窗口,无须复制固件。
一、单片机引导flash启动
1.1 STM32的地址映射
1.1.1 地址映射与地址别名
STM32F103ZE 的主 Flash 物理基地址为 0x08000000。当 BOOT 配置选择主 Flash 时,硬件还让主 Flash 的开头能从启动空间 0x00000000被取到:
复位取向量时看到的地址 对应的主 Flash 内容
0x00000000 ↔ 0x08000000:初始 MSP
0x00000004 ↔ 0x08000004:Reset_Handler
这叫 alias(地址别名/映射):两组地址在启动场景下指向同一份存储内容,并不是把 Flash 复制了一遍。CPU 从 0x00000004读出的向量值通常仍是链接器生成的 0x0800xxxx | 1,随后便从主 Flash 的物理地址取指。
同理,选择 System Memory 时,工厂 Bootloader 所在的 System Memory 成为启动空间。选择 SRAM 时,STM32F1 使用专门的启动机制;SRAM 本身仍在 0x20000000,应用初始化时还需要正确安排 SRAM 向量表。
ST 的 RM0008 参考手册明确说明:BOOT 引脚在复位后被采样,CPU 随后从 0x00000000取得栈顶、从 0x00000004开始执行启动存储器中的复位入口。
1.1.2 BOOT0、BOOT1选择表
STM32F103 的三种硬件启动方式为:
| BOOT1 | BOOT0 | 启动空间 | 典型用途 |
|---|---|---|---|
| 任意 | 0 | 主 Flash | 正常运行用户程序或本课程自定义 Bootloader |
| 0 | 1 | System Memory | 运行 ST 出厂写入的 ROM Bootloader,执行 ISP |
| 1 | 1 | 片上 SRAM | 调试或特殊 RAM 启动场景 |
BOOT0=0时 BOOT1 无关,因此普通运行最关键的是把 BOOT0可靠拉低。BOOT 引脚在复位时采样,不是程序运行过程中随时切换代码来源的普通开关。
1.1.3 跳线帽对初学者意味着什么
开发板上的 BOOT 跳线通常把引脚接到:
GND:逻辑 0;3.3V:逻辑 1。
若要经 STM32F103ZE 的工厂串口 Bootloader 下载,常见步骤是:
- 断电或确保操作安全后,把 BOOT0 接到 1、BOOT1 接到 0;
- 重新上电或按复位键,使 BOOT 引脚被重新采样;
- 通过指定的 USART1 引脚与上位机 Bootloader 工具通信;
- 下载完成后把 BOOT0恢复为 0;
- 再次复位,才从用户 Flash 运行刚烧写的程序。
不能只移动跳线而不复位,因为启动选择已经在上一次复位时锁存。也不要让 BOOT 引脚悬空;具体三针跳线的 3.3V、BOOT、GND排列必须看板上丝印或原理图,错误短接可能直接把电源与地相连。
1.1.4 ICP、ISP、IAP不要混淆
视频思维导图列出了三种固件写入/升级方式:
| 名称 | 本课语境 | 是否依赖 ST 工厂 Bootloader | BOOT 引脚要求 |
|---|---|---|---|
| ICP(In-Circuit Programming) | 用 ST-LINK 经 SWD/JTAG 在板编程 | 否 | 通常可保持主 Flash 启动配置 |
| ISP(In-System Programming) | 进入 System Memory,经 ST ROM Bootloader 和 USART1 下载 | 是 | F103 通常设 BOOT0=1、BOOT1=0 后复位 |
| IAP(In-Application Programming) | 由用户固件在系统内接收并改写另一段 Flash | 否,运行的是用户自己的 Bootloader | 本课程保持 BOOT0=0,从主 Flash 启动 |
ST-LINK 是调试/编程器,常用 SWD,也可使用器件支持的 JTAG;“用 ST-LINK 烧写”不等于“从 System Memory 启动”。
1.2 单片机复位进入IAP升级模式(准确说是进入 ISP/System Memory)
原标题把 BOOT0 置 1进入工厂 Bootloader 称为 IAP,概念上应更正为 ISP/System Memory 启动。两条流程如下。
工厂 Bootloader/ISP:
BOOT1=0、BOOT0=1
→ 复位
→ System Memory 成为启动空间
→ 运行 ST 出厂写入的 ROM Bootloader
→ 通过 USART1 接收命令和固件
→ 改写主 Flash
→ BOOT0 改回 0并再次复位
→ 从主 Flash 运行用户程序
本课程自定义 IAP:
BOOT0=0
→ 复位后始终从 0x08000000 的用户 Flash 启动
→ 先运行课程 Bootloader
→ 读取 0x08006000 的升级标志
→ 必要时经 USART2/YMODEM 把 APP 写入 0x08008000
→ 软件跳转 APP
二者虽然都能更新主 Flash,但 Bootloader 的所有者和所在位置不同:
- ST 工厂 Bootloader 位于 System Memory,生产时写入,普通的主 Flash 擦除不会删除它;
- 课程 Bootloader 位于用户主 Flash 的
0x08000000~0x08005FFF,整片擦除会把它一起删除。
对 STM32F10xxx 高密度器件,ST 官方资料给出的工厂 Bootloader 位于从 0x1FFFF000开始的 2 KiB System Memory,并使用 USART1。详细接口和引脚要求应查 AN2606:System Memory Boot Mode,不能把其他 STM32 系列支持的 USB、CAN 或其他串口直接套到 F103ZE。
工厂 Bootloader 的串口参数也不能照搬本工程配置:AN2606 为该器件列出 USART1 的 8 数据位、偶校验、1 停止位(8E1),而下文配套工程的 huart1是 115200 8N1;两者属于不同程序,必须分别按对应协议配置上位机。
主 Flash 正常启动后的流程仍是:
从启动向量首项装载 MSP
→ 从第二项进入 Reset_Handler
→ 调用 SystemInit
→ 进入 C 运行库 __main
→ 初始化 .data/.bss
→ 调用用户 main
这与第 199 课的软件跳 APP相似,但硬件复位会重新建立复位状态,而 Bootloader 的函数指针跳转不会自动复位所有外设。
1.3 STM32F103ZE
1.3.1 芯片手册地址分配
视频查看的是 STM32F103xC/xD/xE 数据手册中的 Memory mapping 图。与本项目直接相关的地址如下:
| 区域 | 起始地址 | 末地址 | 本器件容量或用途 |
|---|---|---|---|
| 启动别名窗口 | 0x00000000 | 由所选启动区域决定 | 供复位向量获取,不是另一块独立存储器 |
| 主 Flash | 0x08000000 | 0x0807FFFF | 512 KiB;放 Bootloader、参数和 APP |
| System Memory | 0x1FFFF000 | 0x1FFFF7FF | 2 KiB;放 ST 工厂 Bootloader |
| Option Bytes | 0x1FFFF800 | 0x1FFFF80F | 读保护、写保护等选项字节 |
| SRAM | 0x20000000 | 0x2000FFFF | 64 KiB |
| 外设地址空间起点 | 0x40000000 | — | APB/AHB 外设寄存器映射起点 |
| USART1 区域 | 0x40013800 | 0x40013BFF | USART1 寄存器所在的 APB2 地址块 |
这些数值与配套工程 stm32f103xe.h中的 FLASH_BASE、FLASH_BANK1_END、SRAM_BASE、PERIPH_BASE完全一致,也可在 STM32F103ZE 数据手册的内存映射图中核对。
视频已在约 4 分 24 秒主动纠正上一课口误:SRAM 末地址是 0x2000FFFF,不是 0x20010000或 0x2000E000。0x20010000是末端后一地址,可作为向下增长栈的初始上边界,但它本身不是 SRAM 中可存放一个字节的位置。
1.3.1.1 32位地址空间的准确理解
Cortex-M3 使用 32 位地址,理论地址空间包含:
2^32 = 4,294,967,296 个字节地址
最低地址 = 0x00000000
最高地址 = 2^32 - 1 = 0xFFFFFFFF
总地址空间 = 4 GiB
这不表示 STM32F103ZE 内部真的有 4 GiB 存储器。绝大多数地址是外设映射区、保留区或未实现空间;本器件实际只有 512 KiB Flash 和 64 KiB SRAM。
“因为有 32 根地址总线,所以叫 32 位单片机”也不够准确。Cortex-M3 的“32 位”主要描述其体系结构、通用寄存器和主要数据处理宽度;封装外部并不存在供用户直接使用的 32 根完整地址引脚。
1.3.2 代码分析
本节通过 USART1 展示“手册中的地址如何变成 C 代码中的寄存器访问”。完整关系是:
芯片手册规定 USART1 基地址 0x40013800
→ stm32f103xe.h 用宏计算 USART1_BASE
→ USART1 宏把该整数地址转为 USART_TypeDef *
→ huart1.Instance 保存这个寄存器结构体指针
→ HAL_UART_Init(&huart1) 根据 Init 参数读写相应寄存器
这里没有在 0x40013800真正创建一个普通 C 结构体对象。USART_TypeDef只是让编译器按成员偏移生成对内存映射寄存器的读写指令。
1.3.2.1 例题: USART1外设基地址分析
UART_HandleTypeDef是 HAL 的“软件句柄”,其中最关键的两部分是:
Instance:指向具体 USART 寄存器组;Init:保存波特率、字长、停止位、校验、收发模式、流控和过采样等配置。
所以:
huart1.Instance = USART1; /* 选定 USART1 这组硬件寄存器,而不是 USART2、USART3。 */
右侧 USART1宏最终是一个 USART_TypeDef *,左侧 Instance字段的类型也是 USART_TypeDef *,两者类型匹配。
USART_TypeDef中每个字段均为 uint32_t,所以本结构里的相邻寄存器偏移增加 4 字节:
| 成员 | 偏移 | 实际地址 | 作用 |
|---|---|---|---|
SR | 0x00 | 0x40013800 | 状态寄存器 |
DR | 0x04 | 0x40013804 | 数据寄存器 |
BRR | 0x08 | 0x40013808 | 波特率寄存器 |
CR1 | 0x0C | 0x4001380C | 控制寄存器 1 |
CR2 | 0x10 | 0x40013810 | 控制寄存器 2 |
CR3 | 0x14 | 0x40013814 | 控制寄存器 3 |
GTPR | 0x18 | 0x40013818 | 保护时间与预分频寄存器 |
“每个寄存器都占 4 字节”只适用于这里列出的 USART 寄存器布局,不能推广成所有 MCU、所有外设寄存器都一定如此。判断偏移必须看对应参考手册和设备头文件。
成员前的 __IO在当前 CMSIS 中展开为 volatile,表示寄存器可读可写,且每次 C 表达式访问都必须落实为实际硬件访问,不能像普通 RAM 变量那样被编译器随意缓存或删除。
1.3.2.2 例题: USART1_BASE地址分析
原笔记标题中的“urt e base”是 USART1_BASE的识别错误。设备头文件按总线层级计算它:
PERIPH_BASE = 0x40000000
APB2PERIPH_BASE = PERIPH_BASE + 0x00010000
= 0x40010000
USART1_BASE = APB2PERIPH_BASE + 0x00003800
= 0x40013800
原转录把 0x40013800识别成 0x40013820,并把 0x00010000说成“加 0x0001”,都应以上述头文件实际常量为准。
最后一层宏为:
#define USART1 ((USART_TypeDef *)USART1_BASE) /* 把 0x40013800 解释成 USART 寄存器结构体指针。 */
于是 HAL 或用户代码写 USART1->BRR时,编译器实际访问的是 0x40013800 + 0x08 = 0x40013808。这就是“结构体映射寄存器”的本质。
用 Keil 的 Go To Definition沿着下面的链条追踪,是验证地址的有效方法:
huart1.Instance
→ USART1
→ USART1_BASE
→ APB2PERIPH_BASE
→ PERIPH_BASE
但最终仍应以对应型号的官方数据手册和参考手册为准,不能拿视频开头的 STM32F4 地址图替代 STM32F103ZE 的资料。
二、单片机的配置
CubeMX 图形界面不是在单片机运行时“把配置复制进结构体”,而是在 PC 上 生成 C 源码。编译后的 MCU 每次启动时执行这些赋值语句:
CubeMX/.ioc 中选择参数
→ 生成 MX_USART1_UART_Init()
→ 运行时给 huart1.Instance 和 huart1.Init 各字段赋值
→ 调用 HAL_UART_Init(&huart1)
→ HAL 根据句柄配置 USART 寄存器
配套工程对 USART1 的实际配置为 115200、8 数据位、1 停止位、无校验、收发模式、无硬件流控、16 倍过采样。这通常简称 115200 8N1。
HAL_UART_Init(&huart1)传入的是句柄地址,原因是初始化函数既要读取 Instance和Init,也要更新句柄内部的状态、锁、错误码等成员。只按值传递一个结构体副本,就无法让调用者看到这些状态变化。
HAL 初始化不只计算 BRR:它还会结合 HAL_UART_MspInit()完成或依赖底层资源配置,包括 USART 时钟、TX/RX GPIO,以及需要时的 NVIC。实际调用关系应按当前 HAL 版本源码核对,而不是把所有工作都想象成一个寄存器赋值。
HAL 与标准外设库的主要差异不是“HAL 永远更好”,而是抽象层级不同:
- HAL 句柄保存配置、状态、缓冲区和回调信息,接口统一、移植和生成代码方便,但调用路径更长;
- 较低层接口或直接寄存器代码更贴近硬件,开销和控制更明确,但使用者要自己承担更多细节;
- 学习时应先会用 HAL,再沿
Instance → 寄存器结构 → 地址宏 → 手册向下追踪,理解封装的边界。
三、串口相关知识点
视频提到的串口接收方式可整理为:
| 方式 | 常见 HAL 接口 | CPU 行为 | 适用情况 |
|---|---|---|---|
| 轮询/阻塞 | HAL_UART_Receive() | 在函数内反复检查状态,直到完成或超时 | 数据量小、流程简单、允许等待 |
| 普通中断 | HAL_UART_Receive_IT() | 启动后返回,串口事件到来时进入 IRQ | 固定长度、不能阻塞主循环 |
| 空闲事件中断 | HAL_UARTEx_ReceiveToIdle_IT() | 数据接收或线路空闲事件触发回调 | 不定长帧 |
| DMA/空闲事件 | HAL_UARTEx_ReceiveToIdle_DMA() | DMA搬运数据,CPU主要处理事件和缓冲区 | 数据量大或接收频繁 |
对普通中断接收,调用链不是“硬件直接调用 callback”,而是:
USART 硬件置位状态并向 NVIC 请求中断
→ Cortex-M 根据向量表进入 USART1_IRQHandler
→ USART1_IRQHandler 调用 HAL_UART_IRQHandler(&huart1)
→ HAL 检查 SR/CR、更新计数和状态
→ 满足完成或错误条件后调用对应 callback
配套 stm32f1xx_it.c中的 USART1、USART2 中断入口都只有一条 HAL 分发调用,但 stm32f1xx_hal_uart.c中的 HAL_UART_IRQHandler()内部需要处理发送、接收、错误、空闲等多种状态,所以“用户只写一句回调”背后确实有较完整的状态机。
不过,存在 USART2 IRQ 入口不等于本课程 YMODEM 正在用中断接收。本地配套源码的 Serial_Recv_data()实际调用:
return HAL_UART_Receive(&huart2, pData, Size, Timeout); /* YMODEM 当前使用 USART2 阻塞接收并带超时。 */
也就是说,本课后半段对中断与空闲接收的讨论是 HAL 串口机制扩展,不应误写成当前 YMODEM 的实际接收方式。
查看 HAL 底层代码时,建议按一个具体问题逐层追踪,而不是一次阅读整个驱动文件。例如分析中断接收:
- 从用户调用的
HAL_UART_Receive_IT()开始; - 查看它保存了哪些缓冲区、长度和状态;
- 查看它开启了哪些 USART 中断位;
- 回到
USARTx_IRQHandler(); - 进入
HAL_UART_IRQHandler(),跟踪对应状态分支; - 最后确认何时调用
HAL_UART_RxCpltCallback()或错误回调。
这样可以同时练习指针、结构体、位运算、状态机和中断调用链,又不会把 HAL 的封装误认为“单句 API 自动完成一切”。
四、本课易错点汇总
0x00000000是复位启动窗口,不是 STM32F103ZE 主 Flash 的物理基地址;主 Flash 基址是0x08000000。- 主 Flash 启动依靠硬件别名和向量获取,不是隐藏代码先运行再执行普通 C 跳转。
- BOOT0=1、BOOT1=0进入的是 ST System Memory Bootloader/ISP,不是课程自定义 IAP。
- 课程 IAP 的 Bootloader 位于用户 Flash,正常应保持 BOOT0=0。
- F103ZE SRAM 字节范围是
0x20000000~0x2000FFFF,共 64 KiB。 - 32 位地址空间有
2^32个地址,最大地址是2^32-1 = 0xFFFFFFFF。 - USART1 基地址是
0x40013800,不是0x40013820。 - APB2 基地址的增量是
0x00010000,不是0x0001。 UART_HandleTypeDef.Instance指向寄存器映射结构体;它本身不是硬件寄存器组。USART_TypeDef成员间隔 4 字节是当前外设的具体布局,不能推广到所有寄存器。- CubeMX 在 PC 上生成初始化赋值代码,不是在 MCU 运行时读取
.ioc文件。 - 回调前还经过芯片 IRQ 入口和
HAL_UART_IRQHandler();当前 YMODEM 接收则实际使用阻塞接口。