尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32堆栈设置与HardFault排查:从启动文件到内存布局实战

STM32堆栈设置与HardFault排查:从启动文件到内存布局实战 很多时候一提起STM32崩溃大家条件反射就是改启动文件里的Stack_Size。我以前也这么干过把Stack_Size从0x400改成0xA000结果芯片直接HardFault查了半天发现Heap和Stack的内存布局被我搞乱了。这让我明白Heap、Stack、启动文件三者是互相绑定的不明白原理就改参数反而会带来更隐蔽的问题。这篇文章围绕STM32堆栈设置的完整逻辑讲清楚栈大小怎么估算、堆大小怎么判断、出了HardFault怎么定位最后会给出可直接套用的检查清单。1. 启动文件里的两行常量为什么敢改的人都不懂1.1 一个真实翻车案例Stack_Size改到0x2000后直接HardFault先说我自己的翻车经历。有一块STM32F103C8T6只有20KB SRAM项目里已经塞了两个全局数组一个做串口接收缓冲一个做传感器数据缓存加起来大概8KB。原本启动文件里Stack_Size是0x400也就是1KBHeap_Size是0x200512B。设备跑一段时间后会偶发死机我当时想都没想直接把Stack_Size改成0x2000也就是8KB觉得“栈大总不会是坏事”。改完编译没报错下载后一上电就直接HardFault连main的第一行都没跑进去。我当时整个人懵了栈变大了怎么还能让系统起不来后来把RAM布局拉出来看才明白F103C8T6总共就20KB RAM静态数据已经占了8KB我硬塞一个8KB的栈进去再加上512B的堆内存总量已经逼近甚至超过20KB。链接器虽然勉强把段放了下去但堆区被压缩到几乎没有剩余空间启动初始化时某个模块调用了malloc申请一块20B的缓冲区malloc返回了NULL代码没检查直接往地址0附近写数据于是系统一开始就跑飞了。这个案例告诉我们堆栈大小不是一个可以随便填的“舒服值”它直接决定了整个RAM区域的划分。你动了栈堆就会被挤动了堆静态区可能就会撞车。所以想调堆栈第一步是搞清楚它们到底被放在了哪里。1.2 启动文件里Stack_Size和Heap_Size的真实作用在Keil的STM32启动文件startup_stm32f103xe.s里Stack和Heap的定义通常长这样AREA STACK, NOINIT, READWRITE, ALIGN3 Stack_Mem SPACE Stack_Size __initial_sp Heap_Size EQU 0x00000200 AREA HEAP, NOINIT, READWRITE, ALIGN3 Heap_Mem SPACE Heap_Size __heap_base __heap_limit其中Stack_Size EQU 0x00000400就是在SRAM里保留1KB作为系统栈。Stack_Mem SPACE Stack_Size真正划出这块空间。__initial_sp被链接器导出最终会用来初始化MSP寄存器。Cortex-M上电后处理器从向量表偏移0读出这个值直接放进SP所以它必须是有效的RAM地址。Heap_Size同理它在SRAM里保留一段空间给C库的malloc/free使用。__heap_base和__heap_limit这两个符号会被C库运行时引用malloc就是从__heap_base开始向上找空闲内存直到__heap_limit为止。很多人以为启动文件里这两行是“建议值”或者“最大值”其实它们定义的是两块真实占RAM的区域。程序里其他数据段、数组、RTOS内核用的内存都要和这两块区域共享同一片物理SRAM。1.3 链接脚本(.sct/.ld)如何在背后二次分配启动文件定义好这两个区域之后真正决定它们落在什么地址的是链接脚本。Keil用的是分散加载文件.sct里面会把启动文件里的STACK和HEAP段安排到RAM区域LR_IROM1 0x08000000 0x00010000 { ER_IROM1 0x08000000 0x00010000 { *.o (RESET, First) *(InRoot$$Sections) .ANY (RO) } RW_IRAM1 0x20000000 0x00005000 { startup_stm32f103xe.o (STACK, HEAP) .ANY (RW ZI) } }这里.ANY (RW ZI)会把所有全局变量、静态变量放进来启动文件里的STACK和HEAP也在这个区域里。链接器按段属性决定地址分配如果RAM装不下所有内容会有“region overflow”错误。但有时候栈设得太大其他段被挤到一起编译也能过运行却可能一启动就崩。在STM32CubeIDE这样的GCC环境里情况又不一样。CubeMX生成的.ld脚本里设置堆栈的是两个符号_Min_Heap_Size 0x200 ; _Min_Stack_Size 0x400 ;然后链接脚本里有个._user_heap_stack区域._user_heap_stack : { . ALIGN(8); PROVIDE ( end . ); . . _Min_Heap_Size; . . _Min_Stack_Size; . . ALIGN(8); } RAM所以如果你在CubeIDE里直接改启动文件里的Stack_Size EQUGCC汇编器往往会直接报错或者改了也没人理会。正确做法是去改.ld里的_Min_Stack_Size和_Min_Heap_Size。这个跨工具链差异是很多从Keil平移CubeIDE的项目第一个大坑。2. 栈和堆的本质一个向下长一个向上长共用一块SRAM2.1 栈的地址布局从高地址往低地址长ARM Cortex-M内核的栈是满递减栈SP指向当前栈顶压栈时SP先减小再写入数据。也就是说栈是从高地址往低地址方向生长的。可以把RAM想象成一个从天花板往下堆叠的仓库栈就是一堆从顶部往下叠的箱子。SP是当前箱子所在的位置每调用一层函数就往下叠一个新箱子。函数执行完箱子被拿走SP回到原来位置。启动文件里的__initial_sp是栈顶通常会被放到RAM的高端。比如F103C8T6的RAM地址范围是0x20000000到0x20005000那么__initial_sp常常是0x20005000。栈区域从0x20005000往下占1KB的栈就是0x20004C00到0x20005000。2.2 堆的地址布局从静态区末尾往高地址长堆的方向和栈正相反。程序里的全局变量、静态变量会被链接器放在RAM的低地址区域。这些数据段结束之后剩下没有被静态数据占用的空间就可以给堆使用。堆从低地址往高地址生长malloc每申请一块内存堆分配器就往上移动指针把空间分出一块同时还要在每块内存前面记录管理信息。所以给一个MCU画RAM图通常从低地址到高地址依次是.data段.bss段堆区向上栈区向下栈顶。两个方向相向而行中间是自由空间谁也不知道剩下的“真空地带”到底属于谁谁先申请谁先用。2.3 栈堆碰撞两个方向相遇时会发生什么当程序调用函数层次过深或者局部变量太大栈不断向下生长就会碰到堆区的顶部。这时栈上的返回地址、局部变量会覆盖堆分配器维护的管理结构甚至覆盖堆里的数据块。反过来如果malloc疯狂向上申请把堆顶顶到栈底堆里的数据就可能覆盖栈上正在使用的局部变量。这种破坏不是立刻可见的可能会等到那个局部变量被下一次使用或者free那一段内存时系统才莫名其妙地挂掉。在RAM很小的MCU上栈堆碰撞是最难排查的死机原因之一因为故障点离真正的“案发现场”往往隔了很长的调用链。你看到的HardFault可能发生在A函数真正改写内存的是B函数里那个超大的局部数组。2.4 为什么STM32没有MMU栈溢出往往“死得莫名其妙”x86这类带MMU的处理器访问非法地址时MMU能立刻抛异常告诉你地址错了。STM32这种MCU大部分没有MMU只有部分型号有简单的MPU。默认情况下栈溢出后的写操作会直接落到相邻RAM地址上CPU完全不会感知直到被破坏的数据被使用。这就造成了嵌入式里常见的“延迟爆炸”问题你没在栈溢出的瞬间死机而是在几十毫秒后某个被踩坏的全局变量被别的地方读到程序才彻底跑飞。这个时间差让复现和定位都变得非常困难所以排查栈溢出别指望靠断点一次抓到很多时候要靠水位标记和长期运行监测。3. 栈大小估算从“拍脑袋”到“有依据”3.1 函数调用深度与中断嵌套最坏情况叠加裸机环境下系统栈的需求可以按这个最坏情况公式估算栈需求 ≈ 主程序最大调用链路栈用量 最坏中断嵌套栈用量总和主程序最大调用链路是指从main开始一层层调用下去中间没有返回时所有函数占的栈的总和。中断嵌套栈用量要分两种情况如果允许A中断被B中断打断那么A和B的栈用量要一起叠加到主程序之上。比如main最深处函数调用链用了500B中断A服务函数要用200B中断B服务函数要用100BB可以打断A那么最坏栈峰值就是500 200 100 32 832B最后那个32B是Cortex-M进入中断时硬件自动压栈的8个寄存器xPSR、PC、LR、R12、R3-R0如果用到FPU或者双精度浮点压栈数据会更多。因此启动文件里的Stack_Size只设1KB在这类场景下其实是临界状态非常危险。3.2 单层调用帧的近似计算局部变量临时数据想知道每个函数到底占多少栈可以靠编译器输出。在GCC/armclang环境下给编译选项加上-fstack-usage编译器会生成一个.su文件里面记录每个函数的最大栈使用量。armcc环境则可以用--stack_usage选项。得到每个函数的栈用量后找出调用路径中最坏的那条“串”把它们相加。这个过程人工做很麻烦但如果项目结构不复杂主要就是看那几个大数组和深层调用链。比如某个函数里定义了一个uint8_t buf[128]那这个函数单层的栈消耗至少就有128B再加上返回地址和寄存器保存很容易超过160B。如果工具链没开栈用量分析就按经验估算一个普通函数局部变量不多的情况下栈消耗大概在16到32B带一个128B的局部数组大概在160B左右带printf、sprintf这类格式化函数单层轻松上到200到400B。把这些数量级记在心里再去算调用链误差不会太离谱。3.3 实测法1Keil的栈使用量报告Keil开发环境下如果用的是ARMCC可以打开编译选项让编译器把栈使用信息输出到Listing文件。编译完成后在生成的.lst文件里找“Stack Usage”区域能看到每个函数的栈消耗。还有更暴力的方法直接把栈区域填上特征值跑一会儿程序再停下来看水位。比如把栈区域的每个字节填成0xABABABAB运行一段业务逻辑后暂停从栈顶地址向下找第一个不是0xABABABAB的位置就是栈曾经触碰到的最大深度。这个水位法在实际工程里非常实用但要注意填充的时机。最好是在程序刚复位后用调试器的“Memory Fill”功能直接填充RAM中的栈区域然后再启动程序。不要在main里写一个循环去刷因为跑到main的时候栈上已经存了返回地址和局部变量直接刷会把当前运行现场破坏掉。3.4 实测法2栈填充0xABABABAB后的水位排查具体操作流程可以这样在调试器里查启动文件里__initial_sp的值假设是0x20005000。根据Stack_Size算出栈底地址假设栈大小是0x800那么栈底是0x20004800。用调试器的内存窗口把0x20004800到0x20005000区域全部填充成0xAB。全速运行程序复现实际业务场景比如跑一整天或者触发最深的函数调用。暂停程序从0x20005000向下查看找到连续0xAB终止的位置。如果栈已经用到接近0x20004800说明栈快要触底了。这时才需要把Stack_Size适当加大。水位法比单纯看SP寄存器更准确因为它记录的是“历史最高水位”哪怕你现在所在的函数很浅也能看出之前深函数调用留下的痕迹。3.5 实测法3通过调试器实时查看SP寄存器水位的缺点是只能手动查看不能在运行时实时报警。更简单直接的方法是看SP寄存器。程序停在HardFault时在调试器寄存器窗口里读出SP的值。如果SP已经低于栈底地址基本可以断定栈溢出了。这里的栈底地址是栈区域的最低地址不是栈顶。比如栈区域是0x20004800到0x20005000那么SP小于0x20004800就是溢出。要注意的是在启用了RTOS的情况下中断会使用MSP任务会使用PSP发生异常后要区分是哪个SP溢出。可以看一下异常返回的LR寄存器如果LR的bit2是1说明使用的是PSP任务栈否则是MSP系统栈。如果看不懂LR最简单的办法是在HardFault_Handler里把这两个SP都保存下来之后一起去分析。还可以给HardFault_Handler加一个故障现场记录功能把当前的PC、LR、SP存到一片专用RAM里。真机上电复位后把数据dump出来比每次插着调试器复现方便得多。4. 堆的配置比栈更容易被忽略的定时炸弹4.1 malloc、new与堆大小之间的关系很多裸机项目里堆只服务一个对象malloc。如果你在代码里写了malloc(100)这个100B就是从启动文件Heap_Size定义的区域里分配的。C里的new底层同样会调堆分配函数。堆大小并不等于“能成功malloc到的最大字节数”。因为堆管理器需要在每块已分配内存中保存头部信息即使你malloc几十个字节实际消耗的内存可能比请求的多出8到16B具体取决于工具链的库实现。所以Heap_Size 0x10004KB并不代表你能连续malloc出4个1KB块能分到多少还要看分配器开销和块的排列。另外很多人会混淆FreeRTOS里的configTOTAL_HEAP_SIZE。那是FreeRTOS内核独立管理的一块内存池用来创建任务栈、队列、信号量。它和启动文件里的Heap_Size是两回事不要放在一起考虑。4.2 内存碎片不是堆大小不够而是堆被“切碎”了碎片化是嵌入式动态内存里最经典的问题。我遇到过这样一个情况设备跑一整天都不出问题跑到第二天malloc突然返回NULL。把堆相关数据都拉出来看总空闲空间还剩6KB但要申请800B就是失败。原因是程序运行过程中每次网络数据到达都malloc一块缓存区用完再free。数据长度有大有小Windows下malloc和free的时序又不固定堆里逐渐形成很多“蜂窝煤”一样的小空洞。每个空洞都不超过100B但总空闲加起来很多。这就是典型的碎片问题。堆大小本身并没有不够而是空闲空间不再连续。你可以用malloc申请几个固定大小对象来验证也可心在程序里加一个堆完整性检测模块定期统计自由列表。不过最省心的还是从设计上避免动态分配或者用固定槽位的内存池。4.3 ARMCC的MicroLib与标准C库堆管理差异在Keil里有一个选项叫“Use MicroLib”。勾选之后C库会换成精简版malloc和free的实现也更简单代码占用小很多适合小容量MCU。但MicroLib也有代价。它对C标准支持没全量标准库那么完整printf默认不支持浮点输出一些高级混合函数可能行为不同。更关键的是它的堆管理算法比较简单碎片化可能比标准库更快。如果不勾MicroLib标准ARM C库的malloc功能强一些但占用的代码空间和内部全局状态也更大。选择哪种取决于芯片Flash和SRAM的余量。在空间紧张的小芯片上我一般推荐MicroLib配合静态内存池尽量避免在运行时malloc。在GCC/Newlib环境下还有一个额外的坑malloc底层依赖_sbrk系统调用而这个函数必须明确告知堆的起始地址和结束地址。如果链接脚本里没有正确提供堆边界malloc可能分配到错误地址甚至踩到栈。移植CubeIDE工程时如果自己改过.ld一定要确认_Min_Heap_Size和end符号没有丢。4.4 用静态缓冲替代动态内存能避掉80%的运行时问题我们在MCU上写程序不是写PC应用。MCU资源极其有限没有操作系统层面的内存保护也没有swap空间动态内存存在的意义没有想象中那么大。我现在的习惯是能用全局数组不用malloc能静态创建RTOS对象不动态创建。包括一些库比如FreeRTOS内部那些队列、互斥量全都用静态版本API。这样在编译阶段就能确定RAM占用运行期不会突然出现malloc失败。如果实在需要用到动态分配比如某些第三方库只提供malloc接口那就采用“启动时一次性分配运行期不释放”的策略。所有对象在系统初始化阶段malloc好之后使用同一个指针。这能避免大部分碎片问题也让堆生命周期非常清晰。5. 启动文件之外RTOS任务栈、链接脚本、C库配置的联动5.1 FreeRTOS任务栈 vs 系统栈Stack_Size的分工在FreeRTOS环境下栈的职责被拆成了两半。任务运行在线程模式下使用的是每个任务自己关联的栈。xTaskCreate里的参数usStackDepth单位是字不是字节。比如usStackDepth 128表示512B很多人第一次用FreeRTOS都会在这里换算错误导致任务栈比预期小不少。而系统栈启动文件里的Stack_Size在FreeRTOS里主要给启动阶段和中断处理使用。Cortex-M的中断默认运行在MSP上也就是系统栈。所以即使你所有用户函数都跑在任务栈里只要中断服务函数里用栈比较大系统栈仍然要留足空间。常见错误是把系统栈设得很小觉得“FreeRTOS已经不让用户任务用MSP了系统栈无所谓”。结果高优先级中断里调用sprintf或复杂浮点运算直接把系统栈踩穿系统就HardFault。RTOS环境下系统栈仍然至少要保证“最大深度中断嵌套高频中断里最大局部变量”不会被压爆。5.2 __section(.heap)IAR环境下自定义堆数组到底怎么用在IAR开发环境中堆和栈的设置在.icf链接配置文件里define symbol __ICFEDIT_size_heap__ 0x400; define symbol __ICFEDIT_size_stack__ 0x400;不过很多从别处移植过来的工程里还能看到这种代码uint8_t ucheap[4096] __section(.heap) {0};这是一段IAR环境下把堆定义成显式数组的写法。它把一个4KB缓冲区放到名为.heap的段里等价于手动为C库提供了堆内存空间。用这种写法时要注意如果你又在.icf里设置了__ICFEDIT_size_heap__那么相当于配了两次堆RAM会多占用一份。一般建议二选一。还有样例代码里那个 {0}不是必需的字段让它存在会让这个数组被放到.data段启动时要从Flash复制初始值过来等于多占一份Flash。如果只是想给堆做空间可以去掉初始化让它变成未初始化变量。5.3 STM32CubeIDEGCC下堆栈设置与Keil的差异很多从Keil切到STM32CubeIDE的人第一反应是去启动文件里找Stack_Size。但在GCC工具链里启动文件里根本没有Stack_Size的EQU定义取而代之的是弱符号和_estack引用。在STM32CubeIDE生成的链接脚本末尾会看到这样的定义_estack ORIGIN(RAM) LENGTH(RAM);启动文件里的Reset_Handler会用这行代码设置初始SPldr sp, _estack而栈和堆的大小是在.ld文件里通过_Min_Stack_Size和_Min_Heap_Size控制的。想要增大栈应该去改这个链接脚本。如果你只是在编译器选项里加了个-Xlinker --stack0x1000很可能不起作用因为_estack根本不看这个。下面这个表格是我自己项目里常用的对照供参考工具链环境栈大小配置位置堆大小配置位置最容易踩的坑Keil MDK (ARMCC)启动文件Stack_Size EQU启动文件Heap_Size EQU.sct里STACK/HEAP段冲突Keil MDK (armclang)启动文件Stack_Size EQU启动文件Heap_Size EQU分散加载文件漏更新IAR EWARM.icf配置或__section(.heap).icf配置或__section(.heap)同时配置两处堆区STM32CubeIDE (GCC).ld里_Min_Stack_Size.ld里_Min_Heap_Size改启动文件无效5.4 常见IDE启动文件修改工具链不一致的坑跨工具链移植时最容易出现的问题是去改了一个“看起来像”堆栈设置但实际不生效的地方。比如在CubeIDE里手动往启动文件加一句Stack_Size EQU 0x2000GCC编译器会直接报错。又比如在Keil工程里如果分散加载文件写成RW_IRAM1 0x20000000 0x00005000 { startup_stm32f103xe.o (STACK, HEAP) }然后又在启动文件里把Stack_Size改得很大导致STACK段超过RAM区域链接器会报错如果没报错也可能是HEAP被挤到几乎不存在的位置。还有一类坑和MPU相关。部分STM32型号支持MPU有些人会专门给系统栈设置一个MPU保护区域。这时候启动文件或链接脚本里只要改了栈大小和地址MPU的配置参数也得同步改否则保护区域和实际栈不一致本来该触发的溢出检测就失效了。6. 三类“改堆栈”踩坑现场的完整复盘6.1 现场A中断里调用printf栈被瞬间打爆有个用F103做串口采集的项目USART1收到一帧数据后直接在中断服务函数里调用了printf把数据格式化打印出来。整个工程默认栈是1KB跑了一段时间后高频率数据过来就开始HardFault。排查过程很简单先把程序停在HardFault位置看寄存器发现SP已经跑到了栈底之下。然后用水位法填充栈区域复现一次发现中断里的printf每次调用可能吃掉500甚至600B的栈。加上主程序执行到深处时的栈消耗1KB的栈被瞬间塞满。这个问题的根治方式是不要在中断里做格式化打印。改成中断里只置标志位主循环里统一处理。如果非要保留ISR打印那至少要把系统栈加大到2KB并且避免在打印里用浮点格式%f。6.2 现场Bmalloc返回NULL堆却“看起来”剩余26KB另一个项目是动态JSON解析每次收到服务器下发的配置就malloc一个小的JSON树解析完再释放。Heap_Size给到16KB运行十几个小时后malloc开始随机失败连64B的小请求都会返回NULL。把堆区的所有空闲块统计出来总剩余空间有10KB以上但最大的连续空闲块只有不到300B。这就是典型的堆碎片化。因为JSON树结构有嵌套节点大小各不相同释放顺序也不固定堆被逐步切成了碎片。这种场景下再加大堆大小只能延缓问题不能解决问题。后来把解析器改成了固定内存池方案预先分配100个固定大小的JSON节点节点数组和空闲链表都在编译期定义好运行时不再动态分配。从那以后堆碎片问题彻底消失。6.3 现场CFreeRTOS任务栈溢出从系统栈抢内存导致死机一个带FreeRTOS和LVGL的项目GUI任务里定义了很大的局部数组任务栈只给了512B。运行到某个界面切换时系统随机死机。一开始怀疑是系统栈不够把Stack_Size加到8KB问题仍然存在。后来把所有任务栈都打开高水位检测发现是GUI任务的栈溢出把相邻的一个任务控制块覆盖了整个调度链表被破坏。排查这类问题的正确思路是用uxTaskGetStackHighWaterMark()函数检查每个任务的历史最低剩余空间。如果哪个任务的高水位已经接近0就说明那个任务栈不够而不是去改系统栈。这里要特别注意FreeRTOS的任务栈溢出有两种检测机制一种是运行时检查栈尾部的填充值是否被破坏另一种是编译期跟踪。要在FreeRTOSConfig.h里把configCHECK_FOR_STACK_OVERFLOW设为2才能在任务切换时及时发现问题。6.4 复盘稳定项目的堆栈配置模板参考堆栈配置没有固定答案但我可以分享两个经受过实际项目考验的起步模板。第一类STM32F103C8T6裸机串口modbus小规模状态机不使用malloc。Stack_Size0x10004KBHeap_Size0x200512B全局数据控制在8KB以内20KB SRAM剩余约8KB做余量第二类STM32F407VET6FreeRTOSLwIPFatFSLVGL192KB RAM。系统栈Stack_Size0x20008KB供启动和中断使用Heap_Size0x10004KB只给少量启动期mallocFreeRTOS内核堆configTOTAL_HEAP_SIZE80KB用heap_4方案GUI任务栈8KB网络任务栈6KB文件系统任务栈4KB这些不是精确值只是起点。每个任务栈都要在跑完压力场景后用高水位检测再校准把余量控制在20%以上。7. 实操清单下次改堆栈前先按这个顺序检查7.1 第一步先看.map文件确认RAM不是已经爆了无论是改Stack还是Heap先打开编译生成的.map文件看RAM区域占用。重点看三块RW数据量、ZI数据量、堆栈区域占用。如果三者加起来已经很接近芯片SRAM上限这时候不是改堆栈的问题而是程序整体占内存太高需要做内存裁剪。7.2 第二步确认你改的是当前工具链真正认的入口Keil改启动文件GCC改.ldIAR改.icf不要混着来。你可以在代码里临时定义一个数组把printf(%p, array)打出来验证改动后的地址是否落在预期RAM区域。如果你改了栈大小程序运行时的SP值却没有变化那十有八九改错了地方。7.3 第三步用栈填充/水位计先测量再决定要不要加不要凭感觉决定改多大。先把当前栈区域用0xAB填充跑一个完整业务周期然后看水位。如果实际最大使用量只有栈大小的30%就没必要动。只有当最大使用量接近80%才考虑加栈。同时观察堆如果有人malloc失败先分析碎片再决定是否增加Heap_Size。7.4 第四步改完之后做“饥饿测试”不是跑通一次就算完改完堆栈之后必须做高负载压力测试。把所有模块最大负载同时拉起来串口满载、Wi-Fi重传、Flash持续写入、GUI全屏刷新。跑12小时以上期间观察HardFault或者内存异常。只跑通一次正常流程就打包发布等于给事故埋雷。还可以在代码里加一个周期性的栈哨兵检查定期读取SP如果SP低过某个阈值就记录触发点和调用栈。这样即使设备在客户现场出问题也能从Flash里拿到现场数据。7.5 第五步保留HardFault现场记录作为最后一道防线不管堆栈调得多合理都要在HardFault_Handler里保存当前PC、LR、SP到一段独立RAM或者备份寄存器。因为很多栈溢出问题难以在本地复现一旦客户现场死机能拿到异常现场才能定位。这个习惯能让你少熬很多个通宵。我后来在项目里做堆栈配置流程基本都是先看map再实测水位再改参数最后压测。那条最开始的“瞎改启动文件”的老路再也没有走过。希望这篇关于STM32堆栈设置的实战笔记也能帮你避开我以前踩过的那些坑。
返回列表