DAPLink 下载 STM32 报 Contents mismatch 排查记录

最近在使用 DAPLink 给 STM32F103C8T6 下载程序时,Keil 出现下面错误:

1
2
3
4
5
6
7
8
9
10
11
Full Chip Erase Done.
Programming Done.
Contents mismatch at: 08000000H (Flash=7CH Required=40H) !
Contents mismatch at: 08000001H (Flash=02H Required=0DH) !
Contents mismatch at: 08000002H (Flash=04H Required=00H) !
Contents mismatch at: 08000003H (Flash=06H Required=20H) !
Contents mismatch at: 08000008H (Flash=40H Required=45H) !
(......)
Contents mismatch at: 08000192H (Flash=19H Required=10H) !
Too many errors to display !
Error: Flash Download failed - "Cortex-M3"

尝试换用其他新的单片机和 DAPLink,上述问题仍然存在;而改用 ST-Link 则可以正常下载。参考 STM32 用 DAPLINK 下载报错处理办法_daplink下载出现错误-CSDN博客 的方法,在 Cortex-M Target Driver Setup 的 Debug 页把 Reset 选项由默认的 Autodetect 改为 SYSRESETREQ 后,DAPLink 下载恢复正常。

可是这是为什么呢?为什么同样情况下 ST-Link 没问题而 DAPLink 会有问题,它们有什么样的区别,为什么在 Keil 中更改了 Reset 选项就恢复正常了。下面是我的一些学习总结仅供参考,有不对的地方恳请指出。


一、「编译 → 下载 → 运行」链路

首先在 Keil 这种集成开发环境中调用编译器,对用户编写的 C 语言代码进行编译,生成单片机能够看懂的 Hex / Bin 可执行映像(Hex 是 Intel HEX,本质是 ASCII 文本;Bin 是纯二进制),然后向下载器下达调试 / 下载指令。下载器(ST-Link / J-Link / DAPLink 这类硬件)负责把编译好的映像写入单片机的 Flash 闪存。下载前,Keil 会先通过下载器让单片机复位,好把内核接管过来、可靠地擦写 Flash;下载后还会再复位一次(对应 Download 页的 Reset and Run),让新程序从向量表重新跑起来。

单片机(以常见的 ARM Cortex-M 内核为例)刚复位时,CPU 会到地址 0x00000000(STM32 上 BOOT0=0 时,这里是 Flash 起始地址 0x08000000 的重映射别名)去读中断向量表的头两项:

参考自STM32F10xxx参考手册(中文)。

  • 第一个字:栈顶指针(MSP)。
  • 第二个字:复位向量(Reset Vector)。这是一个内存地址,指向单片机上电后要执行的第一条指令。

CPU 取到 Reset Vector 后,跳转到启动文件里的 Reset_Handler。它只做两件事,之后就把控制权完全交出去:

  1. 调用 SystemInit —— 初始化时钟。时钟在这里配置,但真正干活的是 C 函数 SystemInit(),汇编只是调用它。(具体配到什么频率要看用哪套库:SPL 的 system_stm32f10x.c 默认激活 SYSCLK_FREQ_72MHz,会把主频配到 72 MHz;CubeMX/HAL 的 SystemInit() 基本只把 RCC 复位到默认状态,真正的时钟配置在 main.c 的 SystemClock_Config() 里,由 main() 调用。)

  2. 跳到 C 运行库入口 __main,由它做分散加载(__scatterload):把 Flash 里的 .data 初值复制到 RAM、把 .bss 段清零。

    聊聊程序分散加载启动的奥秘-腾讯云开发者社区-腾讯云

    深入解析程序内存布局代码段数据段BSS堆栈-开发者社区-阿里云

    这一步是 C 运行库做的,不是 startup 汇编自己做的。

接下来的事已经不归 Reset_Handler 管了:__main 完成数据搬运后,再由 C 运行库调用 main(),进入我们的 while(1) 主循环。

二、回到报错本身:这是「校验失败」,不是「写入失败」

日志顺序是 Full Chip Erase Done. → Programming Done. → Contents mismatch ...,说明擦除和写入都已经走完了,失败的是最后一步回读校验(Verify)——读回来的字节和要写的对不上。

所以问题并不出在「写不进 Flash」,而是出在整个下载过程中「复位 + 接管内核」的时序上。

三、回答那三个「为什么」

1)为什么 ST-Link 可以、DAPLink 不行?它们到底差在哪?

两者是不同厂家的调试器,固件和 PC 端驱动都不一样,默认走的复位路径也不同。

  • ST-Link:走 ST 自家驱动,默认用 SWD 上的软件复位——这才是它不接复位线也一切正常的原因。
  • DAPLink:在 Keil 里走的是通用 CMSIS-DAP 驱动,而 CMSIS-DAP 协议里「硬件 nRESET 线」是一等公民(有专门查询 / 执行硬件复位引脚的命令)。当 Reset 停在 Autodetect 让 Keil 自己猜时,驱动倾向于选硬件复位路径。

2)为什么改了 Reset 选项就恢复正常?

Autodetect 会让 Keil 自己挑复位方式,它可能挑到需要 nRST 硬件线的方式,而 DAPLink 没有(或没用上)这根线,于是复位实际没有发生,下载流程里「停住内核 → 把 Flash 算法搬进 RAM → 让目标跑算法」这几步就都是在目标状态未知的情况下做的;加上板子原来的程序还在跑(可能重配了 PA13/PA14、进了低功耗、开了看门狗等),于是时序错乱、校验对不上。

这里的关键是:Keil 的 Flash 编程算法并不是跑在 PC 上,而是几百字节的 ARM 机器码被搬进目标 RAM、由目标内核自己执行(它去敲 FLASH->KEYR / CR / SR 完成擦写),用的也是目标的时钟。所以只要复位没做干净、目标不在已知状态,这段算法随时可能被原程序抢 RAM、被看门狗打断、或在错误的时钟下运行 → 写进去的就是垃圾,校验自然满篇 mismatch。

而 SYSRESETREQ 是 Cortex-M 内核自带的软件系统复位:调试器通过 SWD 往内核的 SCB->AIRCR 寄存器(地址 0xE000ED0C)写入 0x05FA0004 就能触发系统复位,完全不需要 nRST 引脚,复位时序确定且可复现,所以问题消失。


结论:遇到这个报错,把 Cortex-M Target Driver Setup → Debug 页的 Reset 由 Autodetect 改成 SYSRESETREQ,就能绕过对 nRST 的依赖;根治办法是给 DAPLink 补一根 nRST(开漏)接到目标板的 NRST。