
1. Cortex-M3调试与异常处理寄存器从理论到实战的深度解析在嵌入式系统开发尤其是基于ARM Cortex-M3内核的项目中我们常常会遇到一些“玄学”问题程序跑着跑着就进了HardFault调试器单步执行时行为诡异或者想设置一个数据观察点却不知从何下手。这些问题看似棘手但根源往往在于我们对处理器最底层的“控制面板”——系统寄存器——理解不够透彻。Cortex-M3作为一款经典的微控制器内核其异常与调试架构设计得非常精妙而HFSR、DFSR、DHCSR、DEMCR等寄存器正是这套架构的“仪表盘”和“操纵杆”。很多技术手册和教程只是简单罗列了寄存器的位域定义但真正到了实战调试时你会发现仅仅知道每个位是干什么的还远远不够。你需要知道它们之间如何联动在什么场景下该读哪个、写哪个以及一个误操作可能带来的连锁反应。我在多年的汽车电子和工业控制项目里无数次通过解读这些寄存器从崩溃的边缘拉回系统也踩过不少因为理解偏差而导致的坑。今天我就结合这些实战经验带你深入这些关键寄存器的内部不仅看懂手册更学会用它们解决实际问题。2. 核心寄存器功能定位与系统级视图在深入每一个寄存器的细节之前我们必须先建立一个顶层的系统视图。Cortex-M3的异常和调试机制是一个有机的整体各个寄存器在其中扮演着不同的角色相互协作。如果孤立地看待HFSR或DFSR很容易陷入“只见树木不见森林”的困境。2.1 异常与调试的“三层漏斗”模型你可以把Cortex-M3的异常处理想象成一个三层漏斗。最上层是各种可配置的异常如内存管理故障、总线故障、用法故障它们拥有可编程的优先级可以被屏蔽或启用。中间一层是硬故障HardFault它是一个“兜底”的异常当可配置故障因优先级不够或被禁用而无法激活时就会升级为硬故障。这意味着任何无法被正确处理的严重错误最终都会落到这里。最底层则是调试事件包括断点、观察点、外部调试请求等它们不一定引发异常但会改变处理器的执行流进入调试状态。HFSR寄存器就位于这个漏斗的第二层它负责报告“为什么最终会掉进硬故障这个兜里”。而DFSR寄存器则更多地与最底层的调试事件相关报告“调试器是如何让处理器停下来的”。理解这个层次关系至关重要因为它决定了你排查问题的顺序先看DFSR如果是调试引发的问题再看具体故障状态寄存器CFSR最后才看HFSR。2.2 寄存器访问的“上帝模式”与“凡人模式”访问这些系统寄存器通常有两种途径。第一种是通过处理器内核自身的指令比如MRS和MSR。这就像是软件在“自我观察”和“自我调整”但权限有限有些寄存器如DHCSR的C_DEBUGEN位明确规定不能由内核自身设置。第二种也是更强大的途径是通过调试访问端口DAP通常是SWD或JTAG接口由外部调试器如J-Link ST-Link以“上帝视角”直接访问处理器的内存映射空间。这些寄存器的偏移地址如HFSR的0xE000_ED2C就是相对于系统控制块SCB基地址的。调试器可以无视处理器的运行状态直接读写这些寄存器这对于修复一个已经死锁的系统是唯一的手段。注意在编写异常处理函数如HardFault_Handler时你只能使用第一种方式MRS/MSR指令来读取HFSR、CFSR等寄存器。而如果你想在程序运行时动态启用调试或设置向量捕获则往往需要调试器介入或者在代码中通过设置CoreDebug-DEMCR等外设寄存器来实现这属于第一种方式访问内存映射地址。3. 硬故障状态寄存器HFSR深度剖析与故障溯源实战当你的程序陷入HardFault一切似乎都停止了这是最让人紧张的时刻。HFSR地址0xE000_ED2C是你的第一盏“探照灯”。它是一个写1清除W1C的寄存器这意味着你通过写1来清除对应的状态位读操作不会影响其值。3.1 HFSR关键位域实战解读手册上对DEBUGEVT、FORCED、VECTTBL这几个位的描述可能有些晦涩我们结合真实场景来翻译一下DEBUGEVT (位31)调试事件引发的硬故障。这个位被置1的情况比较特殊它意味着一个本应触发调试事件如断点的操作因为调试功能未被正确启用而“降级”成了硬故障。什么情况下会发生执行了BKPT指令但停机调试Halting Debug和调试监视器Debug Monitor都被禁用了。此时处理器无法进入调试状态BKPT指令就会触发一个硬故障。在仅启用调试监视器的模式下执行了BKPT指令但当前优先级高于调试监视异常DebugMon_Handler的优先级。此时调试监视异常无法抢占BKPT同样会引发硬故障。实战心得如果你的产品固件在最终发布时禁用了调试接口为了安全或低功耗但代码中遗留了BKPT指令可能是之前调试用的那么上电后程序会直接跳入HardFault并且HFSR的DEBUGEVT位会被置位。排查方法就是检查链接脚本和启动代码确保发布版本的代码段中没有0xBEABThumb状态的BKPT #0xAB这类指令码。FORCED (位30)强制升级的硬故障。这是最常见的情况。它表示一个可配置的故障内存管理、总线故障、用法故障发生了但由于其异常被禁用比如在NVIC中关掉了BusFault_Handler或者其优先级不高于当前执行环境的优先级包括被BASEPRI寄存器屏蔽导致它无法激活。根据Cortex-M3的异常模型这个故障就被“强制升级”给了硬故障来处理。此时硬故障服务例程必须去查询其他寄存器来找到元凶。排查流程一旦发现FORCED位为1你的HardFault_Handler应该立即去读取可配置故障状态寄存器CFSR并检查其中的MMFSR、BFSR、UFSR子域定位到具体的故障类型如非法内存访问、未对齐访问、除零错误等。VECTTBL (位1)向量表读取故障。在处理器响应异常包括复位时需要从向量表中加载异常处理函数的入口地址。如果这次读操作本身产生了总线错误比如向量表地址配置错误或者该内存区域不可读那么就会触发一个硬故障并且此位置位。此时程序计数器PC会被更新为触发异常的那条指令的地址而不是异常处理函数的地址。典型场景在启动阶段如果你将向量表重定位到了SRAM通过SCB-VTOR但SRAM尚未初始化或供电不稳定随后发生了一个中断处理器去SRAM取向量时就会总线错误触发此硬故障。另一个常见原因是将向量表地址设置到了一个非32字节对齐的地址Cortex-M3要求向量表地址至少128字节对齐M3/M4通常要求256字节对齐。3.2 编写一个信息丰富的HardFault_Handler仅仅打印HFSR的值是不够的。一个健壮的故障处理程序应该自动完成上述溯源工作。下面是一个用C语言和内联汇编实现的增强型HardFault_Handler示例它不仅能捕获HFSR还能自动获取并分析CFSR甚至尝试获取出错的堆栈帧和程序地址这些信息对于离线分析比如通过日志输出至关重要。// 用于存储故障上下文的全局结构体可根据需要扩展 typedef struct { uint32_t hfsr; uint32_t cfsr; uint32_t mmfar; uint32_t bfar; uint32_t r0; uint32_t r1; uint32_t r2; uint32_t r3; uint32_t r12; uint32_t lr; // Link Register (EXC_RETURN) uint32_t pc; // Program Counter at fault uint32_t psr; // Program Status Register } FaultContext_t; volatile FaultContext_t g_faultCtx; __attribute__((naked, noreturn)) void HardFault_Handler(void) { __asm volatile( tst lr, #4\n\t // 检查EXC_RETURN的位2判断使用的是MSP还是PSP ite eq\n\t mrseq r0, msp\n\t // 如果使用MSP将其存入r0 mrsne r0, psp\n\t // 如果使用PSP将其存入r0 ldr r1, g_faultCtx\n\t // 将全局结构体地址存入r1 // 保存通用寄存器 R0-R3, R12, LR, PC, PSR // 注意此时的R0已是堆栈指针我们需要从堆栈帧中加载原始的R0 ldmia r0!, {r2-r5}\n\t // 从堆栈加载R0, R1, R2, R3到r2-r5 (r0此时是栈帧指针) stmia r1!, {r2-r5}\n\t // 存储到g_faultCtx.r0~r3 ldr r2, [r0, #16]\n\t // 从堆栈加载R12 (偏移量 4*4) str r2, [r1], #4\n\t // 存储到g_faultCtx.r12 ldr r2, [r0, #20]\n\t // 加载LR (EXC_RETURN) str r2, [r1], #4\n\t // 存储 ldr r2, [r0, #24]\n\t // 加载PC str r2, [r1], #4\n\t // 存储 ldr r2, [r0, #28]\n\t // 加载PSR str r2, [r1], #4\n\t // 存储 // 现在读取系统寄存器 ldr r0, 0xE000ED2C\n\t // HFSR地址 ldr r2, [r0]\n\t ldr r1, g_faultCtx\n\t str r2, [r1, #0]\n\t // 存储到g_faultCtx.hfsr ldr r0, 0xE000ED28\n\t // CFSR地址 (包含MMFSR, BFSR, UFSR) ldr r2, [r0]\n\t str r2, [r1, #4]\n\t // 存储到g_faultCtx.cfsr ldr r0, 0xE000ED34\n\t // MMFAR地址 ldr r2, [r0]\n\t str r2, [r1, #8]\n\t // 存储到g_faultCtx.mmfar ldr r0, 0xE000ED38\n\t // BFAR地址 ldr r2, [r0]\n\t str r2, [r1, #12]\n\t // 存储到g_faultCtx.bfar // 在此处你可以将g_faultCtx的内容通过串口、RTT、或保存到非易失性存储器 // 例如调用一个用C写的日志函数 fault_log(g_faultCtx); // 注意调用C函数前需确保堆栈有效。这里为了简单我们进入死循环。 b .\n\t // 死循环等待调试器介入 ); }这个处理程序首先通过检查LR此时的值是EXC_RETURN来判断故障发生时使用的是主堆栈MSP还是进程堆栈PSP从而找到正确的堆栈帧。然后它从堆栈帧中提取关键的寄存器上下文包括触发故障时的PC值这能直接告诉你代码在哪条指令附近出了问题。最后它读取了HFSR、CFSR、MMFAR、BFAR为后续分析提供了完整的数据包。4. 调试故障状态寄存器DFSR与调试控制实战如果说HFSR是用于处理“灾难性错误”的那么DFSR地址0xE000_ED30就是调试工程师的“日常工具”。它清晰地告诉你处理器为什么以及如何进入了调试状态 halted state。4.1 DFSR各状态位场景化解读DFSR也是一个写1清除的寄存器它的每一个位都对应一种进入调试状态的方式HALTED (位0)外部调试请求。当调试器通过DAP发送一个 halt request 信号时处理器会在下一条指令边界停止并置位此位。这是最常用的“暂停”操作。在调试器点击“暂停”按钮后你可以通过查询此位确认处理器是否因调试器请求而停止。BKPT (位1)断点指令执行。当处理器执行一条BKPT指令时此位置位。这里有个关键点BKPT指令是否导致处理器停止取决于调试配置。如果停机调试使能DHCSR.C_DEBUGEN1则处理器直接进入调试状态如果只有调试监视器使能则触发DebugMon异常如果两者都禁用则如前所述会触发硬故障HFSR.DEBUGEVT置位。DWTTRAP (位2)数据观察点Data Watchpoint匹配。这是DWTData Watchpoint and Trace单元的功能。你可以设置一个内存地址或地址范围和访问类型读、写、读写当处理器访问该地址时触发此事件并置位该位。这是排查内存数据被意外篡改的利器。VCATCH (位3)向量捕获Vector Catch。这是DEMCRDebug Exception and Monitor Control Register寄存器提供的功能。你可以使能对特定异常如复位、硬故障、总线故障等的“捕获”。当使能的异常发生时处理器不会立即跳转到其服务例程而是先进入调试状态。这对于在异常入口的第一时间检查系统状态非常有用。此位置位时通常需要结合DEMCR的VC_*位来确定具体捕获了哪个异常。EXTERNAL (位4)外部调试请求。这个信号通常来自芯片内部的其它核心或外设在Cortex-M3单核系统中较少使用功能上与HALTED类似但来源不同。4.2 利用DFSR进行高效调试的流程一个高效的调试流程离不开对DFSR的主动利用而不是仅仅在调试器暂停后被动地查看。初始化调试在调试会话开始时通过调试器或初始化代码确保DHCSR.C_DEBUGEN被置位启用停机调试。设置断点与观察点在代码中设置软件断点实际是插入BKPT指令或利用FPB单元或通过DWT设置硬件观察点。运行与停止当程序停止时第一件事就是读取DFSR。假设你看到BKPT和HALTED位同时为1这通常是因为你在调试器中设置了断点导致BKPT然后手动点击了暂停导致HALTED。你需要清除这些位写1以便区分下一次停止的原因。单步执行当你使用调试器的“单步”功能时调试器会设置DHCSR.C_STEP1并清除C_HALT。处理器执行一条指令后再次停止此时DFSR的HALTED位会因为单步完成而置位。注意单步过程中如果使能了C_MASKINTS则可屏蔽中断会被屏蔽这会影响带中断的单步调试行为。处理意外停止如果程序在你不希望的地方停止了检查DFSR。如果是DWTTRAP说明触发了数据观察点去检查你的DWT配置和访问的内存地址。如果是VCATCH说明发生了你设置要捕获的异常去检查DEMCR和异常现场。重要提示DFSR的位是“粘性”的意味着多个事件可以同时置位多个位。例如一个数据观察点DWTTRAP触发的同时调试器也可能发出了一个halt请求HALTED。因此在分析完一次调试停止事件后最佳实践是手动向DFSR写入读取回来的值即写1清除所有已置位的位为下一次调试事件做好准备。否则历史状态位会干扰你对新事件的判断。5. 调试控制与状态寄存器DHCSR/DCRSR/DCRDR的精细操控DHCSR、DCRSR和DCRDR这三个寄存器构成了通过调试端口直接控制内核的“金三角”。它们允许调试器在处理器停止时深入检查和修改其几乎所有的内部状态这是普通运行时代码无法做到的。5.1 DHCSR调试状态的“总开关”DHCSR地址0xE000_EDF0是一个需要特殊“钥匙”才能写入的寄存器任何写操作其高半字bits[31:16]必须为0xA05F否则写入会被忽略。这是一种安全机制防止程序意外修改调试状态。C_DEBUGEN (位0)调试使能。这是调试功能的根开关。此位只能通过调试访问端口DAP设置内核自身的代码无法设置或清除它。这意味着如果你的产品固件在启动后关闭了此位例如为了省电或安全那么软件断点BKPT、单步、数据观察点等功能将全部失效调试器将无法再停止内核。C_HALT、C_STEP、C_MASKINTS等控制位在C_DEBUGEN0时都会被硬件忽略。C_HALT (位1)停机控制。调试器写1可以请求处理器停止。当处理器因断点、观察点等事件停止时此位也会被硬件自动置1。此位在系统复位时会被清除。这就是为什么在调试一个从复位开始运行的代码时你需要先让调试器“连接”并设置C_HALT1才能在其运行前设置断点。C_STEP (位2)单步控制。仅在C_DEBUGEN1且S_HALT1处理器已停止时才能被修改。设置此位为1并清除C_HALT处理器会执行一条指令后再次停止S_HALT自动置1。C_MASKINTS (位3)中断屏蔽。同样只能在停止状态下修改。如果置1则在单步或从停止状态恢复执行时可屏蔽中断PendSV, SysTick, 外部中断将被屏蔽。这对于调试中断服务例程ISR或分析非中断代码路径非常有用。S_HALT (位17)停机状态。这是一个状态位只读虽然描述为R/W但高半字钥匙中对应位必须写1。为1表示内核当前处于调试停止状态。这是判断处理器是否可被调试器操作的主要标志。S_REGRDY (位16)寄存器访问就绪。当通过DCRSR/DCRDR访问内核寄存器时此位指示上一次访问是否完成。在读取寄存器数据前必须轮询此位为1。5.2 DCRSR与DCRDR内核寄存器的“后门”这是调试器能够查看和修改R0-R15、xPSR、MSP、PSP等核心寄存器的底层机制。选择寄存器调试器向DCRSR地址0xE000_EDF4写入一个值。其中REGSEL[4:0]指定要操作的寄存器编号见手册列表如0x10是xPSRREGWNR位指定是读0还是写1操作。等待就绪调试器轮询DHCSR的S_REGRDY位直到它变为1表示内核已准备好数据传输。数据传输如果是读操作调试器从DCRDR地址0xE000_EDF8中读取数据即为所选寄存器的值。如果是写操作调试器将想要写入的值先放入DCRDR然后执行步骤1的写DCRSR操作REGWNR1。再次等待就绪完成传输后需要再次等待S_REGRDY变为1才能发起下一次寄存器访问。实战陷阱这个访问过程不是原子的且需要严格的顺序。如果调试器在S_REGRDY为0时就发起新的DCRSR写操作结果将是不可预测的。成熟的调试器软件如OpenOCD PyOCD的底层驱动会妥善处理这些序列。但如果你自己在写底层调试脚本或工具必须严格遵守这个流程。6. 调试异常与监视器控制寄存器DEMCR的进阶应用DEMCR地址0xE000_EDFC主要管理两件事向量捕获Vector Catch和调试监视器Debug Monitor控制。它同样不受系统复位影响只在上电复位时清零。6.1 向量捕获在异常入口“设伏”向量捕获功能VC_*位允许你在异常即将被响应时让处理器先进入调试状态而不是直接跳转到异常处理函数。这对于调试难以复现的偶发性异常如某些特定内存访问触发的总线错误极其有用。工作原理当使能了VC_BUSERR总线错误捕获后一旦发生总线错误处理器不会立即激活BusFault异常而是先产生一个调试事件导致内核停止如果C_DEBUGEN1或触发调试监视器异常如果MON_EN1。此时DFSR的VCATCH位会被置位。应用场景假设你的系统偶尔会跑飞最后发现是进入了BusFault。但BusFault发生得太快现场已被破坏。你可以使能VC_BUSERR然后全速运行。当总线错误发生时处理器会立刻停在触发该错误的指令地址上或者其下一条指令边界所有寄存器上下文都保持原样。此时你可以完整地检查内存、外设状态、堆栈精准定位问题根源。注意事项向量捕获是“半同步”的。处理器需要等到当前指令边界才会停止。对于内存访问指令如果错误发生在传输过程中停止点可能在指令之后。6.2 调试监视器非侵入式调试的基石调试监视器模式通过MON_EN等位控制是Cortex-M3除了停机调试之外的另一种调试模式。当调试事件如断点、观察点发生时如果停机调试未使能C_DEBUGEN0但调试监视器使能MON_EN1并且其优先级足够高那么处理器会触发一个DebugMon异常转而执行DebugMon_Handler。与停机调试的区别在调试监视器模式下处理器不会停止而是执行一个软件异常处理函数。这个函数可以记录调试信息、修改内存、甚至修复一些错误条件然后返回让程序继续运行。这对于在不停止系统的情况下进行监控、追踪或实现“飞行记录仪”black box功能非常有用。实战配置要使用调试监视器你需要在NVIC中为DebugMon异常设置合适的优先级通常设为最高或次高。实现DebugMon_Handler函数。在代码中设置DEMCR的MON_EN位。注意DHCSR.C_DEBUGEN的优先级高于MON_EN。如果C_DEBUGEN1则调试事件总是导致停机不会触发DebugMon异常。典型应用在量产产品中出于安全考虑你可能会禁用停机调试接口C_DEBUGEN0。但你仍然可以通过使能调试监视器并在DebugMon_Handler中将关键的故障信息如通过DFSR、HFSR、CFSR获取记录到非易失性存储器如Flash的特定扇区中。当产品在现场出现问题时可以通过读取这片Flash区域来获取“最后时刻”的系统快照。7. 实战问题排查与寄存器操作技巧实录理论最终要服务于实践。下面我整理了几个在真实项目中遇到的典型问题以及如何利用上述寄存器进行排查和解决。7.1 案例一HardFault中FORCED置位但CFSR全为0现象程序进入HardFaultHFSR显示FORCED位为1但读取CFSR0xE000_ED28的值却是0MMFAR和BFAR也是0。分析与排查可能性A栈溢出。这是最常见的原因之一。栈指针MSP或PSP冲垮了其他内存区域如全局变量区破坏了关键数据。当异常发生时处理器需要将8个寄存器压栈如果栈地址非法这个压栈操作本身就会引发一个总线错误。但这个总线错误发生在异常响应的“序幕”阶段此时处理器可能还未来得及正确更新CFSR/BFAR或者更新过程又被破坏。检查方法在HardFault_Handler中尽早通过MSP/PSP读取栈指针检查其是否在链接脚本定义的栈区域范围内。同时检查编译生成的.map文件确认栈大小是否充足。可能性B向量表损坏。如果向量表本身被破坏例如由于野指针写操作处理器在响应任何异常包括HardFault本身时读取向量地址就会失败。这可能导致一种“双重故障”或不可恢复的状态使得状态寄存器更新异常。检查方法在启动阶段将向量表地址SCB-VTOR和其内容特别是前几个异常向量如Reset_Handler, NMI_Handler, HardFault_Handler的地址通过安全方式如初始化时的串口打印输出并验证。可能性C优先级配置错误。如果HardFault本身或其中断优先级被意外设置为可屏蔽理论上HardFault优先级固定为-1不可屏蔽但在某些极端配置下如果使用了BASEPRI寄存器屏蔽了所有中断而一个更高优先级的不可屏蔽异常如NMI正在服务中可能会导致状态更新紊乱。检查方法检查启动代码和任何修改SCB-SHCSR或NVIC优先级的代码。解决策略对于栈溢出最有效的方法是增加栈空间并使用编译器提供的栈溢出检测功能如GCC的-fstack-protector-strong。对于内存破坏可以使用MPU内存保护单元将关键区域如向量表、栈顶底部设置为只读或禁止访问一旦有非法写操作立即触发MemManage Fault便于早期定位。7.2 案例二单步执行时程序“跑飞”不按预期停止现象在调试器中单步执行代码偶尔会发现按下“Step Over”后程序没有停在下一行而是执行了一大段甚至跑飞。分析与排查检查DFSR单步执行后首先查看DFSR。如果HALTED位没有置位说明处理器根本没有进入调试状态。这可能是因为DHCSR.C_DEBUGEN或DHCSR.C_STEP设置有问题。检查DHCSR确认S_HALT是否为1。如果不是说明处理器处于运行状态。检查C_DEBUGEN是否为1。一个常见陷阱在某些低功耗模式下调试模块可能被断电。当处理器被唤醒后C_DEBUGEN可能被硬件清除。你需要确保在进入低功耗模式前调试器连接稳定或者有机制在唤醒后重新建立调试连接。中断干扰单步执行时如果C_MASKINTS0默认且单步的指令执行期间发生了中断处理器会先去响应中断。这会导致你“看丢”很多代码。解决方案在单步调试关键代码段时可以在调试器中临时设置C_MASKINTS1或者直接禁用全局中断__disable_irq()但要注意这可能会影响系统实时性。指令流水线效应Cortex-M3有3级流水线。当你在一条分支指令如BL,BX上单步时调试器停止的地址可能是分支目标地址而不是你源代码的下一行。这是正常现象需要结合反汇编窗口理解。7.3 案例三数据观察点DWT不触发现象在调试器中设置了一个数据观察点Watchpoint希望当某个全局变量被修改时停止但程序运行时从未触发。分析与排查资源限制Cortex-M3的DWT单元通常只提供最多4个硬件观察点。检查你是否已经用满了所有观察点。调试器界面有时不会明确提示资源已耗尽。地址对齐与范围DWT观察点对地址对齐有要求。通常观察点地址必须是所监控数据大小的整数倍例如监控一个uint32_t地址必须是4字节对齐。另外观察点可能只支持特定的地址范围如整个内存空间或者只支持特定大小的数据块如1, 2, 4字节。查阅你的芯片参考手册确认DWT的具体能力。访问类型确认你设置的访问类型读、写、读写与实际发生的访问匹配。例如变量可能只是被读取而你设置了写观察点。DWT使能DWT模块需要被使能才能工作。这通常通过DEMCR寄存器的TRCENA位位24控制。确保在初始化或调试器连接时该位已被置1。TRCENA是DWT、ITM指令跟踪等跟踪组件的总开关。编译器优化如果变量是局部变量且被编译器优化到寄存器中那么就不会有内存访问观察点自然无效。尝试将变量声明为volatile或者使用全局变量进行测试。7.4 寄存器操作的安全与性能贴士安全第一在非调试环境下如量产固件除非有特殊需求如使用调试监视器记录日志否则应确保DHCSR.C_DEBUGEN0和DEMCR.TRCENA0。这可以关闭调试和跟踪模块节省功耗并提高系统安全性防止未经授权的调试访问。性能考量频繁地通过DCRSR/DCRDR读取寄存器比如在调试脚本中循环读取某个变量速度较慢因为它需要内核参与每次传输。对于需要频繁监控的数据考虑使用DWT的数据观察点触发ITM指令跟踪宏单元输出或者直接通过调试器访问内存映射的外设寄存器后者速度更快。复位的影响记住DHCSR.C_HALT和DEMCR的大部分位除了TRCENA在系统复位SYSRESETn时不会被清除只有上电复位POR才会清除。这意味着如果你在调试中设置了C_HALT1然后进行了系统复位内核在复位后可能仍然处于“halted”状态导致程序不执行。此时需要调试器重新连接并清除C_HALT位。DEMCR的向量捕获位如果在复位前被使能复位后依然有效这可能会让你在调试启动代码时感到困惑因为一复位就触发了VC_CORERESET捕获。