如果你只是独立开发,遇到这种问题,一般都是使用阅读代码,修改代码等常规方法,因为你写的代码是最熟悉的,改动一般不会太大大的。易于定位。但是今天的产品变得越来越复杂。目前的开发模式是合作开发,每个人负责自己的模块。此类项目代码量大,复杂度高,定位问题也比较困难。而且有的时候,刚进公司,什么代码都不熟悉,HardFault又出现,更郁闷,分分钟有跑的冲动(你和代码,只要一个能跑).这个时候有个大牛能解决这样的疑难杂症,可以大大节省时间,而且我在公司也解决过很多类似的问题,所以经验丰富,起到了这种作用。Osprey定位Hardfault的方法一般是通过KEIL在线调试+C语言+权威指南中的知识来完成。目前Osprey对BUG的解决方案大致是这样的:1.一定会出现,熟悉代码的话,几个小时内就可以修复。2.偶尔,解决问题的时间视情况而定。一般出现四五次,基本可以定位。3.实现难度大。这一般需要记录仪实时记录操作。经历了这么多,Osprey能解决几天的Hardfault问题已经很少了(还记得刚来深圳的时候因为别人写的一个BUG不得不通宵几天,如果不是偶然得到的可能是不可能的)。这里打个小广告,如果很难解决,可以找Osprey付费解决Hardfault问题。但是最近因为工作中使用C++,对这个基础不是很熟悉,解决Hardfault的速度又下降了。项目编译优化级别-O2也增加了调试难度,所以掌握以下方法很重要:这里需要一个大佬写的组件:CmBacktrace(其实能在线调试的话,Osprey就没有需要用到这个组件,但是复现困难的时候用这个组件还是蛮香的)。gitee仓库:https://gitee.com/Armink/CmBacktrace这个组件估计很多道友都听说过用过,但是Osprey想说的是道友用的一些组件可能比较老,做没有以下建议您更新跟踪功能。上面可以看到,当出现错误时,函数的调用栈(有时可能出错,需要实际分析,仅供参考)_call_main->main->fult_test_by_div0还是比较实用的。同时,这个笔记不仅适用于定位FreeRTOS中的Hardfault,实际上uCOS、rt-thread等RTOS修改后仍然可以使用(更不用说裸机了)。仓库示例支持的平台:baremetal、rt-thread、ucoss-ii、freertos。这里的重点是如何将这个组件迁移到freertos(其实仓库的文档也很详细,大家可以参考一下)。由于freertos也在不断更新,这个组件的例子不能完全应用到新版本,而Osprey也是刚移植过来,所以记录在这里,方便大家。1、将仓库中的cm_backtrace(源代码文件)整个文件夹复制到自己的项目文件夹中。2.将这些文件添加到自己的工程中(我们可以打开demos->os->freertos工程查看)。只有两个文件,它相当简单。一个是核心源码,一个是汇编代码,代码执行入口。注意,根据IDE的不同,选择的汇编文件也不同:实际上,startup_stm32f10x_hd.s中的hardfault默认处理函数被重定位到cmb_fault.S中。注意这里有个weak,这样在链接的时候,不会链接this,而是cmb_fault.S:为了更方便的定位问题,后面需要修改这段代码。注意,如果你的启动文件中的hardfault代码被修改了,而且你不懂汇编,建议恢复成上面的,否则可能无法正常运行。3.main函数中的初始化代码。这里的字符串需要和这个一样(根据你自己的项目名称修改):所以建议用英文建项目。这个在输出错误信息的时候用到,不然每次查看调用栈都需要修改,很麻烦。如果内部看门狗开启,建议关闭://HAL库__HAL_DBGMCU_FREEZE_IWDG1();//标准库DBGMCU_Config(DBGMCU_IWDG_STOP,ENABLE);在断言失败的位置添加函数cm_backtrace_assert:这样即使断言失败也能看到调用栈起来了。4.FreeROTS内核文件修改(内核版本V10.2.1)。为了分析错误代码,我们必须知道每个任务的堆栈信息,而FreeRTOS可能没有这些信息,所以我们需要添加它。task.cFreeRTOS.h注意老版本的freertos只需要修改一处,新版本需要修改两处,否则会断言失败,无法继续运行。建议也添加评论。UBaseType_tuxSizeOfStack;/**/相关函数修改task.cprvInitialiseNewTask():在task.c文件末尾添加如下代码,获取栈地址、大小、名称:为了方便复制,粘贴代码这里/*-----------------------------------*//**/uint32_t*vTaskStackAddr(){returnpxCurrentTCB->pxStack;}uint32_tvTaskStackSize(){#if(portSTACK_GROWTH>0)return(pxNewTCB->pxEndOfStack-pxNewTCB->pxStack+1);#else/*(portSTACK_GROWTH>0)*/returnpxCurrentTCB->uxSizeOfStack;#endif/*(portSTACK_GROWTH>0)*/}char*vTaskName(){returnpxCurrentTCB->pcTaskName;}/*------------------------------------------------------------*/5。根据RTOS平台和芯片内核修改组件配置信息cmb_cfg.h1)需要定义打印输出函数,一般使用printf进行打印,也可以使用自己自定义的一些打印函数,功能与printf类似。#definecmb_println(...)printf(__VA_ARGS__);printf("\r\n")2)启用RTOS支持#defineCMB_USING_OS_PLATFORM3)具体RTOS选择FreeRTOS#defineCMB_OS_PLATFORM_TYPECMB_OS_PLATFORM_FREERTOS4)芯片内核基于实际选择,目前支持M0、M3、M4、M7。#defineCMB_CPU_PLATFORM_TYPECMB_CPU_ARM_CORTEX_M35)打印虚拟栈,出错时可以打印出原始栈信息,可能有助于分析#defineCMB_USING_DUMP_STACK_INFO6)语言支持:英文。其实是支持中文的,但是建议使用英文(没有配置,默认是英文)#defineCMB_PRINT_LANGUAGECMB_PRINT_LANGUAGE_ENGLISH7)如果是C++编译的,可能会出错,可以在开头这样定义:#define__CLANG_ARM7,根据需要修改组件,方便使用(看看有没有机会把这些合并到大哥的分支)1)因为函数范围小,可以把相关头文件的包含形式改成这样,这样就不需要改变头文件的路径,移植也更方便:#include-->>#include"./cm_backtrace.h"#include-->>#include"./cmb_cfg.h"#include"cmb_def.h"-->>#include"./cmb_def.h"main里面不用include头文件,直接声明这个函数在需要的位置,因为外部只需要调用这个函数#includevoidcm_backtrace_init(constchar*firmware_name,constchar*hardware_ver,constchar*software_ver);这样就不用加上头文件的路径了。或者使用相对路径添加头文件:#include"../../driver/cm_backtrace/cm_backtrace.h"另外我们可以让程序在进入Hardfault之前停止,这样我们可以更好的利用在线Debug代码。HardFault_HandlerPROCLDRr0,=0xE000EDF0;DEMCRLDRr0,[r0,#0x00]ANDr0,r0,#0x00000001CBZr0,not_in_debugBKPT0not_in_debugMOVr0,lr;获取lrMOVr1,sp;getstackisMcurrpointer()BLcm_backtrace_fault因为刚进入Hardfault时信息是最全的,不想每次都中断,所以上面的代码很好的实现了功能,不会影响正常运行程序的(它会自动判断它是否处于调试模式)。8.实验。以上都做完之后,就可以验证效果了。这里我们可以模拟一下,看看情况。(修改项目配置,Osprey之前分享过这些内容,不多说了)运行仓库示例后,应该可以打印如下信息,告诉我们有div0错误,你移植的项目也应该打印类似信息(添加测试代码:fault_test_by_div0();)如果打印失败,有两种可能:1.打印函数未初始化,进入Hardfault2.打印函数有问题。然后我们复制最后一行,然后运行仓库工具中的add2line工具,查看调用栈信息:在gitbash中可能执行失败,可以添加程序路径,当然也可以添加工具路径到Windows环境变量。可能会提示找不到axf文件,将这个文件复制到工具中即可。正确的做法是把工具放到C盘目录下,同时添加环境变量,然后就可以在axf目录下打开gitbash或者cmd窗口执行命令了。这里只是为了演示,就不多介绍这些了。最后简单介绍下这个组件的实现原理:如果发生hardfault,首先进入汇编文件的HardFault_Handler,这里会获取当前栈指针和LR,根据LR判断是哪个栈出错栈《STM32 两个栈,你用哪一个?》(这里是PSP栈)根据错误寄存器信息,判断是哪种错误(这里是除0错误)。然后分析FLASH上的汇编代码,获取堆栈信息、LR、PC,找出可能的跳转指令。这里查到的两个跳转地址是0x08001f960x08000368,然后就得到了调用栈。因此,要想准确获取调用栈,有两个重要的前提条件(推荐优化级别-O0):1.栈没有损坏。2、芯片运行代码与axf文件一致。即便如此,你也不能保证找到的调用栈是正确的。比如使用fault_test_by_unalign()来测试,结果如下:中间多了一个fputc。因此,这些印刷信息仅供参考。但是在线调试就不一样了,更专业,不容易出错。