【FreeRTOS】中断管理

张开发
2026/6/10 20:06:08 15 分钟阅读
【FreeRTOS】中断管理
【FreeRTOS】中断管理前言一、两套 API 函数1.1 为什么需要两套 API1.2 两套 API 函数列表1.3 xHigherPriorityTaskWoken 参数1.4 怎么切换任务二、中断的延迟处理前言在 RTOS 中需要应对各类事件。这些事件很多时候是通过硬件中断产生怎么处理中断呢假设当前系统正在运行Task1时用户按下了按键触发了按键中断。这个中断的处理流程如下CPU 跳到固定地址去执行代码这个固定地址通常被称为中断向量这个跳转时硬件实现的执行代码做什么保存现场Task1 被打断需要先保存 Task1 的运行环境比如各类寄存器的值分辨中断、调用处理函数(这个函数就被称为 ISRinterrupt service routine)恢复现场继续运行 Task1或者运行其他优先级更高的任务你要注意到ISR是在内核中被调用的ISR执行过程中用户的任务无法执行。ISR要尽量快否则其他低优先级的中断无法被处理实时性无法保证用户任务无法被执行系统显得很卡顿如果这个硬件中断的处理就是非常耗费时间呢对于这类中断的处理就要分为2部分ISR尽快做些清理、记录工作然后触发某个任务任务更复杂的事情放在任务中处理所以需要 ISR 和任务之间进行通信要在FreeRTOS中熟练使用中断有几个原则要先说明FreeRTOS 把任务认为是硬件无关的任务的优先级由程序员决定任务何时运行由调度器决定ISR 虽然也是使用软件实现的但是它被认为是硬件特性的一部分因为它跟硬件密切相关何时执行由硬件决定哪个 ISR 被执行由硬件决定ISR 的优先级高于任务即使是优先级最低的中断它的优先级也高于任务。任务只有在没有中断的情况下才能执行。本章涉及如下内容FreeRTOS 的哪些 API 函数能在 ISR 中使用怎么把中断的处理分为两部分ISR、任务ISR 和任务之间的通信一、两套 API 函数1.1 为什么需要两套 API在任务函数中我们可以调用各类 API 函数比如队列操作函数xQueueSendToBack。但是在 ISR 中使用这个函数会导致问题应该使用另一个函数xQueueSendToBackFromISR它的函数名含有后缀FromISR表示从 ISR 中给队列发送数据。FreeRTOS中很多API函数都有两套一套在任务中使用另一套在ISR中使用。后者的函数名含有FromISR后缀。为什么要引入两套API函数很多 API 函数会导致任务计入阻塞状态运行这个函数的任务进入阻塞状态比如写队列时如果队列已满可以进入阻塞状态等待一会ISR 调用 API 函数时ISR 不是任务ISR 不能进入阻塞状态所以在任务中、在 ISR 中这些函数的功能是有差别的为什么不使用同一套函数比如在函数里面分辨当前调用者是任务还是ISR呢示例代码如下BaseType_txQueueSend(...){if(is_in_isr()){/* 把数据放入队列 *//* 不管是否成功都直接返回 */}else/* 在任务中 */{/* 把数据放入队列 *//* 不成功就等待一会再重试 */}}FreeRTOS 使用两套函数而不是使用一套函数是因为有如下好处使用同一套函数的话需要增加额外的判断代码、增加额外的分支是的函数更长、更复杂、难以测试在任务、ISR 中调用时需要的参数不一样比如在任务中调用需要指定超时时间表示如果不成功就阻塞一会在 ISR 中调用不需要指定超时时间无论是否成功都要即刻返回如果强行把两套函数揉在一起会导致参数臃肿、无效移植 FreeRTOS 时还需要提供监测上下文的函数比如 is_in_isr()有些处理器架构没有办法轻易分辨当前是处于任务中还是处于 ISR 中就需要额外添加更多、更复杂的代码使用两套函数可以让程序更高效但是也有一些缺点核心矛盾FreeRTOS 有两套 API普通任务用的 API、专为中断服务程序ISR设计的FromISR系列 API。限制FromISR函数可以在任务和 ISR 中都调用但普通非FromISR函数只能在任务中用绝对不能在 ISR 中调用。问题场景如果有一个第三方库函数你无法修改它且它内部调用了 FreeRTOS 的普通任务 API但这个库函数既要在任务中用又要在 ISR 中用就会触发 ISR 调用非安全 API 的错误。3 种解决方案方案 1中断延迟处理Defer interrupt processing不在 ISR 里直接处理完整逻辑只做最紧急的事把需要调用库函数的操作放到任务中执行绕开 ISR 的限制。方案 2修改库函数使用 FromISR API如果能修改库函数就把内部的普通 API 替换为FromISR系列让库函数同时兼容任务和 ISR 环境。方案 3库函数自带 OS 抽象层第三方库自己做环境判断在任务中调用普通 API在 ISR 中自动切换为FromISR API对使用者透明。1.2 两套 API 函数列表1.3 xHigherPriorityTaskWoken 参数这个参数在之前的文章中就讲过一次。xHigherPriorityTaskWoken 的含义是是否有更高优先级的任务被唤醒了。如果为pdTRUE则意味着后面要进行任务切换。还是以写队列为例。任务A调用xQueueSendToBack()写队列有几种情况发生队列满了任务 A 阻塞等待另一个任务 B 运行队列没满任务 A 成功写入队列但是它导致另一个任务 B 被唤醒任务 B的优先级更高任务 B 先运行队列没满任务 A 成功写入队列即刻返回可以看到在任务中调用 API 函数可能导致任务阻塞、任务切换这叫做context switch上下文切换。这个函数可能很长时间才返回在函数的内部实现了任务切换。xQueueSendToBackFromISR()函数也可能导致任务切换但是不会在函数内部进行切换而是返回一个参数表示是否需要切换函数原型与用法如下/* * 往队列尾部写入数据此函数可以在中断函数中使用不可阻塞 */BaseType_txQueueSendToBackFromISR(QueueHandle_t xQueue,constvoid*pvItemToQueue,BaseType_t*pxHigherPriorityTaskWoken);/* 用法示例 */BaseType_t xHigherPriorityTaskWokenpdFALSE;xQueueSendToBackFromISR(xQueue,pvItemToQueue,xHigherPriorityTaskWoken);if(xHigherPriorityTaskWokenpdTRUE){/* 任务切换 */}pxHigherPriorityTaskWoken 参数就是用来保存函数的结果是否需要切换*pxHigherPriorityTaskWoken 等于 pdTRUE函数的操作导致更高优先级的任务就绪了ISR 应该进行任务切换*pxHigherPriorityTaskWoken 等于 pdFALSE没有进行任务切换的必要在FromISR中标记是否需要切换在 ISR 返回之前再进行任务切换示例代码如下voidXXX_ISR(){inti;BaseType_t xHigherPriorityTaskWokenpdFALSE;for(i0;iN;i){xQueueSendToBackFromISR(...,xHigherPriorityTaskWoken);/* 被多次调用 */}/* 最后再决定是否进行任务切换 */if(xHigherPriorityTaskWokenpdTRUE){/* 任务切换 */}}上述的例子很常见比如 UART 中断在 UART 的 ISR 中读取多个字符发现收到回车符时才进行任务切换。在ISR中调用API时不进行任务切换而只是在xHigherPriorityTaskWoken中标记一下除了效率还有多种好处效率高避免不必要的任务切换让 ISR 更可控中断随机产生在 API 中进行任务切换的话可能导致问题更复杂可移植性在 Tick 中断中调用 vApplicationTickHook()它运行与 ISR只能使用FromISR的函数使用FromISR函数时如果不想使用xHigherPriorityTaskWoken参数可以设置为NULL。1.4 怎么切换任务FreeRTOS 的 ISR 函数中使用两个宏进行任务切换portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);或portYIELD_FROM_ISR(xHigherPriorityTaskWoken);这两个宏做的事情是完全一样的在老版本的 FreeRTOS 中portEND_SWITCHING_ISR 使用汇编实现portYIELD_FROM_ISR 使用 C 语言实现新版本都统一使用 portYIELD_FROM_ISR。使用示例如下voidXXX_ISR(){inti;BaseType_t xHigherPriorityTaskWokenpdFALSE;for(i0;iN;i){xQueueSendToBackFromISR(...,xHigherPriorityTaskWoken);/* 被多次调用 */}/* 最后再决定是否进行任务切换 * xHigherPriorityTaskWoken 为 pdTRUE 时才切换 */portYIELD_FROM_ISR(xHigherPriorityTaskWoken);}如果中途唤醒了更高级的任务系统就会把最后一个参数变为pdTRUE但是这样只是设置了系统不会自己去调用更高优先级的任务必须我们手动写一行代码开启调度才能在中断结束后运行更高优先级的任务。如果传入最后一个参数时是NULL是不是更高优先级任务要在中断结束后的下一个TICK才会执行。在传入xHigherPriorityTaskWoken参数时一定不能直接传pdTRUE或者pdFALSE因为要传入的是地址而不是一个值。这个地址的初始值是pdTRUE还是pdFALSE不影响因为如果有更高优先级任务被唤醒系统就会设置它为pdTRUE二、中断的延迟处理前面讲过ISR 要尽量快否则其他低优先级的中断无法被处理实时性无法保证用户任务无法被执行系统显得很卡顿如果运行中断嵌套这会更复杂ISR 越快执行约有助于中断嵌套如果这个硬件中断的处理就是非常耗费时间呢对于这类中断的处理就要分为2部分ISR尽快做些清理、记录工作然后触发某个任务任务更复杂的事情放在任务中处理这种处理方式叫中断的延迟处理(Deferring interrupt processing)处理流程如下图所示t1任务 1 运行任务 2 阻塞t2发生中断该中断的 ISR 函数被执行任务 1 被打断ISR 函数要尽快能快速地运行它做一些必要的操作(比如清除中断)然后唤醒任务 2t3在创建任务时设置任务 2 的优先级比任务 1 高(这取决于设计者)所以ISR 返回后运行的是任务 2它要完成中断的处理。任务 2 就被称为deferred processing task中断的延迟处理任务。t4任务 2 处理完中断后进入阻塞态以等待下一个中断任务 1 重新运行有关【FreeRTOS】中断管理 内容就到这里希望对你有所帮助感谢观看码文不易留个赞再走吧~参考文章韦东山FreeRTOS入门与工程实践

更多文章