FreeRTOS在PIC32MX系列微处理器上的调试心得

2019-04-15 13:21发布

1. 引言

最近在调试程序时发现程序会在运行一段时间后崩溃。在仔细阅读FreeRTOS和PIC32MX的相关资料后,修改了FreeRTOS配置,调整了优先级设置,优化了关键区域的代码,显著降低了崩溃的概率。在这里分享一些心得。 想学习FreeRTOS的基础知识请阅读专栏:
1. FreeRTOS基础篇
2. FreeRTOS高级篇

2. 配置FreeRTOSConfig.h

这里主要提几个与PIC32MX微处理器的特性有关的配置项,其他通用的配置请参考 FreeRTOS系列第6篇—FreeRTOS内核配置说明 /* Interrupt nesting behaviour configuration. */ /* The priority at which the tick interrupt runs. This should probably be kept at 1. */ #define configKERNEL_INTERRUPT_PRIORITY 1 /* The maximum interrupt priority from which FreeRTOS.org API functions can be called. Only API functions that end in ...FromISR() can be used within interrupts. */ #define configMAX_SYSCALL_INTERRUPT_PRIORITY 6 PIC32MX的中断优先级为1-7,其中1最低,7最高。低优先级的中断触发的中断服务程序可以被高优先级的中断打断,从而执行更紧急的中断服务程序。 系统定时器中断使用最低优先级1,其可以被中断优先级为2-7的中断嵌套。 优先级为2-6的中断触发的中断服务程序里,可以使用FreeRTOS提供的后缀为FromISR()的API函数。 优先级为7的中断触发的中断服务程序不受FreeRTOS的影响,且不可以使用后缀为FromISR()的API函数。 设置configMAX_SYSCALL_INTERRUPT_PRIORITY的意义在于,使一些中断服务函数可以使用自己的栈空间。不高于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断服务程序都使用相同的中断栈空间。 /* Records the interrupt nesting depth. This is initialised to one as it is decremented to 0 when the first task starts. */ volatile UBaseType_t uxInterruptNesting = 0x01; /* Stores the task stack pointer when a switch is made to use the system stack. */ UBaseType_t uxSavedTaskStackPointer = 0; /* The stack used by interrupt service routines that cause a context switch. */ StackType_t xISRStack[ configISR_STACK_SIZE ] = { 0 }; /* The top of stack value ensures there is enough space to store 6 registers on the callers stack, as some functions seem to want to do this. */ const StackType_t * const xISRStackTop = &( xISRStack[ configISR_STACK_SIZE - 7 ] ); uxInterruptNesting用来标示中断嵌套层数。每进入一次中断,uxInterruptNesting值自加;每退出一次中断,uxInterruptNesting自减。嵌套次数越多,这个数越大。程序在保存现场时使用这个值来判断是将CPU寄存器组压入任务栈还是中断栈。 uxSavedTaskStackPointer用来在进入中断服务程序前保存任务栈,在退出中断服务程序时将sp指针指向任务栈。 xISRStack是中断服务程序用到的栈。进入中断服务程序时,sp指针被指向这个中断栈的顶部xISRStackTop;退出中断服务程序时,sp指针恢复到任务的栈指针uxSavedTaskStackPointer。 这里写图片描述 明确了这几个变量的用途后,程序编写时需要注意如下几点:
  • uxInterruptNesting最大不应该超过硬件中断源的总数,否则可以判定程序运行异常;
  • xISRStack的大小由configISR_STACK_SIZE设置,单位是字节。所有中断服务程序中创建的局部变量都会使用这个栈。需要确保中断嵌套层数达到最大时xISRStack也够用,否则会产生栈的溢出,后果很可能是程序崩溃;
  • 不要在中断服务程序中随意移动sp指针,否则中断嵌套发生时,被中断的服务程序的栈有可能被破坏。

3. 优化任务优先级

