news 2026/10/4 11:22:31

stlink Flashloader 深度解析:STM32 高速烧写的两阶段架构、汇编约束与调试实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
stlink Flashloader 深度解析:STM32 高速烧写的两阶段架构、汇编约束与调试实战
  • 嵌入式
  • 硬件开发
  • 开发工具
  • 调试器

【免费下载链接】stlink

Open source STM32 MCU programming toolset

项目地址:https://gitcode.com/gh_mirrors/st/stlink
点击查看免费下载

本文围绕 stlink 开源 STM32 编程工具集(仓库根目录)中的核心性能机制——Flashloader(闪存加载器)展开。Flashloader 是一段被 stlink 加载到 STM32 SRAM 中执行的微型汇编程序,负责把"擦除—写数据—等待 FLASH 不忙"的循环从主机端搬到芯片端,从而大幅缩短烧写时间。读完本文,你将掌握 Flashloader 的两阶段烧写原理、st-flash 与 flashloader 之间的寄存器调用约定、各 STM32 系列的汇编实现差异、构建流程以及一套可落地的调试技巧。

什么是 Flashloader:为什么 STM32 烧写需要它

STM32 片上 FLASH 的写入是以字节 / 半字 / 字 / 双字为粒度进行的(具体粒度因系列而异)。如果整个过程完全由主机端的stlink逐单位写数据、逐单位等待 FLASH 完成,主机与芯片之间的一次次往返通信会累积成难以忍受的漫长烧写时间。

Flashloader 的引入把烧写过程拆成两个阶段(见 doc/flashloaders.md):

  1. 第一阶段(加载阶段):stlink把编译好的 flashloader 二进制和待烧写数据一并写入 SRAM。此阶段不做 busy 检查,纯数据搬运,速度快。
  2. 第二阶段(运行阶段):stlink启动(kick-start)flashloader,由它在芯片内部把 SRAM 中的数据写入 FLASH。此阶段 flashloader 自身执行"写单位数据 → 检查 FLASH 是否忙 → 继续"的循环。

这样,"写—查忙"的循环完全由 STM32 芯片自己完成,省去的是 stlink 与 STM32 之间大量的等待往返,通信开销被显著压缩。

由于 SRAM 容量通常远小于 FLASH,stlink一次只烧写一页(若 SRAM 不足则不足一页),整个烧写过程可能包含多次启动 flashloader。每次启动都重复"加载 loader + 数据 → 运行 → 等待 halt"的周期。

烧写全流程:从 st-flash 到 FLASH

原文档给出的完整流程如下(实现位于 src/stlink-lib/flash_loader.c 与 src/stlink-lib/common_legacy.c):

  1. st-flash调用stlink_flash_loader_init(flash_loader.c),把对应芯片的 flashloader 二进制加载到 SRAM;
  2. st-flash调用stlink_erase_flash_page(位于common_legacy.c),擦除对应 FLASH 页;
  3. st-flash调用stlink_flash_loader_run(flash_loader.c),在此函数内完成下列动作:
    • 将一整页的数据缓冲写入 SRAM,紧跟 flashloader 之后;
    • 把缓冲区起始地址(SRAM 内)写入寄存器r0;
    • 把目标起始地址(FLASH 内、页对齐)写入寄存器r1;
    • 把缓冲区大小写入寄存器r2;
    • 把 flashloader 的起始地址(当前为0x20000000)写入r15(即pc);
    • 之后启动 flashloader,等待内核停住(halt,由 flashloader 触发的断点所致),并确认r2已归零来判定烧写完成;
  4. flashloader 端的行为本质上是一个带 busy 检查的memcpy:
    • 从 SRAM 拷贝单个单位数据到 FLASH;
    • (大多数设备)等待 FLASH 不再忙碌;
    • 完成后触发断点(breakpoint)使内核停下。

从源码看stlink_flash_loader_run的寄存器约定

