嵌入式(Stm32)程序跑飞

  搞嵌入式开发,有时候总会因为自己的粗心,或者是硬件本身的缺陷导致的程序跑飞;要找到程序跑飞的原因,排查方法尤为重要,这里介绍一些经验。

一、常见的 程序跑飞

  分享常见的三种死机情况:

  1. 无操作系统程序死机
    1. 常见的就是内存溢出(数组下标、申请内存的长度)
    2. 语法问题,导致死循环、或者条件异常直接跳转出去(常见低级错误)
  2. 带操作系统程序死机
    1. 临界区代码没有进行保护,导致堆栈溢出;常见于底层驱动中断
    2. 任务的堆栈(内存)设置太小;出现程序死机时,可以尝试扩大任务的堆栈大小,若无死机情况了再从大往小了减。
  3. emwin的不当操作导致死机
    1. emwin由于是不开源的,所以有时候不当的操作,比如没有创建窗口时就删除该窗口,就会导致死机。
    2. 所以在运用emwin的时候一定要严格遵循他要求的顺序,如果可以的话多加一下判断再删除界面。

二、进入HardFault_Handler

  STM32出现HardFault_Handler故障的根本原因有两个方面:

  1. 内存溢出或者访问越界。
  2. 堆栈溢出。(增加堆栈的大小,大多数是因为中断嵌套)

  但是能造成故障的方法有很多种:例如,数组越界操作、中断处理错误、中断嵌套过多等等。

这里只介绍两种简单排查的方法。

2.1 方法1

  默认的HardFault_Handler处理方法不是 B .(汇编) or while(1)(C语言) 这样的死循环么?楼主将它改成 BX LR直接返回的形式。

  然后在这条语句打个断点,一旦在断点中停下来,说明出错了,然后再Step两下,就可以返回到出错的位置的下一条语句那儿。

1
2
3
4
5
6
7
8
9
__asm void wait()
{
BX lr
}
void HardFault_Handler(void)
{
/* Go to infinite loop when Hard Fault exception occurs */
wait();
}

2.2 方法2

  在硬件中断函数HardFault_Handler里的while(1)处打调试断点,程序执行到断点处时点击“STOP”停止仿真

  在Keil菜单栏点击“View”——“Call Stack Window”弹出“Call Stack + Locals”对话框。然后在对话框中右键选择“Show Caller Code”,就会跳转到出错之前的函数处;仔细查看这部分函数被调用或者数组内存使用情况。

三、上操作系统(ucOS)程序跑飞

  频繁操作文件,容易出现死机,操作文件时,最好关闭任务;操作完成后,再打开任务; uC/OS-II的OSSchedLock()和OSSchedUnlock()函数应成对出现,允许应用程序锁定当前任务不被其它任务抢占。使用时应当注意的是:当你调用了OSSchedLock()之后,而在调用OSSchedUnlock()之前,千万不要再调用诸如OSFlagPend()、OSMboxPend()、OSMutexPend()、OSQPend()、OSSemPend()之类的事件等待函数!而且应当确保OSSchedLock()和OSSchedUnlock()函数成对出现,特别是在有些分支条件语句中,要考虑各种分支情况,不要有遗漏!


  任务优先级。每个任务都必须符合事件驱动的编程模型,即uC/OS-II的应用程序都必须是“事件驱动的编程模型”。一个任务首先等待一个事件的发生,事件可以是系统中断发出的,也可以是其它任务发出的,又可以是任务自身等待的时间片。当一个事件发生了,任务再作相应处理,处理结束后又开始等待下一个事件的发生。如此周而复始的任务处理模型就是“事件驱动的编程模型”。事件驱动模型也涵盖了中断驱动模型,uC/OS-II事件归根结底来自三个方面:

  • 中断服务函数发送的事件
  • 系统延时时间到所引起的
  • 其它任务发送的事件。
    • 其中“中断服务函数发送的事件”就是指每当有硬件中断发生,那么中断服务程序就会以事件的形式告诉任务,而等待该事件的最高优先级任务就会马上得以运行;“系统延时时间到所引起的”事件其实也是硬件中断导致的,那就是系统定时器中断。而“其它任务发送的事件”则是由任务代码自身决定的,这是完全的“软事件”。不管“软事件”还是“硬事件”,反正引起uC/OS-II任务切换的原因就是“事件”,所以用户编写应用代码的时候一定要体现出“事件驱动的编程模型”。任务的优先级也一定要分配得当;
  • 查看堆栈是否满;

  不可重入函数。一些C语言函数,用到uCOS-II操作系统中,有时也可能出现问题,例如:gotoxy();printf()。


  OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()也可以用来保护应用程序中的临界代码;然而要特别小心,如果再调用一些如 OSTimeDel() 之类的功能函数之前关中断,应用程序将会死机;原因是任务被挂起一段时间,直到挂起时间到,但由于中断关掉了,时钟节拍中断一直得不到服务,显然所有的挂起类调用都有这样的问题,所以要特别小心。

  需要一并提醒的是:当调用开关中断函数 OS_ENTER_CRITICAL() 和 OS_EXIT_CRITICAL() 时也要确保成对出现,否则系统将可能崩溃!不过,在 OS_ENTER_CRITICAL() 和 OS_EXIT_CRITICAL() 函数之间调用 OSFlagPend()、 OSMboxPend()、 OSMutexPend()、 OSQPend()、 OSSemPend()之类的事件等待函数是允许的。


  官方ucOS-III 不支持stm32f4x系列的FPU功能(ucOS-III代码没考虑到该功能);一旦使能FPU,程序会进入到HardFault_Handler,或者出现打印浮点数异常(打印就死机)的现象。修改进行对源码的一些修改才能使用!

-------------本文结束感谢您的阅读-------------