在创建FreeRTOS任务时需要为任务指定一个优先级。优先级高的任务更容易获得CPU的使用权。 产品代码创建了17个任务,其中有一个任务专门用于访问串口硬件以发送串口log,优先级设成最高。将这个任务的优先级设成最低后,系统崩溃的概率降低了。

4. 优化中断处理

中断服务程序会打断任务代码的执行,且进入和退出中断时需要频繁调用入栈和出栈函数,加重CPU的运行负担。 中断处理的优化方法一般有如下几种:
  • 降低中断嵌套次数
  • 减少中断产生频率
  • 缩短中断处理时间

4.1. 降低中断嵌套次数

产品程序崩溃一般发生在硬件中断频繁产生的时候。为了降低中断嵌套次数,根据中断触发的频率,调整了硬件的中断优先级:
  • 2个I2C中断(优先级4)
  • 2个SPI中断(优先级6)
  • 3个UART中断(优先级3)
  • 2个定时器中断(优先级2)
  • 1个输入电平捕捉中断(优先级5)
I2C中断和UART中断触发的频率最高,然后是定时器中断和SPI中断,最后是输入电平捕捉中断。调整的原则是使触发频率高的中断在执行中断服务程序时可以被触发频率低的中断打断,降低中断嵌套的可能性。

4.2. 减少中断产生频率

减少中断产生频率的方法可以有:
  • 尽量用FreeRTOS提供的时间函数
  • 硬件驱动方式从中断式改成轮询式
在产品代码中,定时器2周期性给一个全局变量加1作为时钟节拍。但FreeRTOS已经能够产生系统时钟节拍,因此定时器2可以去掉。 PIC32MX的硬件驱动方式支持轮询式。

4.3. 缩短中断处理时间

在中断服务程序中,只接收和发送数据,而不作数据的解析。接收到的数据通过队列发送给中断延时服务任务: uint8_t recvBuf[100]; void func_ISR(void) { receiveData(recvBuf); queueSend(&recvBuf); clearISRFlag(); } void func_task(void) { while(1) { if (TRUE == queueRecv(&recvBuf)) { parse(recvBuf); } } }

5. 分析系统崩溃原因

PIC32MX的CPU提供了一种程序异常处理的机制。当程序运行异常时,程序会跳转到_general_exception_handler函数: void _general_exception_handler ( void ) { /* Mask off Mask of the ExcCode Field from the Cause Register Refer to the MIPs Software User's manual */ _excep_code = (_CP0_GET_CAUSE() & 0x0000007C) >> 2; _excep_addr = _CP0_GET_EPC(); _cause_str = cause[_excep_code]; } _excep_code 给出产生exception的原因:
这里写图片描述 _excep_addr给出直接导致exception的地址。

6. 优化底层移植汇编代码

调试时程序经常崩溃在如下汇编代码:
这里写图片描述 ehb指令的作用主要是解决流水线执行异常,但不能解决流水线指令异常。 这里写图片描述 浏览官方技术支持论坛后,发现有人遇到类似的问题:
http://www.microchip.com/forums/m797764.aspx
他的解决办法是在ehb之前加上ssnop指令。 我试着在vPortYieldISR和portRESTORE_CONTEXT里的ehb之前添加了ssnop指令,与ehb相关的程序崩溃解决了。

7. 解决堆栈溢出

7.1. 任务堆栈的溢出

configCHECK_FOR_STACK_OVERFLOW设成1或2,任务堆栈溢出时会执行vApplicationStackOverflowHook()。 可以通过查看xPortGetFreeHeapSize()的返回值获取剩余任务堆栈的大小,其中包含所有。 也可以通过查看uxTaskGetStackHighWaterMark()的返回值获取单个任务在运行时的堆栈使用情况,返回值越小,可用的堆栈越少。

7.2. 中断堆栈的溢出

configCHECK_FOR_STACK_OVERFLOW设成3,xPortStartScheduler会将中断堆栈内的初值全部设为相同值,便于检查中断堆栈用了多少。

8. 预防进程死锁

程序执行过程中尽量不要使用OSA_Delay(portMAX_DELAY),防止某个进程一直得不到执行。