FreeRTOS学习笔记十【中断管理-上】
目的
- 介绍可以在中断服务函数例使用的FreeRTOS API函数。
- 介绍将中断中处理的内容推迟到任务中处理的方法。
- 创建和使用二值信号量和计数信号量。
- 二值信号量和计数信号量的区别。
- 使用队列将数据传入和传出中断服务函数。
- 介绍一些FreeRTOS移植的中断嵌套模型。
中断中使用API
中断安全的API
通常需要在中断服务函数(ISR)中调用FreeRTOS的API函数,但许多的API在ISR中是不安全的,其中一些API会将调用的任务转换到阻塞态,如果在ISR中调用了这类API则会出现很多问题。FreeRTOS通过提供两个版本的API来解决这个问题,一个版本供任务调用,一个版本供ISR调用,用于ISR版本的API其函数名都带有"FromISR"后缀。用于中断版的API代码更简洁,ISR代码更高效,并且中断版的输入参数更简单。
注意:千万不要在中断服务函数中调用没有"FromISR"后缀的API函数。
xHigherPriorityTaskWoken参数
如果中断当前执行的上下文(发送中断,暂停当前执行的任务,进入到中断服务函数中执行),则中断服务函数退出时接下来运行的任务可能与进入中断之前运行的任务不同。例如:任务1正在运行,任务2在等待队列中的数据,现在发送了中断,任务1被抢占,进入中断后,ISR向队列中写入数据,此时任务进入就绪态,ISR结束后任务2进入运行态。这种情况下中断前运行的时任务1,中断后运行的时任务2.
如果API函数(入队、延时时间到达等API)解除阻塞的任务的优先级高于运行态任务的优先级,根据内核的调度策略,应该会立即切换到高优先级的任务,实际发生任务切换时,取决于调用API函数的上下文:
- 从任务中调用API函数
如果将FreeRTOSConfig.h中configUSE_PREEMPTION设置为1,那么切换到高优先级任务的调度会在API函数中自动发送(即在API函数退出之前),如FreeRTOS学习笔记六【任务管理-调度算法】中的抢占式调度。 - 从中断中调用函数
在中断内不会自动切换到高优先级的任务,相反,需要应用程序设置变量以通知调度器执行上下文切换。中断安全版的API(以“FromISR”结尾的函数) 具有一个名为pxHigherPriorityTaskWoken的指针参数就是作用于此。如果应该执行上下文切换,则中断安全版API函数将(*pxHigherPriorityTaskWoken)设置为pdTRUE,因此,pxHigherPriorityTaskWoken指向的变量必须在第一次使用前初始化为pdFALSE。如果应用程序选择不从ISR返回时的pxHigherPriorityTaskWoken的状态切换上下文,那么优先级较高的任务将保持就绪态,直到下一次调度程序运行(最坏情况下将在下次滴答中断时切换)。
API函数只能将(*pxHigherPriorityTaskWoken)设置为pdTRUE,如果ISR调用多个API函数,则可以给每个API传入一个pxHigherPriorityTaskWoken指向的变量,但必须在第一次使用前初始化为pdFALSE。
在中断安全版的API中不自动切换上下文有一下几个原因:
- 避免不必要的上下文切换
在执行任何任务前,中断可以发生很多次。例如:UART中断,每收到一个字节就会中断一次,但是需要在这一段数据接收完后才会处理。 - 控制执行顺序
中断是偶尔发生的,并且发生时间也不可预测。 - 可移植性
不自动切换上下文是所有处理器移植时最简单的机制。 - 效率
在小型处理器中,同一ISR中可以调用多次API,但是不需要(或不允许)在同一个ISR中切换多次上下文,而只能在ISR最后切换上下文。 - 在滴答中断中执行
可以将应用程序代码添加到RTOS的滴答中断中执行,在滴答中断内尝试切换上下文的结果取决于正在使用的FreeRTOS的移植。充其量,它只会导致调度程序不必要的调用。
pxHigherPriorityTaskWoken参数是可选的。如果不需要,将pxHigherPriorityTaskWoken设置为NULL即可。
portYIELD_FROM_ISR() 和portEND_SWITCHING_ISR()宏
taskYIELD() 是一个可以在任务中调用以请求上下文切换的宏。portYIELD_FROM_ISR() 和portEND_SWITCHING_ISR() 都是taskYIELD() 的中断安全版本。 portYIELD_FROM_ISR() 和portEND_SWITCHING_ISR() 以相同的方式使用,并执行相同的操作。 一些FreeRTOS移植仅提供两个宏中的一个。 较新的FreeRTOS移植提供两种宏。 本文将使用portYIELD_FROM_ISR()宏。
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
从中断安全版的API传出的xHigherPriorityTaskWoken可以直接用于这两个宏的参数,如果xHigherPriorityTaskWoken为pdFALSE,调用portYIELD_FROM_ISR()将不会发生上下文切换,否则就会发生上下文切换,并且处于运行态的任务会改变。大多数Free RTOS的移植中允许在ISR内的任何地方调用portYIELD_FROM_ISR(),但一些小型处理器只允许在ISR最后调用portYIELD_FROM_ISR()。
延迟中断处理
通常,ISR需要尽可能的短,简单。原因有一下几点:
- 即使任务分配了非常高的优先级,它们也只能在没有硬件中断服务时运行。
- ISR可以破坏任务的运行时间。
- 某些处理器在执行ISR不能接收新的中断或不能被打断。
- 某些处理器可以执行中断嵌套,但是中断嵌套越多,就越复杂,也不可预测。
- 应用程序需要考虑任务和ISR同时访问变量,外设、内存等资源时的注意事项。
中断服务程序必须记录中断的产生原因,并清除中断。中断所需要的其他任何处理都可以在任务中执行,以保证ISR尽可能快的退出,这称为延迟中断处理,因为将中断所需处理的放到了任务里处理。将中断处理推迟到任务还允许应用程序对于其他任务确定处理的优先级,并可以使用所有API函数。如果延迟处理的任务优先级高于其他任务优先级,则将立即执行处理,就像在ISR里执行一下。下图描述了这种情况,任务1是普通应用程序任务,任务2是延迟中断处理任务。
图中,中断处理中t2开始,并执行到t4才结束,但是仅在t2到t3之间才是ISR的处理时间,如果没有使用延迟终端处理,那么在t2到t4之间的整个时间段都会在ISR中。关于何时在ISR中执行最好,何时将处理推迟到任务中,没有绝对的规则,但在以下情况发生时将处理延迟到任务是最有用的:
- 中断所需处理的并非简单的任务,例如,如果中断只是存储数模转换的结果,那在ISR中执行时最好的,但如果转换结果需要软件滤波,那么它最好时在任务中执行滤波。
- 无法在ISR内执行的操作,例如,分配内