调试
FreeRTOS 提供的调试手段:
- 打印
- 断言: configASSERT
- Trace
- Hook 函数(回调函数)
打印
printf: FreeRTOS 工程里使用了 microlib,里面实现了 printf 函数。
int fputc( int ch, FILE *f )
断言
一般的 C 库里面,断言就是一个函数:
void assert(scalar expression);
它的作用是:确认 expression 必须为真,如果 expression 为假的话就中止程序。
在 FreeRTOS 里,使用 configASSERT(),比如:
##define configASSERT(x) if (!x) while(1);
或
##define configASSERT(x)
if (!x)
{
printf("%s %s %d\r\n", __FILE__, __FUNCTION__,__LINE__);
while(1);
}
configASSERT(x)中,如果 x 为假,表示发生了很严重的错误,必须停止系统的运行。
Trace
系统中定义了很多空的宏,需要调试时可以进行改写。
trace 宏 | 描述 |
---|---|
traceTASK_INCREMENT_TICK(xTickCoun t) | 当 tick 计数自增之前此宏函数被调用。参数 xTickCount 当前的 Tick 值,它还没有增加。 |
traceTASK_SWITCHED_OUT() | vTaskSwitchContext 中,把当前任务切换出去 之前调用此宏函数。 |
traceTASK_SWITCHED_IN() | vTaskSwitchContext 中,新的任务已经被切换 进来了,就调用此函数。 |
traceBLOCKING_ON_QUEUE_RECEIVE(px Queue) | 当正在执行的当前任务因为试图去读取一个空 的队列、信号或者互斥量而进入阻塞状态时, 此函数会被立即调用。参数 pxQueue 保存的是 试图读取的目标队列、信号或者互斥量的句 柄,传递给此宏函数。 |
traceBLOCKING_ON_QUEUE_SEND(pxQue ue) | 当正在执行的当前任务因为试图往一个已经写 满的队列或者信号或者互斥量而进入了阻塞状 态时,此函数会被立即调用。参数 pxQueue 保 存的是试图写入的目标队列、信号或者互斥量 的句柄,传递给此宏函数。 |
traceQUEUE_SEND(pxQueue) | 当一个队列或者信号发送成功时,此宏函数会 在内核函数 xQueueSend(),xQueueSendToFront(),xQueueSe ndToBack(),以及所有的信号 give 函数中被调 用,参数 pxQueue 是要发送的目标队列或信号 的句柄,传递给此宏函数。 |
traceQUEUE_SEND_FAILED(pxQueue) | 当一个队列或者信号发送失败时,此宏函数会 在内核函数 xQueueSend(),xQueueSendToFront(),xQueueSe ndToBack(),以及所有的信号 give 函数中被调 用,参数 pxQueue 是要发送的目标队列或信号 的句柄,传递给此宏函数。 |
traceQUEUE_RECEIVE(pxQueue) | 当读取一个队列或者接收信号成功时,此宏函 数会在内核函数 xQueueReceive()以及所有的 信号 take 函数中被调用,参数 pxQueue 是要 接收的目标队列或信号的句柄,传递给此宏函 数。 |
traceQUEUE_RECEIVE_FAILED(pxQueue) | 当读取一个队列或者接收信号失败时,此宏函 数会在内核函数 xQueueReceive()以及所有的 信号 take 函数中被调用,参数 pxQueue 是要 接收的目标队列或信号的句柄,传递给此宏函 数。 |
traceQUEUE_SEND_FROM_ISR(pxQueue) | 当在中断中发送一个队列成功时,此函数会在 xQueueSendFromISR()中被调用。参数 pxQueue 是要发送的目标队列的句柄。 |
traceQUEUE_SEND_FROM_ISR_FAILED(px Queue) | 当在中断中发送一个队列失败时,此函数会在 xQueueSendFromISR()中被调用。参数 pxQueue 是要发送的目标队列的句柄。 |
traceQUEUE_RECEIVE_FROM_ISR(pxQueue) e) | 当在中断中读取一个队列成功时,此函数会在 xQueueReceiveFromISR()中被调用。参数pxQueue 是要发送的目标队列的句柄。 |
traceQUEUE_RECEIVE_FROM_ISR_FAILED (pxQueue) | 当在中断中读取一个队列失败时,此函数会在 xQueueReceiveFromISR()中被调用。参数 pxQueue 是要发送的目标队列的句柄。 |
Malloc Hook 函数
内存越界经常发生在堆的使用过程总:堆,就是使用 malloc 得到的内存。
并没有很好的方法检测内存越界,但是可以提供一些回调函数:
使用 pvPortMalloc 失败时,如果在FreeRTOSConfig.h 里配置configUSE_MALLOC_FAILED_HOOK 为 1,会调用:
void vApplicationMallocFailedHook( void );
栈溢出 Hook 函数
在切换任务(vTaskSwitchContext)时调用taskCHECK_FOR_STACK_OVERFLOW 来检测栈是否溢出,如果溢出会调用:
void vApplicationStackOverflowHook( TaskHandle_t xTask, char * pcTaskName );
判断栈溢出的两种方法:
- 当前任务被切换出去之前,它的整个运行现场都被保存在栈里,这时很可能就是它对栈的使用到达了峰值。
- 创建任务时,它的栈被填入固定的值,比如: 0xa5
检测栈里最后 16 字节的数据,如果不是 0xa5 的话表示栈即将、或者已经被用完了;没有方法 1 快速,但是也足够快, 能捕获几乎所有的栈溢出
优化
查看栈的使用情况
在创建任务时分配了栈,可以填入固定的数值比如 0xa5, 以后可以使用以下函数查看还有多少空余的栈空间:
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
原理是:从栈底往栈顶逐个字节地判断,它们的值持续是 0xa5 就表示它是空闲的。
返回值:逐 个 函 数 从 栈 的 尾 部 判 断 栈 的 值 连 续 为 0xa5 的 个 数 , 它 就 是 任 务 运 行 过 程 中 空 闲 内 存 容 量 的 最 小 值 。
注意:假设从栈尾开始连续为 0xa5 的栈空间是 N 字节,返回值是 N/4。
任务运行时间统计
因为有更高优先级的任务就绪时,当前任务还没运行一个完整的
Tick 就被抢占了,所以使用Tick来计算任务运行时间很不准确。
可以使用比Tick更快的时钟,
volatile
直接存取原始内存地址
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。
如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
volatile用法:
- 中断服务程序中修改的供其它程序检测的变量,需要加volatile;
当变量在触发某中断程序中修改,而编译器判断主函数里面没有修改该变量,因此可能只执行一次从内存到某寄存器的读操作,而后每次只会从该寄存器中读取变量副本,使得中断程序的操作被短路。
- 多任务环境下各任务间共享的标志,应该加volatile;
在本次线程内, 当读取一个变量时,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当内存变量或寄存器变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致 。
- 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
假设要对一个设备进行初始化,此设备的某一个寄存器为0xff800000。for(i=0;i< 10;i++) output = i;前面循环半天都是废话,对最后的结果毫无影响,因为最终只是将output这个指针赋值为9,省略了对该硬件IO端口反复读的操作。
注意:
- 一个参数既可以是const还可以是volatile;
- 当一个中断服务子程序修改一个指向buffer的指针时volatile可以是指针
- 频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。