结合 flash_loader.c 的实现,实际的寄存器设定比文档描述多一个r3:

寄存器内容说明
r0fl->buf_addr数据缓冲区起始地址(SRAM 内,紧贴 loader 之后)
r1target目标 FLASH 地址(页对齐)
r2padded_size待写入字节数(可能按对齐规则补齐)
r3flash_baseFLASH 寄存器基址偏移(仅 F1_XL 双 bank 场景使用,对其他芯片无害)
r15fl->loader_addrflashloader 起始地址,即pc,从这里开始执行

代码中还包含几个容易被忽略的细节:

  • 对齐补齐:对 WB0/H5,pad_modulo取 16(H5 以 128 位 quad-word 为单位编程),其余设备默认按 1 处理;size若不对齐会被补齐为padded_size(flash_loader.c)。
  • 数据写入 SRAM:write_buffer_to_sram(common_legacy.c)把数据写到fl->buf_addr,并用0xFF填充对齐产生的空隙。
  • IWDG 喂狗:若fl->iwdg_kr非零,启动 loader 前会向独立看门狗键寄存器写入0xAAAA(STM32F0_WDG_KR_KEY_RELOAD),避免 loader 运行期间看门狗复位(flash_loader.c)。该键寄存器地址在 stlink_flash_loader_init 中按STM32_FLASH_TYPE_H7(0x58004800)与其他设备(0x40003000)分别设置。
  • 超时与轮询:启动后主机以time_ms() + 500为截止时间,每usleep(10000)检查一次stlink_is_core_halted(内部通过stlink_status更新core_stat,见 common_legacy.c),直到内核 halt 或超时(flash_loader.c)。
  • 成功判定:halt 后读取r2,只有r2 <= 0且不小于-7才视为成功;r2为正说明还有字节未写完,负得太多说明因末块未对齐写出了多余字节(flash_loader.c)。
  • 失败诊断:出错时强制进入 debug 状态,转储DHCSR/DFSR/CFSR/HFSR与R0~R7寄存器,便于定位 loader 停在了哪条路径上(flash_loader.c)。

flash_loader_t结构体定义于 inc/stlink.h,包含loader_addr(loader 的 SRAM 地址)、buf_addr(缓冲区 SRAM 地址)、rcc_dma_bkp(RCC DMA 使能状态备份)与iwdg_kr(看门狗键寄存器地址)四个字段。

编写 Flashloader 的约束清单

对于想要修改或新写 flashloader 的开发者,原文档列出了必须满足的约束:

  • 只能使用thumb-1(如 STM32F0 等)或thumb-1 + thumb-2(如 STM32F1 等)指令,不得使用 ARM 指令;
  • 不能使用栈,因为栈区可能覆盖缓冲区数据;
  • 对大多数设备,写完单个单位数据后必须等待 FLASH 不再忙碌;
  • 部分设备还需在烧写过程中检查是否发生错误;
  • 必须遵守单次拷贝的单位大小;
  • 烧写完成后必须触发断点(breakpoint)让内核停下;
  • 一次成功运行的结束标志是halt 时r2已归零;
  • flashloader 至少要能在0x20000000(SRAM 基地址)运行。

补充的调用约定细节记录在仓库内的 flashloaders/cleanroom.md:所有参数通过寄存器传递,r0为拷贝源基址、r1为拷贝目标基址、r2为拷贝字节数、r3为 FLASH 寄存器偏移(用于支持双 bank)。寄存器r3~r12可自由使用,而r13(sp)、r14(lr)与r15(pc)有特殊用途,禁止任何栈操作。汇编文件开头必须带.syntax unified编译指令,代码段放在.text。

对需要等待 FLASH 不忙的设备,检查FLASH_SR的BUSY 位;对需要检错的设备,检查FLASH_SR中(X)ERR位族(X可为任意错误状态)。各设备的FLASH_SR相关偏移与单次拷贝粒度可在 ST 官方参考手册或仓库的 cleanroom 文档中找到,下面一节给出仓库内可验证的汇总。

