STM32MP1自举程序详解:USB DFU与USART协议实战
直接说结论如果你手上正好有一块 STM32MP1 系列的板子想搞清楚“第一次烧录到底走的是什么链路”或者你被 AN5275 这份应用笔记里那些“USB DFU 和 USART 协议”的术语搞得有点晕这篇文章就是为你写的。我会基于自己在 MP1 平台上的实际调试经历把自举程序里这两个协议的关键机制、操作流程和踩坑点拆开讲清楚不绕弯子。STM32MP1 和普通 MCU 最大的不同是它跑的是 Linux但芯片内部依然保留了一段出厂固化的 ROM 自举程序BootROM。所以即使 Flash 里什么都没有只要通过 BOOT 引脚配置好启动模式芯片上电后就会主动进入 USB DFU 或者 USART 下载模式等待主机发送固件。AN5275 讲的就是这段自举程序里两种下载通道的协议细节而我更想结合实际操作告诉你这些细节在什么场景下会真正影响到你以及怎么利用它们。1. 自举程序在 MP1 启动链路中的位置以及为什么它决定了烧录方式很多人第一次拿到 MP1 开发板第一反应是“我直接拿 ST-Link 连接板子像烧写 F103 那样把程序下载进去”。这个想法在 MP1 上行不通。STM32MP1 的启动流程和传统 MCU 有很大区别我们需要先把它整个启动链路的基本框架铺开这样才能理解为什么 AN5275 专门用一整篇应用笔记去讲 USB DFU 和 USART 协议。1.1 从 BootROM 到 FSBL 再到 SSBL自举程序只是第一步STM32MP1 的上电启动顺序大概是这样的芯片复位后CPU 首先执行的是芯片内部 ROM 里的一段固化代码这段代码就是 BootROM也就是我们说的自举程序。它的任务很简单就是根据 OTP一次性可编程区域里的配置和 BOOT 引脚的电平状态决定从哪个外设接口加载下一级代码。在 MP1 上下一级通常是 FSBLFirst Stage Boot Loader一般由官方提供的 U-Boot 或者 TF-A 来承担。这里有个关键点BootROM 本身不具备“识别文件系统”或者“解析 Linux 内核镜像”的能力。它的能力边界非常窄只支持从固定的存储介质比如 SD 卡、eMMC、NAND、NOR读取固定格式的数据或者通过 USB DFU / USART 等外设接口接收主机发送的数据。所以当你手里拿到一块完全没有程序的空板子时USB DFU 和 USART 就是唯一能“无中生有”地把 FSBL 灌进去的途径。相比之下传统 STM32 MCU比如 F103的 BootROM 虽然也会检查 USART1/2、USB DFU 等接口但绝大多数人用 ST-Link 烧录因为 SWD 接口可以直接控制内核复位和内存访问。MP1 的 SWD 接口依然存在但它只能用于调试没法直接烧写外部存储介质。你要往 eMMC 或 NAND 里写 FSBL要么让 BootROM 先进入下载模式接收代码要么通过 U-Boot 再烧写。AN5275 里描述的 USB DFU 和 USART 协议就是 BootROM 这个“第一关”的系统服务。提示理解 BootROM 和 FSBL 的关系对后续排错非常有帮助。如果你的板子已经能启动到 U-Boot 了那说明 BootROM 这一关已经过了如果没有任何反应优先排查 BOOT 引脚配置和 BootROM 下载通道是否正常。1.2 为什么官方把 DFU 设计成 MP1 的首选下载方式在 MCU 时代USART 下载是相当普及的因为串口简单可靠几乎所有调试环境都有串口。MP1 的 BootROM 也确实保留了 USART 下载通道。但官方在文档里和实际工具链中更推荐的是 USB DFU。原因很直观速度差距太大。USART 的波特率在自举阶段即使能跑到 115200 或者更高实际传输一个几 MB 的 FSBL 镜像也需要很长时间而且没有严格的流控容易出错。USB DFU 则不同它基于 USB 协议理论上传输速度可以达到 MB/s 级别而且有完整的握手和错误重传机制。另外MP1 的 FSBL 文件通常叫 tf-a-stm32mp157c-dk2.stm32 之类本身就比较大包含了不少初始化代码和 DDR 初始化数据。用串口下载的话等待时间会让人崩溃。我实际测试过在 115200 波特率下传输一个约 1.5MB 的镜像需要一分多钟而且中间偶尔会因为环境干扰出现校验错误需要重传而采用 USB DFU 后几秒钟就完成了。所以在实践中我建议你把 USB DFU 作为默认的下载通道USART 保留为“备用”通道。这个备用通道在什么时候有价值呢当你的板子 USB 接口硬件有问题或者你的主机环境无法正常识别 USB DFU 设备时USART 就成了救命的备用方案。AN5275 对两种协议都做了完整的描述也给了我们一个很好的对照学习机会DFU 用于高速传输USART 用于无法使用 USB 的场景。1.3 影响自举程序行为的硬件状态BOOT 引脚和 OTP 配置自举程序并非每次上电都无脑等 USB 握手。它首先会读取一组 BOOT 引脚在 MP1 上标为 BOOT0、BOOT1、BOOT2的电平状态然后结合 OTP 里的配置决定使用哪种启动设备。以 STM32MP157C-DK2 开发板为例板子上的拨码开关组合直接影响 BootROM 的行为BOOT20BOOT10BOOT00从 USB DFU 启动BOOT20BOOT10BOOT01从 SD 卡启动BOOT20BOOT11BOOT00从 eMMC 启动还有其他组合对应 NOR Flash、NAND 等介质如果你的板子 BOOT 引脚配置成了“从 SD 卡启动”但 SD 卡里没有合法的 FSBLBootROM 会尝试读取失败后最终落到串口/USB 下载模式吗这里有个容易混淆的地方MP1 的 BootROM 在启动设备上读取失败后并不一定自动切换到 USB DFU 模式。它会根据 OTP 里配置的“后备启动设备”来决定是否切换。如果后备启动设备是 USB DFU那么确实会等待 USB 枚举如果没有配置后备启动设备就直接报错死循环。因此当你发现自己明明把 USB 线插上了但主机识别不到 DFU 设备先别急着怀疑 USB 线检查一下 BOOT 引脚组合是不是正确落在了“USB DFU”那一档。AN5275 的正文里对这一块也做了说明但用一句话概括就是自举模式是硬件状态和协议状态共同决定的硬件状态不对协议再标准也白搭。2. USB DFU 传输流程中的关键字段与时序以及实际烧录时的系统行为当我们通过 BOOT 引脚配置让 MP1 进入 USB DFU 模式后主机端会看到一个 USB 设备厂商 ID 是 0x0483产品 ID 通常是 0xDF11。这个 DFU 设备在 USB 枚举阶段会暴露一个接口内含两个端点控制端点 0 用于传输 DFU 命令批量端点通常是端点 1用于传输固件内容。很多初学者一上来就跳进了 DFU 命令的细节里其实先搞懂整体传输流程更重要。2.1 DFU 协议的命令/状态结构为什么查询指令必须固定长度USB DFU 协议本身有一套命令集合定义在 USB Device Class Specification for DFU 里ST 在此基础上做了一些适配。AN5275 详细列举了 MP1 BootROM 所支持的 DFU 命令其中包括 DNLOAD下载、UPLOAD上传、GETSTATUS获取状态、CLRSTATUS清除状态、GETSTATE获取状态机状态等。这里我想重点提醒一个容易踩坑的细节DFU 命令不是简单的“发一条指令收一个响应”就完事了。它有一个状态机每次命令交互都需要通过 GETSTATUS 来确认上一次操作是否成功而且 GETSTATUS 的返回结构是固定长度的 6 字节数据。如果你自己是写主机端工具的千万不要忽略这个固定长度查询否则状态机可能永远卡在“busy”状态。具体传输流程大致如下主机发起 DFU_DETACH 命令让设备进入 DFU 模式实际上 MP1 BootROM 在枚举后本身就是 DFU 模式这一步往往省略。主机发送 DFU_GETSTATUS读取设备当前状态确认设备处于 idle 状态。主机发送 DFU_DNLOAD 命令携带下载地址和长度信息数据分块发送每块大小由设备在枚举阶段通过 DFU 功能描述符声明通常是 1024 字节或 2048 字节。每发送一块数据主机都要发送 DFU_GETSTATUS 确认设备是否 ready再发送下一块。全部数据发送完毕后主机发送 DFU_DNLOAD 命令但长度字段为 0表示数据结束。设备进入“manifestation”阶段也就是把接收到的数据写入目标地址完成固件更新。最后主机再次发送 DFU_GETSTATUS确认设备状态从“manifest”变回“idle”整个流程结束。这个流程在官方工具 STM32CubeProgrammer 里被封装得很完善用户只需要选择固件文件点击下载即可。但如果你要自己写自动化脚本或者做一个定制的烧录工具你就必须按照这个状态机逐步骤操作任何一步跳过了设备都可能卡死或者报错。提示MP1 的 BootROM DFU 实现里地址和长度通常不是以纯数据形式直接跟随 DNLOAD 命令的而是通过一个自定义命令前缀通常是 0x21、0x22 等来区分是“设置地址”还是“设置长度”。这个细节在 AN5275 里有明确表格但很多人没注意到导致自己写工具时明明命令格式看起来没问题却一直得到“invalid command”响应。2.2 地址/长度命令与数据阶段的关系以及 0x21/0x22 命令的作用再展开讲一下这个自定义命令前缀因为这个细节特别容易让人困惑。标准 DFU 协议里DNLOAD 命令携带的数据可以是任意内容设备并不关心数据的含义。但在 STM32MP1 的自举实现中DNLOAD 的数据阶段被赋予了额外语义。当你想要下载固件到某个内存地址比如 0x2FFC0000这块是 MP1 的 SYSRAM 或 DDR 初始化前的临时运行区主机需要先发送一条“设置地址”命令命令格式如下请求类型0x21Class, Interface, Host-to-Device请求0x01DFU_DNLOADwValue0x0000wIndex接口号通常为 0数据阶段包含 4 字节的地址值同样的如果要设置下载长度则发送请求类型0x21请求0x01DFU_DNLOADwValue0x0000wIndex0数据阶段包含 4 字节的长度值然后实际固件数据才通过后续的 DNLOAD 命令发送。这三个阶段设置地址、设置长度、发送数据在时序上有严格顺序。AN5275 中关于这一段的描述比较像“协议规范”没有过多解释为什么这样设计。我的理解是ST 希望通过这种方式让 BootROM 在接收固件前就知道目标地址和预期长度这样可以直接启动内部 DMA 或者做内存预分配避免接收到一半发现地址非法的情况。在实际操作中我遇到过一个情况使用自己写的 Python 脚本通过 PyUSB 发送 DFU 命令时由于没有按照“先地址、再长度、再数据”的顺序而是把三个过程混在一起发送导致 BootROM 一直返回错误状态。后来我打开 Wireshark 抓包对比了 STM32CubeProgrammer 的 USB 通信过程才发现 ST 的工具也是严格按这个顺序来组织的。所以如果你要开发自己的烧录工具务必对照 AN5275 的表格把命令交互顺序做成一个状态机而不是简单的一问一答。2.3 MP1 BootROM 对 DFU 下载地址的特殊限制接下来要讲一个特别关键的限制MP1 的 BootROM 并不会允许你把固件写到任意地址。由于 BootROM 自身运行在芯片内部的 ROM 区域RAM 区域里可用的临时存储空间也很有限DFU 下载地址必须是它支持的“合法地址”。AN5275 里列了一张表说明 DFU 支持的目标内存区域。最常见的是0x2FFC0000 到 0x2FFFFFFF这是 256KB 的 SYSRAMBootROM 可以把固件加载到这里然后跳转执行。0xC0000000 到 0xC000FFFFDDR 区域但要求 DDR 已经被初始化对于刚上电、DDR 尚未初始化的空板子来说你能用的其实只有 SYSRAM。所以你会发现通过 DFU 下载 FSBL 时默认的下载地址往往就是 0x2FFC0000。这个限制直接导致了一个实用技巧如果你要烧写的 FSBL 镜像超过 256KB某些带 OP-TEE 的镜像可能逼近这个大小你并不能一次性直接把它下载到 SYSRAM。你需要在镜像设计上将 FSBL 控制在合理范围内或者先通过 DFU 下载一个小型的“初始化程序”到 SYSRAM让它先初始化 DDR然后再通过后续协议把完整的镜像传输到 DDR 区域。U-Boot 的 SPL 设计模式就是干这个用的。我在自己的板子上试过直接把一个 400KB 的 TF-A 镜像用 DFU 写进 0x2FFC0000结果是写入中途就报错。查了 AN5275 的地址表格之后才明白原因。所以任何基于 MP1 的烧录方案都必须先确认你的目标地址是否在 BootROM 支持的合法列表里否则协议流程再完美也会失败。3. USART 自举协议的帧结构与握手细节以及与 DFU 的协作关系如果说 USB DFU 是 MP1 的“主力下载通道”那 USART 就是“备用救生通道”。USART 自举协议在旧款 STM32 上就已经存在MP1 继承并扩展了它。相比 USB DFUUSART 协议更简单直接但也更容易出错。因为它没有 USB 那样的链路层自动重传机制帧结构的每一个字节都需要仔细校验。3.1 波特率自动检测的机制0x7F 字节的价值USART 自举协议的第一个步骤是主机发送一个字节 0x7F 给设备然后等待设备回送一个应答字节 0x79ACK。这个 0x7F 不是随便选的它有双重作用。第一0x7F 的二进制是 0111 1111包含了连续的 0 和 1 电平翻转方便设备端进行波特率自动检测。第二它是协议规定的同步握手标志只有正确收到 0x7F设备才会进入后续命令解析状态。MP1 的 BootROM 在 USART 模式下会以极低的预设波特率开始监听然后根据接收到的 0x7F 信号边沿来估算实际波特率并重新配置串口外设。所以你在主机端设置的波特率只要在一个合理范围内通常是 9600 到 115200设备都能自动适应。但这里有一个前提你的串口发送端必须能输出规范的电平信号如果用的是 USB 转串口模块建议选择基于 FT232 或者 CP2102 这类成熟方案的模块避免因信号质量不好导致波特率检测失败。实际烧录时如果你用 STM32CubeProgrammer 的 USART 模式软件会自动完成 0x7F 握手。但如果你自己写脚本记一下握手成功后后续所有命令都遵循“命令字节 命令字节的反码 等待 ACK0x79或 NACK0x1F”的模式。这个结构很简单但对时序有要求每个字节之间不能有太长间隔否则 BootROM 会认为通信超时并退回握手状态。3.2 读命令、写命令、擦除命令的帧结构以及全局擦除和批量擦除的区别MP1 的 USART 自举协议命令集里最常用的是这三条0x00 或 0x01 之类的命令用于读取芯片版本/支持的命令列表0x31 写命令用于向指定地址写入数据0x44 擦除命令用于擦除指定扇区或进行全局擦除以写命令为例帧结构如下主机发送 0x31随后发送 0xCE0x31 的反码。设备回送 ACK0x79。主机发送 4 字节地址高位在前和 1 字节字节数N-1即实际长度减一。设备回送 ACK。主机发送 N 字节数据最多 256 字节。设备回送 ACK表示这一块数据写入完成。这里有个容易忽略的地方地址字段是 4 字节而长度字段是 1 字节且长度是用 N-1 表示的。也就是说单次最多写入 256 字节。如果你要下载一个 1MB 的镜像就需要分成 4096 次写入。所以在 USART 模式下实际下载速度不仅受波特率限制还受这种固定帧结构的握手次数限制非常慢。如果 Mirror 稍微大一点全程耗时可能是 DFU 的几十倍。擦除命令也值得单独说。0x44 命令有两种使用方式一种是发送 0x44 0xBB 后再发送 0xFF 0xFF执行全局擦除另一种是指定扇区地址只擦除特定区域。全局擦除对 MP1 不太合适因为 MP1 的外部存储介质eMMC/NAND/SD中存在 BootROM、FSBL、环境变量等多个分区全部擦掉会连 BootROM 引导信息都没了。更安全的做法是使用按扇区擦除只擦掉你要写入的 FSBL 分区。AN5275 对这部分给出了更详细的寄存器级说明但从实际应用角度我心里记着的原则是能按扇区擦就不要全局擦能写 256 字节就不要分小段写。3.3 USART 和 USB DFU 在 MP1 烧录流程中的协作先小后大、先引导后系统在实际 MP1 量产或开发过程中USART 和 USB DFU 往往不是互斥的而是协作的。我自己常用的一套方式是先用 USB DFU 下载一个“最小 FSBL”到 SYSRAM。这个最小 FSBL 运行后初始化 DDR。再通过 DFU或者 U-Boot 的 fastboot 命令把完整的 U-Boot、Linux 内核和设备树写到 eMMC 或 SD 卡中。USART 在这种流程里起到的作用更多是“救砖”。如果某次更新中途突发断电导致 eMMC 里的 FSBL 损坏BootROM 尝试从 SD/eMMC 启动失败后如果后备启动设备指向 USART那你依然可以通过串口来恢复。你会发现只要 USART 和 DFU 的协议链路是通的板子基本上不会“变砖”最多是启动失败重新刷写即可。所以不要把 AN5275 的内容当成两条孤立的协议而应当看成是 BootROM 提供的组合下载能力。理解了这一点后面遇到异常情况时的排查思路会清晰很多。4. 烧录过程中的经典故障与恢复路径从协议层到硬件层的排查链路自举程序的调试麻烦不在于协议本身而在于“看起来一切都对但就是烧不进去”。我总结了几类自己实际遇到过的故障按排查链路从协议层到硬件层梳理一遍希望你能少走弯路。4.1 现象一USB 设备无法枚举没有任何 DFU 设备出现这是最典型的问题。USB 线插上后主机完全没有反应lsusb 也看不到 0x0483:0xDF11。出现这种问题我的排查链路是这样先确认 BOOT 引脚配置是否正确。很多开发板默认是从 SD 卡启动如果你没有切换 BOOT 引脚BootROM 压根不会进入 DFU 模式。确认电源是否充分。MP1 的 USB DFU 枚举本身耗电不大但如果你同时启动了其他外设电源不稳会导致 USB 枚举失败。检查 USB 线是不是“充电线”。很多 USB 线只接电源和数据正负极没有接数据线结果怎么插都识别不到。换一条确定支持数据传输的线材是最快的验证方法。用逻辑分析仪或者示波器看 USB D/D- 的波形确认设备是否发了高速 chirp。如果没有说明 BootROM 可能压根没跑到 USB 枚举的阶段。这个排查顺序看起来很简单但实际操作中绝大多数问题出在第一步和第三步而不是协议本身。AN5275 不会教你这些因为这是“硬件常识”但它往往是解决问题的最短路径。4.2 现象二DFU 能枚举但下载到一半报错设备状态变成 error这种情况我遇到得最多。设备能正常识别和枚举说明 USB 链路和 BootROM 的 DFU 逻辑是通的。下载到一半报错问题基本出在地址或长度设置上。举个例子某次我尝试把一个 U-Boot 镜像写入 0xC0000000DDR 地址但当时 DDR 还没初始化。BootROM 在接收数据时发现目标地址不可写直接进入了 error 状态。解决的办法是要么先用 DFU 下载一个小程序初始化 DDR要么把下载地址改到 SYSRAM0x2FFC0000。另一个常见原因是镜像文件本身格式和 BootROM 预期不匹配。MP1 的 BootROM 对映像是有一点点“校验”的尤其是头部格式。如果你把完全裸的二进制文件直接烧进去BootROM 可能会在跳转执行时崩溃而不是在下载时报错。这一点要特别留意AN5275 明确要求 DFU 下载的内容需要符合 STM32 的镜像格式要求比如带有特定头信息。注意当你看到 DFU 状态机的状态从 idle 变成 error千万不要直接重新发送 DNLOAD 命令必须先发送 CLRSTATUS 命令清除错误状态再发送 GETSTATUS 让设备回到 idle否则后续指令都会被忽略。4.3 现象三USART 握手失败发送 0x7F 后设备无响应这个故障出现的概率也不低。我的排查顺序是确认 USART 引脚是否和 BootROM 期望的引脚一致。MP1 的 BootROM 使用特定的 USART 引脚作为下载口通常是 USART1 的 PA9/PA10或者 USART2 的 PD5/PD6具体取决于 OTP 配置。你用错了串口引脚自然收不到响应。确认串口模块的 TX/RX 有没有接反。这个错误太常见了TX 接 TX、RX 接 RX 是错的必须交叉连接。确认地线共地。USB 转串口模块如果不和板子共地电平参考点不一致通信必然失败。一步步降低波特率。虽然 MP1 支持波特率自动检测但并不是所有串口模块都能在 115200 下输出干净的 0x7F 波形降速到 9600 往往能成功。从协议上说如果设备收到 0x7F 后能回 0x79说明同步已经建立问题基本在后续的命令交互如果连 0x79 都没有优先查硬件连接别在协议上浪费时间。4.4 恢复路径如何利用 DFU/USART 让变砖的板子重新活过来最后说一下大家最关心的“救砖”操作。当 eMMC 里的 FSBL 损坏板子上电后 BootROM 找不到合法启动镜像通常不会自动进入 DFU 模式除非 OTP 里配置了后备启动设备。所以保险的做法是在量产前就把 OTP 的后备启动设备配置成 USB DFU这样即使主启动设备全坏了BootROM 依然会进入 DFU 模式等待主机刷机。如果没有配置后备启动设备遇到变砖只能借助 ST-Link 之类的调试器通过 JTAG/SWD 连接并手动初始化 DDR再烧写外部存储。这个操作复杂度高不适合产线操作。因此我的建议是在开发早期就规划好 OTP 配置把 DFU 作为默认后备这能让后续维护省掉大量麻烦。5. 一组我常用的协议侧速查参数以及调试中的经验心得我很清楚看协议文档时最烦的是“每看一句话就要翻回前面的表格”。所以我把 AN5275 和实际调试中反复用到的一些参数整理成了一张速查表方便你直接对照使用。参数或命令值或格式说明DFU 厂商 ID0x0483ST 标准厂商 IDDFU 产品 ID0xDF11ST DFU 标准产品 IDDFU 下载地址SYSRAM0x2FFC0000MP1 空板首选下载地址DFU 设置地址命令0x21, 0x01, wValue0x0000数据段 4 字节地址必须先于数据传输DFU 设置长度命令0x21, 0x01, wValue0x0000数据段 4 字节长度必须在发送数据前发送USART 握手字节0x7F主机发给设备的同步字节USART ACK / NACKACK0x79NACK0x1F设备对命令的响应USART 写命令0x31 0xCE地址 4 字节长度 N-1数据 N 字节单次最多写 256 字节USART 擦除命令0x44 0xBB扇区地址或 0xFF 0xFF按扇区擦除 / 全局擦除常用 USART 波特率9600 / 115200握手阶段自动检测这张表是我自己调试时贴在工作台边的参考每次写脚本或者排查问题时都靠它快速定位。协议本身并不复杂但细节很多一张速查表能大大降低记忆负担。经验层面我还想分享两个心得。第一遇到 DFU 下载失败时不妨先用 Wireshark 或 USB 分析工具抓包看看主机实际发送的命令序列是什么。很多时候你以为自己发了正确的命令但实际发送的字节可能因为大小端序或者长度字段错误而完全不对。抓包能直观地暴露问题。第二USART 模式下如果某次下载卡在某一块数据上始终 NACK不要反复重置设备。先把波特率降下来再把单块数据长度从 256 降到 64绝大多数情况下都能绕过问题。这背后的原因多半是主机端串口缓冲溢出而不是协议错误。关于如何拿到官方参考资料这些在 ST 官网都能找到。AN5275 文档本身是公开的你还可以配合参考手册 RM0436针对 STM32MP157x里的“系统启动”章节一起看。文档之间会有大量交叉引用阅读时别只盯着一张表把协议规范、地址映射和实际工具行为结合起来看理解会快很多。另外我强烈建议你准备好一套最小测试环境一块 STM32MP1 开发板、一根能传数据的 USB 线、一个 USB 转串口模块、一台装有 STM32CubeProgrammer 的电脑就够了。不要一上来就投入复杂的量产工具链先用最简单的环境把 DFU 和 USART 下载流程完整跑通再考虑扩展。基础链路通了后面所有上层功能才有意义。这次把 AN5275 里两个协议的个人理解写下来希望能帮你少踩一些我当初踩过的坑。自举程序这个环节看起来只是启动流程里的一小步但它就像大楼的地基地基稳了后面跑 Linux 也好、做产品量产也好都会顺畅很多。