本章重点介绍FreeRTOS新手用户遇到的最常见问题。首先,它侧重于三个问题,这些问题已被证明是多年来最常见的支持请求来源;中断优先级分配不正确、堆栈溢出和printf()使用不当。然后,它以常见问题解答的风格简要介绍了其他常见错误、可能的原因及其解决方案。
使用configASSERT()可以立即捕获和识别许多最常见的错误源,从而提高生产率。强烈建议在开发或调试FreeRTOS应用程序时定义configASSERT()。
中断优先级
注意:这是支持请求的首要原因,在大多数端口中,定义configASSERT()会立即捕获错误!
如果正在使用的FreeRTOS端口支持中断嵌套,并且中断的服务例程使用FreeRTOS API,则必须将中断的优先级设置为configMAX_SYSCALL_interrupt_priority或更低。如果不这样做,将导致临界区无效,进而导致间歇性故障。
如果在以下处理器上运行FreeRTOS,请特别小心:
中断优先级默认为具有尽可能高的优先级,这在某些ARM Cortex处理器上就是这种情况,也可能在其他处理器上也是如此。在这样的处理器上,不能不初始化使用FreeRTOS API的中断的优先级。
数字上的高优先级数字表示逻辑上的低中断优先级,这似乎违反直觉,因此会引起混淆。ARM Cortex处理器也是如此,可能还有其他处理器也是如此。
例如,在这样的处理器上,以优先级5执行的中断本身可以被优先级为4的中断中断中断。因此,如果将configMAX_SYSCALL_INTERRUPT_PRIORITY设置为5,则任何使用FreeRTOS API的中断只能被分配数值高于或等于5的优先级。在这种情况下,5或6的中断优先级是有效的,但3的中断优先级肯定是无效的。
不同的库实现期望以不同的方式指定中断的优先级。同样,这与针对ARM Cortex处理器的库特别相关,在这些库中,中断优先级在写入硬件寄存器之前会被移位。一些库会自己执行位移,而另一些库则希望在优先级传递给库函数之前执行位移。
同一架构的不同实现实现实现了不同数量的中断优先级位。例如,来自一个制造商的Cortex-M处理器可以实现3个优先级位,而来自另一制造商的Cortex-M处理器可以实现4个优先级位。
定义中断优先级的位可以分为定义优先权的位和定义子优先级的位。确保所有位都分配给指定优先权,这样子优先级就不会被使用。
在某些FreeRTOS端口中,configMAX_SYSCALL_INTERRUPT_PRIORITY的替代名称为configMAX_API_CALL_INTERRUPT.PRIORITY
堆栈溢出
堆栈溢出是第二常见的支持请求来源。FreeRTOS提供了几个功能来帮助捕获和调试与堆栈相关的问题。
uxTaskGetStackHighWaterMark() API 函数
每个任务都有自己的堆栈,堆栈的总大小在创建任务时指定。
uxTaskGetStackHighWaterMark()用于查询任务已接近溢出分配给它的堆栈空间的程度。此值称为堆栈“高水位线”。

xTask
正在查询其堆栈高水位标记的任务的句柄(主题任务)-
任务可以通过在适当的位置传递NULL来查询自己的堆栈高水位标记
有效的任务句柄。
返回值
随着任务的执行和中断的处理,任务使用的堆栈量会增加和减少。
uxTaskGetStackHighWaterMark()返回自任务开始执行以来可用的最小剩余堆栈空间量。这是当堆栈使用率达到最大(或最深)值时,未使用的堆栈量。高水位线越接近零,任务就越接近满溢。
运行时堆栈检查
FreeRTOS包括两个可选的运行时堆栈检查机制。这些由FreeRTOSConfig.h中的configCHECK_FOR_STACK_OVERFLOW编译时配置常量控制。这两种方法都会增加执行上下文切换所需的时间。
堆栈溢出hook(或堆栈溢出回调)是内核在检测到堆栈溢出时调用的函数。要使用堆栈溢出hook函数,请执行以下操作:
1.在FreeRTOSConfig.h中将configCHECK_FOR_STACK_OVERFLOW设置为1或2。
2.使用清单174中所示的确切函数名和原型,提供hook函数的实现。