各系列 Flashloader 的差异:寄存器基址、状态位偏移与拷贝粒度

仓库 flashloaders/ 目录存放着各系列的汇编源文件,下表汇总了 cleanroom.md 中记录的关键参数:

汇编源文件适用系列flash_baseFLASH_SR偏移单次拷贝单位特殊要求
stm32f0.sF0(及编译为 F1/F3 的stm32vl)0x400220000x0c(12)半字(2 字节)每次拷贝前读FLASH_CR置 PG 位写回;等待 BUSY;检查错误位0x04;退出前清 PG 位
stm32f4.sF2/F4 高电压0x40023c000x0e(14)字(4 字节)等待 BUSY 复位
stm32f4lv.sF2/F4 低电压0x40023c000x0e(14)字节(1 字节)每次拷贝一个字节
stm32f7.sF7 高电压0x40023c000x0e(14)字(4 字节)与 F4 类似,但每次拷贝后、查忙前需执行dsb sy内存屏障
stm32f7lv.sF7 低电压0x40023c000x0e(14)字节(1 字节)同 F7,每次拷贝一字节
stm32lx.sL0/L1——字(4 字节)无需等待写完成
stm32l4.sL40x400220000x12(18,即FLASH_BSY)双字(8 字节)允许使用多个寄存器;读FLASH_BSY半字等待 BUSY
stm32h5.sH50x40022000—128 位 quad-word(16 字节)每次编程一个 128 位 quad-word,等待 NSSR BSY
stm32wb0.sWB0/WL3 等0x40001000(FLASH 外设基址)—16 字节块通过 FLASH 外设寄存器写,目标地址须 16 字节对齐、长度须为 16 的倍数;出错提前退出,r2保存未写完数量

源码级示例:stm32f0.s

以 flashloaders/stm32f0.s 为例,其主循环可以逐条对应约束:

loop: # 拷贝 2 字节(半字) ldrh r4, [r0] strh r4, [r1] adds r0, r0, #0x2 adds r1, r1, #0x2 wait: # 读 FLASH_SR,等待 BUSY 位复位 ldr r4, [r5] tst r4, r7 # r7 = 0x01 bne wait # 检查 PGERR 或 WRPRTERR(掩码 0x14)是否置位 ldr r7, =0x14 tst r4, r7 bne exit subs r2, r2, #0x2 # 剩余计数减 2 bgt loop exit: bkpt # 触发断点,让内核停下

对应的.word数据段给出flash_base = 0x40022000、FLASH_SR偏移0x0c。stm32f7.s则在每次str之后多了一条dsb sy(flashloaders/stm32f7.s),以满足 Cortex-M7 的内存顺序要求。

电压相关的 loader 选择

F2/F4 与 F7 系列存在高电压(32 位写)与低电压(8 位写)两个变体:stm32f4.s/stm32f4lv.s、stm32f7.s/stm32f7lv.s。选择逻辑位于 stlink_flash_loader_write_to_sram 调用的loader_v_dependent_assignment(flash_loader.c):

  • STLINK V1 无法读取目标电压,默认使用 32 位写的高压 loader;
  • 其余版本通过stlink_target_voltage读取电压,> 2700 mV 时选用 32 位 loader,否则选用 8 位 loader;读电压失败则直接报错返回。

如何构建 Flashloader:从汇编到字节数组

flashloader 不是独立的可执行程序,而是被嵌入 stlink 主机程序里的字节数组。构建链路由 flashloaders/Makefile 定义:

  1. 用arm-none-eabi-gcc(而非系统 gcc)编译汇编源文件:Cortex-M0(ARMv6-M,stm32f0.s、stm32wb0.s、stm32lx.s)用-mcpu=Cortex-M0,其余 ARMv7-M 用-mcpu=Cortex-M3,均带-ffreestanding -nostdlib且链接脚本为linker.ld;
  2. objcopy -O binary把目标文件转成裸二进制.bin;
  3. xxd -i -c 4把二进制转换成 C 头文件(如stm32f4.h)。

