文章目录
- 一.概要
- 二.什么是Hard fault
- 三.Hard fault 产生的原因分析
- 四.制作一个Hard fault程序并定位出问题原因
- 1.查看堆栈指针SP的地址以及内容
- 2.找到Return address地址
- 3.查看汇编界面
- 4.输入Return address地址,查找到问题代码
- 小结
一.概要
在嵌入式开发中,偶尔会遇到单片机HardFault_Handler死机的异常,单片机无法正常运行,出现Hardfault错误时,问题比较难定位的原因在于此时代码无法像正常运行时一样,所以显得无从下手。通常情况下我们都是通过在某个区间打断点,然后通过单步执行去逐步缩小“包围圈”去找到产生Hard Fault的代码位置,接着再去推敲、猜测问题的原因。对于不是很复杂的程序,这种方法是有效的,但是当用户代码量进一步增大,再用这种单步+断点去逐步缩小包围圈的方式就很难查到问题点,效率也很低。尤其是在有操作系统的应用中,很多代码的跳转是由操作系统调度的,不是严格的顺序执行,所以很难依靠缩小包围圈的方式去有效找到问题产生的点,进一步增加了定位到Hard Fault触发原因的难度。
本文就介绍了出现HardFault_Handler出现的原因,以及发现异常之后,问题的排查分析与解决方法。
二.什么是Hard fault
对于Cortex-M内核,架构采用错误异常的机制来检测问题,当内核检测到一个错误时,异常中断会被触发,并且内核会跳转到相应的异常中断处理函数执行。
错误异常的中断分为以下四种:
HardFault
MemManage
BusFault
UsageFault
其中hardfault为最常见的错误类型,并且在没有开启其他异常处理的情况下,默认进入hardfault异常中断处理函数。
void HardFault_Handler(void)
{
/* USER CODE BEGIN HardFault_IRQn 0 /
/ USER CODE END HardFault_IRQn 0 /
while (1)
{
/ USER CODE BEGIN W1_HardFault_IRQn 0 /
/ USER CODE END W1_HardFault_IRQn 0 */
}
}
三.Hard fault 产生的原因分析
硬件方面常见原因:
1.电源设计有错误,造成器件供电不稳;
2.电源质量不好,文波,噪声过大;
3.器件接地不良;
4.对于带有Vcap引脚的器件,管脚处理不当;
5.电路中有强干扰源,对器件造成干扰;
软件方面常见原因:
1.使用了空指针;
2.对地址偏移量的计算有误;
3.数组越界导致程序出错;
4.动态内存使用不当,导致访问了已释放的内存地址;
5.通过地址访问了已失效的局部变量;
一般因为硬件造成Hard Fault错误的可能性较低,大多数都是软件原因造成的。所以遇到硬件中断错误,基本就是通过软件来排查。
由于异常发生时,内核将R0~R3、R12、Returnaddress、PSR寄存器依次入栈,其中Return address即为发生异常前PC将要执行的下一条指令地址,所以我们可以通过找到Return address,从而排查出产生异常的指令。
四.制作一个Hard fault程序并定位出问题原因
硬件准备:
STLINK接STM32F103C8T6小系统板,STLINK接电脑USB口。
程序准备:
在普通的GPIO例程中修改代码
uint32_t Data2=100,Data3;
uint8_t Temp[100],*p;
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
Data3=Data2*100;
memset(Temp,0x55,100);
Data=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_3);
Data3=100*(Data2-1);
if(Data3>10)
{
Data=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_3);
}
memset(p,0x55,1000);
memset(Temp,0x55,100);
Data=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_3);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
下载完程序调试运行效果:
程序进入HardFault_Handler
查找问题步骤:
1.查看堆栈指针SP的地址以及内容
堆栈指针SP地址为0x20000468
查看Memory,地址为0x20000468
2.找到Return address地址
内核是将R0~R3、R12、Returnaddress、PSR等寄存器依次入栈,所以找到Return address是0x08000FE7
3.查看汇编界面
4.输入Return address地址,查找到问题代码
输入地址0x08000FE7
找到Return address的指令,以及上一条指令,发现上一条指令指针是空指针,问题找到
再举一个例子
uint32_t Data2=100,Data3;
uint8_t Databuff[10000];
uint8_t Temp[100];
void Datacpy(void)
{
memcpy(Temp,Databuff,10000);
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
memset(Temp,0x55,100);
Data=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_3);
Data3=100*(Data2-1);
if(Data3>10)
{
Data=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_3);
}
Datacpy();
Data=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_3);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
根据上述方法,查出有问题的函数是void Datacpy(void),Return Address是0x080001A3,发现是操作数组的时候越界了。
小结
使用调试工具和技术来定位HardFault的根源在单片机开发和调试中是必不可少的,可以帮助我们快速地排查问题所在,提供产品开发效率。