提供堆栈溢出hook是为了使捕获和调试堆栈错误更容易,但当堆栈溢出发生时,没有真正的方法可以从中恢复。函数的参数将溢出堆栈的任务的句柄和名称传递给钩子函数。
堆栈溢出hook从中断的上下文中调用。
一些微控制器在检测到错误的内存访问时会产生故障异常,并且有可能在内核有机会调用堆栈溢出挂钩函数之前触发故障。
运行时堆栈检查——方法1
当configCHECK_FOR_STACK_OVERFLOW设置为1时,选择方法1。
每次任务被换出时,任务的整个执行上下文都会保存到其堆栈中。这可能是堆栈使用达到峰值的时候。当configCHECK_FOR_STACK_OVERFLOW设置为1时,内核会检查保存上下文后堆栈指针是否仍在有效堆栈空间内。如果发现堆栈指针超出其有效范围,则调用堆栈溢出hook。
方法1执行起来很快,但可能会错过上下文切换之间发生的堆栈溢出
运行时堆栈检查——方法2
方法2执行对方法1已经描述的那些检查的附加检查。当configCHECK_FOR_STACK_OVERFLOW设置为2时,此选项被选中。
创建任务时,其堆栈将填充已知模式。方法2测试任务堆栈空间的最后一个有效20字节,以验证此模式是否未被覆盖。如果20个字节中的任何一个与预期值不同,则调用堆栈溢出挂钩函数。
方法2的执行速度不如方法1快,但仍然相对较快,因为只测试了20个字节。最有可能的是,它将捕获所有堆栈溢出;然而,有可能(但极不可能)错过一些溢出。
printf()和sprintf()的使用不当
不恰当地使用printf()是一个常见的错误来源,应用程序开发人员在不知道这一点的情况下,通常会添加对printf(()的进一步调用来帮助调试,这样做会加剧问题。
许多交叉编译器供应商将提供适用于小型嵌入式系统的printf()实现。即使在这种情况下,实现也可能不是线程安全的,可能不适合在中断服务例程中使用,并且根据输出的方向,执行需要相对较长的时间。
如果专门为小型嵌入式系统设计的printf()实现不可用,而使用通用的printf()实现,则必须特别小心,因为:
仅仅包含对printf()或sprintf()的调用就可以大大增加应用程序可执行文件的大小。
printf()和sprintf()可能会调用malloc(),如果使用了heap_3以外的内存分配方案,则malloc可能无效。
Printf-stdarg.c
许多FreeRTOS演示项目使用一个名为printf stdarg.c的文件,该文件提供了一个最小且堆栈高效的sprintf()实现,可以用来代替标准库版本。在大多数情况下,这将允许为调用sprintf()和相关函数的每个任务分配一个更小的堆栈。
printfstdarg.c还提供了一种机制,可以将printf()输出逐个字符地定向到端口字符,虽然速度较慢,但可以进一步减少堆栈使用。
请注意,并非FreeRTOS下载中包含的printf stdarg.c的所有副本都实现了snprintf()。不实现snprintf()的副本只需忽略缓冲区大小参数,因为它们直接映射到sprintf()。
Printf stdarg.c是开源的,但归第三方所有,因此与FreeRTOS分开许可。
其他常见错误来源
1 向演示中添加简单任务会导致演示崩溃
创建任务需要从堆中获取内存。许多演示应用程序项目将堆的大小精确到足以创建演示任务——因此,在创建任务后,剩余的堆将不足以添加任何其他任务、队列、事件组或信号量。
当调用vTaskStartScheduler()时,会自动创建空闲任务,也可能是RTOS守护进程任务。vTaskStartScheduler()仅在堆内存不足以创建这些任务时才会返回。在调用vTaskStartScheduler()后包含一个空循环[for(;);]可以使此错误更容易调试。
为了能够添加更多任务,可以增加堆大小,或者删除一些现有的演示任务。
2 在中断中使用API函数会导致应用程序崩溃
不要在中断服务例程中使用API函数,除非API函数的名称以‘…FromISR()’结尾。特别是,除非使用中断安全宏,否则不要在中断中创建临界区。
在支持中断嵌套的FreeRTOS端口中,如果中断优先级高于configMAX_SYSCALL_interrupt_priority,请不要在该中断中使用任何API函数。
3 有时应用程序在中断服务例程中崩溃
首先要检查的是中断没有导致堆栈溢出。一些端口只检查任务中的堆栈溢出,而不检查中断中的堆栈溢流。
中断的定义和使用方式因端口和编译器而异。 因此,第二件要检查的事情是,中断服务例程中使用的语法、宏和调用约定与为所用端口提供的文档页面上所述的完全一致,也与端口提供的演示应用程序中所示的完全一致。
如果应用程序在使用数字低优先级数字表示逻辑高优先级的处理器上运行,那么请确保分配给每个中断的优先级都考虑到了这一点,因为这似乎违反直觉。如果应用程序在将每个中断的优先级默认为最大可能优先级的处理器上运行,那么请确保每个中断的优先权不会保持在默认值。
4 计划程序在尝试启动第一个任务时崩溃
确保已安装FreeRTOS中断处理程序。
在启动调度程序之前,某些处理器必须处于特权模式。实现这一点的最简单方法是在调用main()之前,在C启动代码中将处理器置于特权模式
5 中断意外地被禁用,或临界区未正确嵌套
如果在调度程序启动之前调用了FreeRTOS API函数,那么中断将被故意禁用,并且在第一个任务开始执行之前不会重新启用。这样做是为了保护系统免受在系统初始化期间、在调度程序启动之前以及在调度程序可能处于不一致状态时尝试使用FreeRTOS API函数的中断所引起的崩溃。
请勿使用除调用taskENTER_CRITICAL()和taskEXIT_CRITINAL()之外的任何方法更改微控制器中断启用位或优先级标志。这些宏记录了它们的调用嵌套深度,以确保只有当调用嵌套完全展开为零时,中断才会再次启用。请注意,一些库函数本身可能会启用和禁用中断。
6 应用程序甚至在调度程序启动之前就崩溃了
在调度程序启动之前,不得允许执行可能导致上下文切换的中断服务例程。这同样适用于任何试图向FreeRTOS对象(如队列或信号量)发送或从其接收的中断服务例程。在调度程序启动之前,无法进行上下文切换。
许多API函数在调度程序启动后才能调用。在调用vTaskStartScheduler()之前,最好将API的使用限制为创建任务、队列和信号量等对象,而不是使用这些对象。
在调度程序挂起时调用API函数,或从临界区内部调用,会导致应用程序崩溃
通过调用vTaskSuspendAll()暂停调度程序,并通过调用xTaskResumeAll()恢复(未暂停)。通过调用taskENTER_critical()来进入临界区,并通过调用taskEXIT_CRITINAL()来退出。
当调度程序挂起时,不要从临界区内部调用API函数。
956

被折叠的 条评论
为什么被折叠?