因此,修改 flashloader 的正确姿势是编辑 flashloaders/ 下的.s汇编文件,而不是直接改字节数组。这一点在 flash_loader.c 的注释中有明确警告:

!!! DO NOT MODIFY FLASH LOADERS DIRECTLY !!! Edit assembly files in the '/flashloaders' instead. The sizes of binary flash loaders must be aligned by 4 (it's written by stlink_write_mem32)

即:编译出的二进制长度必须4 字节对齐,因为它是通过stlink_write_mem32写入 SRAM 的。编译产物以loader_code_stm32vl、loader_code_stm32f0、loader_code_stm32wb0、loader_code_stm32lx、loader_code_stm32f4、loader_code_stm32f4_lv、loader_code_stm32h5、loader_code_stm32l4、loader_code_stm32f7、loader_code_stm32f7_lv等数组形式内嵌于 flash_loader.c。

链接脚本 flashloaders/linker.ld 把代码段放在0x20000000(64K RAM),与文档要求的"至少能在0x20000000运行"一致。

芯片匹配逻辑

stlink_flash_loader_write_to_sram依据chip_id/core_id/flash_type选择 loader:

  • L1/L0 系列 →loader_code_stm32lx(armv6-m 编译,同时兼容 L0 的 Cortex-M0 与 L1 的 Cortex-M3);
  • F1/F3/VL 系列 →loader_code_stm32vl(同一份汇编、thumb-2 编译);
  • F0 系列 →loader_code_stm32f0;
  • WB0 →loader_code_stm32wb0;H5 →loader_code_stm32h5;L4 系列 →loader_code_stm32l4;
  • F2/F4 与 F7 按上文电压规则选择;
  • 无法识别的core_id/chip_id直接ELOG报错并中止。

FLASH_SR 状态位与错误检查

对需要等待的设备,核心是轮询FLASH_SR的BUSY 位(bit 0)。对需要检错的设备(如 F0 的 PGERR/WRPRTERR),源码层面有对应的wait_flash_busy、check_flash_error、clear_flash_error等辅助函数(flash_loader.c与common_flash.c),在stlink_flashloader_write的每轮写入后调用,失败时返回-1并记录错误地址。L0/L1 的软写回退路径还会在每次写字后自行轮询FLASH_SR的 bit 0(见 flash_loader.c)。

不同系列的FLASH_SR相关偏移与单次拷贝粒度可在 ST 官方参考手册、其他开源项目头文件以及仓库内的 flashloaders/cleanroom.md 中找到依据。

调试技巧:断点、gdb/lldb 与超时

当某个 flashloader 损坏,或需要为新型号编写 flashloader 时,原文档给出了如下调试套路:

  1. 把WAIT_ROUNDS宏改大,争取在 st-flash 等待内核 halt 期间手动杀掉它;
  2. 运行st-flash,待 flashloader 加载进 SRAM 后将其杀掉;
  3. 启动st-util与gdb/lldb;
  4. 在 SRAM 基地址处设断点;
  5. 跳到基地址开始单步调试。

该套路之所以有效,是因为闪存解锁、擦除、加载 flashloader 到 SRAM 等大部分准备工作已自动完成,省去了手工搭建调试环境的时间。

需要说明的是:当前仓库源码中已不再存在WAIT_ROUNDS宏,等待内核 halt 的超时逻辑实现为time_ms() + 500的截止时间配合usleep(10000)轮询(flash_loader.c),代码注释中解释了此前"10000 次 × 10 µs"方案在类 Unix 系统上因 tick 舍入实际要等约 20 秒、而调整为按与 tick 同量级睡眠的原因。若要在调试时争取更多手动干预时间,可相应调大timeout计算中的500(ms)或轮询睡眠间隔;同时建议打开-v详细输出观察DLOG("Running flash loader...")之类的进度信息。

调试过程中一旦触发错误路径,stlink_flash_loader_run会把 PC、R0~R7 与 MCU 状态寄存器(DHCSR/DFSR/CFSR/HFSR)一并打印出来(flash_loader.c),这些信息可用于反推 loader 是在等待忙、检错还是计数循环中挂掉的。

小结

Flashloader 是 stlink 烧写性能的关键支点:它以"加载到 SRAM + 芯片内自循环烧写"的两阶段架构,把最耗时的写—查忙周期从主机端通信中剥离出来。本文从 doc/flashloaders.md 出发,结合 flash_loader.c、flashloaders/ 目录下的汇编源码与构建脚本,完整还原了寄存器调用约定、各系列实现差异、约束清单与调试方法。后续若想深入了解擦除流程、软写回退路径或芯片参数加载,可继续阅读 doc/backend.md、doc/flashloaders.md 与 src/stlink-lib/common_flash.c。

  • 嵌入式
  • 硬件开发
  • 开发工具
  • 调试器

【免费下载链接】stlink

Open source STM32 MCU programming toolset

项目地址:https://gitcode.com/gh_mirrors/st/stlink
点击查看免费下载

相关推荐

上一篇:Faster Whisper终极指南:4倍加速的语音识别解决方案
下一篇:Wabbajack模组管理器5个必知技巧:从零开始快速上手

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 11:22:11

OpenShell:将Windows 11开始菜单改回经典高效样式的开源工具

1. OpenShell 到底是个什么东西&#xff1f;我先聊聊我的第一印象前阵子帮一位客户处理电脑&#xff0c;他刚换了个新笔记本装的 Windows 11&#xff0c;开口第一句话就是"这个开始菜单怎么用都不顺手&#xff0c;能不能帮我换回老样子"。说实话&#xff0c;这几年我…

作者头像 李华
网站建设 2026/10/4 11:20:43

paperclip剪贴板管理工具:开源跨平台效率神器安装配置与使用指南

1. 从“paperclip”说起&#xff1a;一个被低估的桌面效率神器第一次看到“paperclip”这个词&#xff0c;大多数人脑子里蹦出来的可能是那个经典的曲别针图标&#xff0c;或者早年Office里那个烦人的回形针助手“大眼夹”。但在我日常折腾各种效率工具的过程中&#xff0c;pap…

作者头像 李华
网站建设 2026/10/4 11:20:37

Blender向量场可视化全攻略:从空对象到几何节点粒子流

没接触过向量场之前&#xff0c;我一直觉得这是物理和数学领域的专属名词&#xff0c;跟Blender搭不上关系。后来为了做一套流体跟随效果的预览&#xff0c;硬着头皮研究了一个周末&#xff0c;没想到用Blender做向量场其实并不复杂——它既可以做成“科学可视化”里那种一堆箭…

作者头像 李华
网站建设 2026/10/4 11:17:26

AI工程化实战:从Linux内核到业务指标的全栈构建

1. 这不是“搭积木”&#xff0c;而是亲手锻造AI系统的完整工程链“AI Engineering from Scratch”——这个标题乍看像一句技术口号&#xff0c;实则藏着一套被多数教程刻意绕开的硬核真相&#xff1a;当前90%的AI学习者&#xff0c;其实只在“调用层”打转。他们熟练使用Huggi…

作者头像 李华
网站建设 2026/10/4 11:16:03

ITSK万能驱动26V5批量更新实战:离线驱动包自动匹配与效率优化

1. 驱动批量更新这件事&#xff0c;为什么值得单独拿出来聊装系统这件事&#xff0c;很多人觉得最麻烦的不是分区、不是激活&#xff0c;而是装完之后那一堆带黄色感叹号的设备。网卡没驱动上不了网&#xff0c;显卡没驱动分辨率锁在800600&#xff0c;声卡没驱动连个响都没有。…

作者头像 李华