花了3个晚上,把这个章节看完,受益不浅。html
printf()
相关的问题应该尽可能不会出现,毕竟只要须要打印调试信息的状况下才使用,并且嵌入式系统通常都是用串口重定向的。printf()
真的挺烦的,严重影响性能,个人开发案例中发现,串口打印会影响板子的 power save性能,这是实测到的。此章节涉及新手最常碰见的3种问题:api
printf()
使用configASSERT()
可以显著地提升生产效率,它可以捕获、识别多种类型的错误。强烈建议在开发或者调试中开启宏configASSERT()
。安全
注意:这是头号须要技术支持的问题,在大多数的移植版本中经过定义configASSERT()
就可以马上捕获这个错误。架构
若是FreeRTOS移植版本支持中断嵌套,而且中断服务程序使用了FreeRTOS API,那么必须把中断优先级设置为configMAX_SYSCALL_INTERRUPT_PRIORITY
或者低一点。没有这么设置将会致使临界区失效,反过来就会致使间歇性的错误。函数
当FreeRTOS运行在如下处理器上须要特别注意:性能
configMAX_SYSCALL_INTERRUPT_PRIORITY
设置为5,那么任何其余的使用FreeRTOS API的中断的优先级必须设置为5甚至更高。在这种情形下优先级为5或者6的是有效的,可是优先级为3的中断是无效的。Cortex-M
处理器某一个厂商可能实现了3个优先级比特位,可是另外一个厂商实现了4个优先级比特位。在某些移植版本中configMAX_SYSCALL_INTERRUPT_PRIORITY
有一个别名configMAX_API_CALL_INTERRUPT_PRIORITY
。测试
栈溢出是第二个常常寻求技术支持的问题。FreeRTOS提供了几个特性来辅助捕获和调试和栈相关的问题。线程
uxTaskGetStackHighWaterMark()
每一个任务都在维护本身的栈,栈的总大小在建立任务的时候就指定了。函数uxTaskGetStackHighWaterMark()
就是用来查询分配给这个任务的栈接近栈溢出的程度。返回值称为栈的高水位线。指针
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
调试
任务使用栈的多少,随着任务的运行和中断的处理时而增长时而减小。uxTaskGetStackHighWaterMark()
返回自任务开始运行以来剩余可用的栈空间的最小值。它返回的是栈未使用的空间占最大值的比值。高水位越接近于0,那么这个任务的栈就越快要溢出。
FreeRTOS提供了两种在运行时检查栈的机制。都是由文件FreeRTOSConfig.h
中的configCHECK_FOR_STACK_OVERFLOW
来在编译时进行控制的。两种方法都会增长上下文切换的时间。
栈溢出钩子函数(又称为栈溢出回调函数)是一个由内核检查到栈溢出时调用的函数。要使用栈溢出钩子函数要知足如下条件:
FreeRTOSConfig.h
中把configCHECK_FOR_STACK_OVERFLOW
设置为1或者2。void vApplicationStackOverflowHook( TaskHandle_t *pxTask, signed char *pcTaskName);
栈溢出钩子函数会让捕获和调试栈错误更加的简单,可是发生栈溢出错误时是没有办法恢复的。此函数把发生栈溢出的任务的句柄和名字传递进去。
栈溢出钩子函数会在一个中断的上下文中进行调用。
某些微控制器在检测到一个错误的内存访问时会产生一个错误异常,这个错误异常的触发会使得内核根本就没有机会调用栈溢出钩子函数。
当进行以下设置是会选择方法1.
#define configCHECK_FOR_STACK_OVERFLOW 1
每当一个任务被切换出去时它的整个的执行上下文都会被保存到它本身的栈中。颇有可能这就是栈使用率达到最大值的时候。当使用方法1是,当任务的上下文被保存以后内核回去检查栈指针是否在栈可用空间内。若是发现栈指针已经超出了可用的范围那么就会调用栈溢出钩子函数。
方法1执行速度快,可是在发生上下文切换时有可能会错过栈溢出。
进行以下设置后才会选择方法2.
#define configCHECK_FOR_STACK_OVERFLOW 2
除了方法1中的行为,方法2还会执行其余的检查。
建立任务时它的栈会被一个已知的样本填充。任务2检查栈空间的最后20个字节,验证这个已知的样本是否已经被覆盖。若是这20个字节的值与预期值不同那么就会调用栈溢出钩子函数。
方法2不如方法1快,当时相对来说仍是快,毕竟只是测试20个字节。颇有可能方法2会捕获到全部的栈溢出,可是有可能(几乎不可能)某些栈溢出仍是漏掉了。
printf()
和sprintf()
不恰当地只用printf()
是一种常见的错误源,而且没有意识到这种错误,一般应用开发者会增长更多的printf()
来辅助调试,结果就是加剧了问题。
许多交叉编译器厂商会提供一种适合在小型嵌入式系统中使用的printf()
的实现。即使在这种情形下,printf()
的实现可能并非线程安全的,几乎能够确定不适合在中断服务程序中使用,而且取决于输出被重定向到哪里,会占用至关长的一段执行时间。。
若是小型嵌入式系统的printf()
的实现不可用,而且使用了通用的printf()
的实现,那么就须要特别注意了:
printf()
或者sprintf()
的调用就会急剧的增长应用执行文件的体积;heap_3
之外的存储空间方案,printf()
和sprintf()
调用了malloc()
,这个是无效的。printf()
和sprintf()
可能会申请一个几倍于一般状况的栈空间。Printf-stdarg.c
许多的FreeRTOS示例工程了使用了一个printf-stdarg.c
的文件,它提供了一个极小的、栈使用率很是高效的可以取代标准库函数版本的sprintf()
实现。在大多数情形下,使得任务每次调用sprintf()
或者相关的函数却分配更少的栈空间。
printf-stdarg.c
提供了一种机制把printf()
输出重定向,一个字节一个字节的输出,虽然慢,可是却极大地减小了栈空间的占用。
注意:并非全部的FreeRTOS下载副本中文件printf-stdarg.c
都实现了snprintf()
函数。没有实现snprintf()
的副本直接忽略缓冲区大小参数,它们是直接映射到sprintf()
函数。
printf-stdarg.c
是开源的,可是是第三方拥有的,所以它的受权和FreeRTOS是分开的。它的受权条款在文件的首部。
建立任务须要从堆中分配内存。许多示例工程的栈空间仅仅可以容纳例程任务,所以在建立了例程任务后,没有足够的堆空间留给其余更多的任务,队列,事件组,信号量。
空闲任务,又或许是FreeRTOS的守护进程,在调用vTaskStartScheduler()
时是自动建立的。只有当堆空间不足以建立这些任务时vTaskStartScheduler()
才会返回。在调用vTaskStartScheduler()
以后添加一个for(;;);
会让这个问题更容易排错。
要想添加更多的任务,要么扩大堆空间,要么减小已经存在的例子任务。
在中断服务程序中不要使用API,除非API名字是以FromISR()
结尾。特别地,在中断中不要建立一个临界区,除非使用中断安全的宏。
在支持中断嵌套的FreeRTOS移植版本中,若是中断优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY
就不要在其中使用 API 函数。
首先要检查中断是否产生了栈溢出。有的移植版本中只检查任务的栈溢出,并无检查中断是否栈溢出。
中断的定义和使用方式随着移植版本和编译器的不一样而不一样。所以,其次要检查语法,宏定义,调用规则在中断服务程序中的使用是否与移植文档中的彻底一致,是否与例程中的彻底一致。
若是应用运行在数值越低的优先级表示逻辑上越高的优先级的处理器上,那么须要确保分配给中断的优先级要考虑到这种状况,由于它看起来是违反直觉的。
若是应用运行在把中断优先级默认设置为最大可能的优先级的处理器上,须要确保每一个中断的优先级没有留置为默认值。
确保设置了FreeRTOS的中断句柄。参考FreeRTOS移植文档,还有示例程序。
某些处理器必须在调度器启动以前处于特权模式。最简单的实现方法是在C语言main()
以前的启动代码中就把处理器置于特权模式。
若是在调度器启动以前就调用了FreeRTOS API函数那么中断就会被蓄意地禁止,直到第一个任务启动以后才会从新使能。这样作是为了保护系统不挂掉,缘由在于初始化过程当中中断尝试调用FreeRTOS API函数,然而调度器尚未启动,它可能处于一个不一致的状态。
除了调用taskENTER_CRITICAL()
和taskEXIT_CRITICAL()
函数以外不要使用任何其它的方法来更改微处理器的中断使能位和优先级标志。这两个宏会统计中断嵌套深度确保当中断嵌套深度为0时中断又使能。须要知悉某些库函数可能在内部使能和禁止中断。
有可能发生上下文切换的中断是禁止在调度器启动以前就开始执行的。一样的规则适用于尝试发送或者接收FreeRTOS对象(例如队列和信号量)的任何中断服务程序。上下文切换必须在调度器启动以后才能发生。
不少API函数必须在调度器启动以后才能调用。最好是在调用vTaskStartScheduler()
以后将API的使用限制在建立诸如任务,队列和信号量上,而不是使用这些对象。
调用函数vTaskSuspendAll()
会挂起调度器,调用函数xTaskResumeAll()
会恢复调度器。
调用函数taskENTER_CRITICAL()
会进入临界区,调用函数taskEXIT_CRITICAL()
会退出临界区。
在调度器挂起时或者临界区内永远不要调用API函数。
欢迎转载,请注明出处和做者,同时保留声明。
做者:LinTeX9527
出处:http://www.cnblogs.com/LinTeX9527/p/8031565.html 本博客的文章如无特殊说明,均为原创,转载请注明出处。如未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。