【ESP32+freeRTOS学习笔记-(七)中断管理】

2023-05-16

目录

  • 1、概述
  • 2、在ISR中使用FreeRTOS中专用的API
    • 2.1 独立的用于ISR中的API
    • 2.2 关于xHigherPriorityTaskWoken 参数的初步理解
  • 3、延迟中断处理的方法-将中断中的处理推迟到任务中去
  • 4 方法一:用二进制信号量来同步ISR与”延时处理的任务“
    • 4.1 二进制信号量
    • 4.2 函数用法
      • 4.2.1 xSemaphoreCreateBinary(void)
      • 4.2.2 xSemaphoreGive() & xSemaphoreGiveFromISR()
      • 4.2.3 xSemaphoreTake()
    • 4.3 使用二进制信号量将任务与中断同步实例
  • 5 方法二:使用计数信号量实现ISR与任务的同步
    • 5.1 xSemaphoreCreateCounting()
    • 5.2 实例:使用计数信号量将任务与中断同步
  • 6 方法三:将工作推迟到 RTOS 守护进程
    • 6.1 RTOS Daemon Task 守护进程
    • 6.2 发送延迟处理任务到守护进程
    • 6.3 xTimerPendFunctionCallFromISR()
    • 6.4 实例
  • 7 在中断服务程序中使用队列
    • 7.1 相关函数
    • 7.2 使用 ISR 中的队列时的注意事项
    • 7.3 例
  • 总结

1、概述

MCU中都是以中断的方式处理各类事件,需要硬件的支持。因此中断系统是一个非常重要的系统。而FreeRTOS是以任务的方式处理事件。任务是纯软件的方式。因此FreeRTOS不可避免的要同时处理好硬件层的中断与软件层的任务的关系。

中断是需要硬件支持 的,但是中断被触发后,必然要跳转到中断处理程序ISR去完成后续的事件处理,而ISR则是软件代码。也就是说不论硬件的中断还是软件的任务,最终的处理其实都是软件的代码实现。虽然是用软件编写的,通常认为中断服务程序ISR是一种硬件功能的一部分,因为硬件控制着哪个中断服务程序将运行以及何时运行。

理解到这里很重要。软件任务有优先级,硬件功能的中断也有优先级。任务只有在没有 ISR 运行时才会运行,因此最低优先级的中断会中断最高优先级的任务,任务无法抢占 ISR。

由于FreeRTOS中的任务是在每一个Tick中必须参与调度的。而中断的优先级又比任务的优先级高。因此中断的ISR一旦运行,则任务的调度实际是停止的**(当然,Tick中断也是系统及中断,这里涉及到系统中断中的优先级问题,而本文是讨论中断与任务的关系,因此不在此详述。)**。因此就要求ISR中的所有事件的处理代码不能占用太长的CPU时间,否则必然会导至整个多任务系统的停止。

基于以上的原因,处理好中断与任务之间的关系,数据的传递,执行时间的分配等就成为我们必须时刻关注的。
因此,本章重点完成任务与中断相关的以下内容详述:

-> 可以在中断服务例程中使用哪些 FreeRTOS API 函数。
-> 将中断处理推迟到任务的方法。
-> 如何创建和使用二进制信号量和计数信号量。
-> 二进制信号量和计数信号量之间的区别。
-> 如何使用队列将数据传入和传出中断服务程序。
-> 某些 FreeRTOS 端口可用的中断嵌套模型。

2、在ISR中使用FreeRTOS中专用的API

前面已说明了,ISR不能执行太长时间,特别是不能有可能导至阻塞的代码或函数语句。否则一旦ISR执行太长时间(比如进入阻塞状态)将会导至整个系统停摆(因为ISR阻塞期间FreeRTOS任务调度无法进行)。

实际使用中,我们通常会在中断服务例程 (ISR) 中使用 FreeRTOS API 函数,但许多 FreeRTOS API 函数执行的操作会使得程序进入Blocked状态;比如读到队列数据时,可能会进入阻塞, 以等待队列中有可读的数据。因此这类会导致阻塞状态的API是不能在ISR使用的。

FreeRTOS为解决ISR的尽可能短而高效持行的问题提供了两个办法。1、是提ISR专用的API,2、把某些长耗时工作延迟到其后的任务中持行。以下就这两方面进行解说。

2.1 独立的用于ISR中的API

FreeRTOS通过提供两个版本的一些API函数解决了这个问题;一个版本用于任务,一个版本用于ISR。用于 ISR 的函数在其名称后附加了“FromISR” 。比如以下的队列接收函数,提供了两个版本,一个是任务中使用的API,一个是ISR中用的API。
在这里插入图片描述
在这里插入图片描述

注意:切勿从 ISR 调用名称中没有“FromISR”的 FreeRTOS API 函数。

2.2 关于xHigherPriorityTaskWoken 参数的初步理解

有这和一个场 景:
从一个正在执行的任务A中,因为中断而跳转到ISR进行处理,而假设ISR中用xQueueSendFromISR()函数向队列写入了一个元素,使得原本一个优先级高于A的任务B(因为在等待读取这个队列的数据而处于阻塞状态)可以退出阻塞,则ISR处理完后,是跳转到任务A中,还是跳转到优先级较高的任务B。

FreeRTOS在这里的处理方式是由使用者来选择。
1、自然情况下,应该是ISR执行完后,应该跳回中断发生的位置,即任务A中。
2、在运行带FromISR的函数里,带有一个标志变量 xHigherPriorityTaskWoken, 这个变量会当有更高优先级任务B离开阻塞状态时(就如上面场景中所述的),而被自动设为pdTRUE,这样使用者可以手动在ISR中去切换到任务B,而不是切换回任务A。这个手动切换的命令为:portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); 当xHigherPriorityTaskWoken为pdTRUE时则切换到任务B,否则还是回到任务A中。

以上这部分的任务切换,按官方的术语,叫上下文切换。
实例 :

void vBufferISR( void )   //这是一个ISR中断处理程序
{
char cIn;
BaseType_t xHigherPriorityTaskWoken;
 xHigherPriorityTaskWoken = pdFALSE;  //默认值必须设为pdFALSE
 do{     //该循环的意图是从buffer中不断读出一个字符,并写入到队列中
	 cIn = INPUT_BYTE( RX_REGISTER_ADDRESS ); //从buffer中读出字符
	 xQueueSendToBackFromISR( xRxQueue, &cIn, &xHigherPriorityTaskWoken );//写入队列,如果出现有相应的高优先级的任务退出阻塞进入ready状态,则xHigherPriorityTaskWoken变量将被设为pdTRUE.
 
 } while( INPUT_BYTE( BUFFER_COUNT ) );
 
 portYIELD_FROM_ISR( xHigherPriorityTaskWoken );  //根据xHigherPriorityTaskWorken的值决定是回到该ISR的中断点,还是切换到新的任务里。
}

3、延迟中断处理的方法-将中断中的处理推迟到任务中去

由于一个ISR必须尽可能的短,这样对于一些可能长时间运行的工作,就不适合在ISR中运行,而FreeRTOS的解决办法就是,把这些必须长时间运行的工作移到ISR运行结束后所切换到的任务中去运行。从而保证ISR尽可能的短。这称为“延迟中断处理”,因为中断所需的处理从 ISR“延迟”到任务。

官方文档里的一个示图,很好的说明了这种“将中断中的处理推迟到任务中去”的处理过程。
在这里插入图片描述

t1时刻,Task1任务正常运行。
t2时刻,出现一个中断,这时ISR开始运行。ISR任务执行过程中使得task2任务退出阻塞态,进入ready态。
t3时刻,ISR结束,并由开发者选择进行上下文切换,使Task2任务进入运行。这里有个细节,我们在设置Task2时,如果把优先级设置得高于Task1,那么ISR一结束,会因为portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); 函数的调用而直接切换到Task2。如果Task2的优先级并未高于Task1,那么即使调用了portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); Task2就不会马上执行,而只能等到调度器来决定切换到哪个任务。所以正常在使用的时,我们一般会把Task2的优先级高于Task1。
t4时刻,当Task2重新进入阻塞状态后,调度器重新切换到Task1任务执行。

选择“延时中断处理”的场景,一般是中断ISR中只处理与外设的快速通信,而对于读入的数据的运算与处理则放到“被延时的处理任务”中去处理。
如,串口中断负责将串口数据读出并写入到一个队列中,而“延时处理任务”则负责将队列中的数据进行转换运算等需要大量时间才能完成的工作。

4 方法一:用二进制信号量来同步ISR与”延时处理的任务“

上一节讲到可以把一些耗时多的工作放到ISR以外的任务中去完成。这个任务叫做“延时处理任务 ”。具体如何实现这种“中断延时处理”的机制。本节就介绍以二进制信号量来实现ISR与“延时处理任务”的同步。

4.1 二进制信号量

二进制信号量实质就是一个标志。其用法通常是这样的。平常,该标志为空,没有内容。take任务要读取该标志“行为是TAKE”,当该标志为空时则take任务进入阻塞状态。give任务向这个标志写入一个标志,写入后,系统会通知take任务,使take任务退出阻塞状态,进入ready状态。而这个二进制信号量的实现是以队列的方式实现的,二进制信号是相当于一个只有一个元素的队列。
所以二进制信号量常用于同步两个任务或ISR与任务之间的持行顺序。take任务此刻需要在阻塞状态中等待二进制信号量。当一个ISR中断服务程序中发现一个GIVE指令,向二进制信号量写入一个标志。这时会使take任务退出阻塞状态进入ready状态。这样在ISR里就可以直接激活这个take任务了。见下图:
在这里插入图片描述

以上就是二进制信号量的使用方法。可以将二进制信号量用来实现在ISR与“延迟处理函数”的同步。

4.2 函数用法

4.2.1 xSemaphoreCreateBinary(void)

在这里插入图片描述
描述:
创建一个二进制信号量,并返回一个用于引用信号量的句柄。

返回值:
NULL : 没有创建二进制信号量。

其它非NULL在值: 二进制信号量被成功创建,返回的值是这个二进制信号量的引用句柄 。

举例:
在这里插入图片描述

4.2.2 xSemaphoreGive() & xSemaphoreGiveFromISR()

在这里插入图片描述
参数:

参数说明
xSemaphore目标二进制信号量的句柄
pxHigherPriorityTaskWoken用于决定ISR是否执行上下文切换的标识变量

pxHigherPriorityTaskWoken详解:可能有多个任务因为等待一个信号量变为可用而处于阻塞状态。调用xSemaphoreGiveFromISR()可以使信号量可用,从而使这些等待的任务离开Blocked状态。如果调用xSemaphoreGiveFromISR()导致任务离开“阻塞”状态,并且未阻塞的任务的优先级高于或等于当前正在执行的任务(被事件中断前正在执行的任务),则xSemaphreeGivefromISR(()将在内部将*pxHigherPriorityTaskWoken设置为pdTRUE。如果xSemaphoreGiveFromISR()将此值设置为pdTRUE,则应在中断退出之前执行上下文切换。这将确保中断直接返回到最高优先级的就绪状态任务。

返回值

说明
pdTRUE函数执行成功
errQUEUE_FULL如果一个二进制信号量已处于可用(被置位了),则返回该失败值

补充说明
在中断服务例程中调用xSemaphoreGiveFromISR()可能会导致正等待获取信号量的任务脱离“阻塞状态”。如果这个脱离阻塞状态的任务的优先级高于或等于当前正在执行的任务(被事件中断前正在执行的任务),则应执行上下文切换。上下文切换将确保中断直接返回到最高优先级的就绪状态任务。
与xSemaphoreGive()API函数不同,xSemaphreeGiveFromISR()本身不会执行上下文切换。它将只指示是否需要上下文切换。
在启动调度程序之前,不得调用xSemaphoreGiveFromISR()。因此,在启动调度程序之前,不允许执行调用xSemaphoreGiveFromISR()的中断。

4.2.3 xSemaphoreTake()

在这里插入图片描述

描述:
用于获取此前已经创建的信号量。这个信号量可以是二进制信号量,计数信号量,互斥信号量。

参数

参数说明
xSemaphore信号量句柄。
xTicksToWait如果要获取的信号量不可用时,使本任务进入阻塞状态等待的时间

返回值
|值|说明|
|pdPASS|当成功获取信号量时,返回pdPASS|
|pdFAIL|返回该值时,说明获取信号量失败|

补充说明:
xSemaphoreTake()只能从正在执行的任务中调用,因此在调度程序处于初始化状态(在启动调度程序之前)时不能调用。
xSemaphoreTake()不能在关键节内或在调度程序挂起时调用

4.3 使用二进制信号量将任务与中断同步实例

此例是官方文档中的一个例子,部份代码为伪代码。重点再于理解用二进制信号量实现的同步逻辑。
此示例使用二进制信号量从中断服务例程中解除处于阻塞中的任务——有效地将任务与中断同步。
例中由一个简单的周期性任务用于每 500 毫秒生成一个软件中断。使用软件中断是为了方便,因为在某些目标环境中挂接到真实中断
很复杂。伪代码中 显示了周期性任务的实现。请注意,该任务在生成中断之前和之后都打印出一个字符串。这允许在执行示例时
产生的输出中观察执行顺序
在这里插入图片描述
以下是中断处理程序,在这里,由于向二进制信号量做了GIVE操作,所以会同时使那些正在等待信号量而阻塞的任务(本例为下方的vHandleTask()任务)进入ready状态。
在这里插入图片描述

在这里插入图片描述
以下是主函数。程序开始运行,先通过主函数分别启动了周期性产生中断的vPriodicTask()任务和中断延迟处理任务vHandlerTask()。

注意以下的任务的优先级,vHandlerTask的优无级为3,比其它都高,是为了确保ISR可以立即切换到该任务执行。
在这里插入图片描述

运行时序图

在这里插入图片描述

示例 使用二进制信号量将任务与中断同步。执行顺序如下:

  1. 中断发生。
  2. ISR 执行并“给予”信号量以解除阻塞任务。
  3. ISR 之后立即执行的任务,并“获取”了信号量。
  4. vHandlerTask()任务处理了事件,然后再次尝试“获取”信号量——进入Blocked 状态,因为信号量尚不可用(另一个中断已尚未发生)

输出结果
产生了如图51所示的输出。正如预期的那样,一旦中断生成,vHandlerTask()就会进入Running状态,因此任务的输出会分割周期性任务产生的输出(如图Handler task被显示在两个Perodic task中间)。
在这里插入图片描述

二进制信号量的弱点
二进制信号量的使用场景一般是中断发生得相对低频的情况下。因为如果太高频,会出现上第一个信号量被take正在任务中处理,第二个信号量已被GIVE,但已无法被take,因为take任务正在处理第一个事件,而这时又一个事件发生,第三个信号量就无法再give,这时新的信号量实际未被GIVE,而是被丢弃了。

5 方法二:使用计数信号量实现ISR与任务的同步

二进制信号量只能使用在中断较低频的场景下,因此很自然的想到,把信号量中记录事件的标志数量增大就可以很好的适应相对高频的使用场景。这里引入了计数信号量。

正如二进制信号量可以被认为是长度为 1 的队列一样,计数信号量也可以被认为是长度大于 1 的队列。任务对存储在队列中的数据不感兴趣——只对队列中的项目数感兴趣。configUSE_COUNTING_SEMAPHORES 必须在FreeRTOSConfig.h 中设置为 1,以便计数信号量可用。

计数信号量常被用于以下两个场景:
1、 计数事件1
在这种情况下,事件处理程序将在每次事件发生时“给予”一个信号量——导致信号量的计数值在每次“给予”时递增。任务每次处理事件时都会“获取”一个信号量——导致信号量的计数值在每次“获取”时递减。计数值是已发生的事件数与已处理的事件数之差。这种机制如图 55 所示。创建用于计数事件的计数信号量,其初始计数值为零。
在这里插入图片描述

2、 资源管理。
在这种情况下,计数值表示可用资源的数量。为了获得对资源的控制,任务必须首先获得一个信号量——递减信号量的计数值。当计数值达到零时,没有空闲资源。当任务完成资源后,它会“返回”信号量——增加信号量的计数值。

5.1 xSemaphoreCreateCounting()

在这里插入图片描述
描述
创建一个计数信号量,并返回其句柄。

参数

参数说明
uxMaxCount可以达到的最大计数值。当信号量达到这个值时,它就不能再被“given给予”了。
uxInitialCount创建信号量时分配给它的计数值。

返回值
NULL : 没有分配
非NULL的其它值 : 分配成功,值是该信号量的句柄。

5.2 实例:使用计数信号量将任务与中断同步

通过使用计数信号量代替二进制信号量对示例 4.3 的实现进行了改进。 main() 已更改为包括对xSemaphoreCreateCounting() 的调用,而不是对 xSemaphoreCreateBinary() 的调用。因此修改的代码如下:

xCountingSemaphore = xSemaphoreCreateCounting( 10, 0 );  //创建的是计数信号量,长度为10,初始计数值为0

为了模拟高频发生的多个事件,中断服务程序被更改为每个中断多次“给予”信号量。每个事件都被锁存在信号量的计数值中。修改后的中断服务例程如下 所示。

static uint32_t ulExampleInterruptHandler( void )
{
	BaseType_t xHigherPriorityTaskWoken;
 	xHigherPriorityTaskWoken = pdFALSE;
 	/*多次“给予”信号量。第一个将解除对延迟中断处理任务的阻塞,下面的“给予”是为了证明信号量锁存事件
 	以允许中断被延迟到的任务依次处理它们,而不会丢失事件。这模拟了处理器接收到的多个中断,即使在这种
 	情况下,事件是在单个中断发生中模拟的。 */
	xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
 	xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
 	xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
 
	 portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); //根据参数的值决定是否上下文切换到“延迟中断处理任务”
}

其它的代码就省略了,都与示例4.3一样。最后给出运行结果。
在这里插入图片描述

6 方法三:将工作推迟到 RTOS 守护进程

到目前为止所介绍的延迟中断处理示例要求应用程序编写者为每个使用延迟处理技术的中断创建一个任务。还可以使用 xTimerPendFunctionCallFromISR()1 API 函数将不能在中断中处理的一些工作推迟到 RTOS 守护程序任务——无需为每个中断创建单独的任务。将中断处理推迟到守护程序任务称为“集中延迟中断处理”。

6.1 RTOS Daemon Task 守护进程

RTOS Daemon Task - RTOS守护程序,是RTOS软件定时器功能系统的主要模块 。我们在创建完任务后,必须调用vTaskStartSchedule()启动定时器,在此期间由调度器自动创建的。用于在系统的每一个tick中断时进行定时器相关的工作,包括执行定时器命令队列的命令,执行定时器的回调函数等。

6.2 发送延迟处理任务到守护进程

上一章描述了与软件定时器相关的 FreeRTOS API 函数如何将命令发送到定时器命令队列上的守护进程任务。
xTimerPendFunctionCall() 和xTimerPendFunctionCallFromISR() API 函数使用相同的计时器命令队列向守护程序任务发送“要推迟的工作”命令。发送给守护任务的函数然后在守护任务的上下文中执行。

xTimerPendFunctionCallFromISR() 是 xTimerPendFunctionCall() 的中断安全版本。
这两个 API 函数都允许应用程序编写者提供的函数(回调函数)由 RTOS 守护程序任务执行,因此也可以在 RTOS 守护程序任务的上下文中执行。要执行的函数和函数的输入参数的值都通过 timer 命令发送到守护程序任务队列。因此,函数实际执行的时间取决于守护程序任务相对于应用程序中其他任务的优先级。

6.3 xTimerPendFunctionCallFromISR()

在这里插入图片描述

描述:
从应用程序中断服务例程中使用,将回调函数的执行延迟到RTOS守护程序任务。
理想情况下,中断服务例程(ISR)尽可能短,但有时ISR要么有很多处理要做,要么需要执行不确定的处理。在这些情况下,xTimerPendFunctionCallFromISR()可用于将函数的处理延迟到RTOS后台进程任务。这允许回调函数与中断在时间上连续执行,就像回调在中断本身中执行一样。可以延迟到RTOS守护进程任务的回调函数必须具有以下的原型
在这里插入图片描述

参数

参数说明
xFunctionToPend要从守护程序任务执行的函数。该函数必须符合所示的PendedFunction_t原型。
pvParameter1将作为函数的第一个参数传递给回调函数的值。该参数具有void类型,可用于传递任何类型。例如,整数类型可以转换为void,或者void*可以用于指向结构
ulParameter2将作为函数的第二个参数传递给回调函数的值。
pxHigherPriorityTaskWoken调用xTimerPendFunctionCallFromISR()将导致在队列上向RTOS计时器守护进程任务发送消息。如果守护程序任务的优先级(由FreeRTOSConfig.h中的configTIMER_task_priority值设置)高于当前正在运行的任务(中断中断的任务)的优先级,则*pxHigherPriorityTaskWoken将为在xTimerEndFunctionCallFromISR()内设置为pdTRUE,指示在中断退出之前应请求上下文切换。因此,*pxHigherPriorityTaskWoken必须初始化为pdFALSE。

返回值

说明
pdPASS函数执行成功,命令被成功发送给守护进程
任意其它值由于消息队列已满,消息未发送到RTOS守护程序任务。队列的长度由的值设置 FreeRTOSConfig.h中的configTIMER_QUEUE_LENGTH

注意
FreeRTOSConfig.h 文件中的 INCLUDE_xTimerPendFunctionCall 与 configUSE_TIMERS 宏必须都设置为1, xTimerPendFunctionCallFromISR() 才可用。

6.4 实例

本例 提供了与前例 4.3 类似的功能,但没有使用信号量,也没有创建专门用于执行中断所需处理的任务。相反,处理由 RTOS 守护程序任务执行。示例 的中断服务例程如下所示。它调用 xTimerPendFunctionCallFromISR() 以将指向名为vDeferredHandlingFunction() 的函数的指针传递给守护程序任务。延迟中断处理由vDeferredHandlingFunction() 函数执行。
中断服务例程每次执行时都会增加一个名为 ulParameterValue 的变量。 ulParameterValue 在调用xTimerPendFunctionCallFromISR() 时用作 ulParameter2 的值,因此在守护任务执行vDeferredHandlingFunction() 时也会用作对 vDeferredHandlingFunction() 的调用中的 ulParameter2 的值。该函数的另一个参数 pvParameter1 在此示例中未使用。

static uint32_t ulExampleInterruptHandler( void )
{
	static uint32_t ulParameterValue = 0;
	BaseType_t xHigherPriorityTaskWoken;  
	xHigherPriorityTaskWoken = pdFALSE;  //必须初始化为pdFALSE
	
 /* 将指向中断延迟处理函数的指针发送给守护程序任务。未使用延迟处理函数的 pvParameter1 参数,因此只需设置
为 NULL。延迟处理函数的 ulParameter2 参数用于传递每次执行此中断处理程序时加一的数字。*/
 	xTimerPendFunctionCallFromISR( vDeferredHandlingFunction,    /* Function to execute. */
 								   NULL,                 /* Not used. */
								   ulParameterValue,     /* Incrementing value. */
 								   &xHigherPriorityTaskWoken );
 	ulParameterValue++;
 	/* 将 xHigherPriorityTaskWoken 值传递给 portYIELD_FROM_ISR()。如果在 xTimerPendFunctionCallFromISR() 运行过程中将
xHigherPriorityTaskWoken 设置为 pdTRUE,则调用 portYIELD_FROM_ISR() 将请求上下文切换(即中断程序执行后后会切换到守护程序)。如果
xHigherPriorityTaskWoken 仍为 pdFALSE,则调用 portYIELD_FROM_ISR() 将无效。 */
 	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

vDeferredHandlingFunction() 的实现如下 所示。它打印出一个固定的字符串,以及它的 ulParameter2 参数的值。

static void vDeferredHandlingFunction( void *pvParameter1, uint32_t ulParameter2 )
{ 
 
 	vPrintStringAndNumber( "Handler function - Processing event ", ulParameter2 );
}

以下是主函数main()的代码

int main( void )
{
	/* 生成软件中断的任务的优先终低于守护进程任务。守护进程任务的优先级由FreeRTOSConfig.h中的宏configTIMER_TASK_PRIORITY确定。*/
	const UBaseType_t ulPeriodicTaskPriority = configTIMER_TASK_PRIORITY - 1;
 	/* 创建一个周期性生成软中断的任务。 */
 	xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, ulPeriodicTaskPriority, NULL );
 	/* 以下语句设定了中断对应的ISR */
 	vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );
 	
 	vTaskStartScheduler();
 
 /* As normal, the following line should never be reached. */
 for( ;; );
}

输出结果
在这里插入图片描述
执行时序
在这里插入图片描述
1、大部分时间是空闲任务在运行。每500毫秒它被Periodic任务抢占,软件中断产生函数。
2、Periodic任务打印一个信息后生成一个中断。中断服务程序ulExampleInterruptHandler()立即执行。
3、 中断调用 xTimerPendFunctionCallFromISR(),它写入定时器命令队列,导致守护程序任务解除阻塞。然后中断服务程序直接返回到守护任务,因为守护任务是最高优先级的就绪状态任务。守护程序任务在返回阻塞状态以等待另一条消息到达计时器命令队列或软件计时器到期之前打印出它的消息,包括递增的参数值。
4、守护进程执行完成后,Periodic又成为ready状态中的最高优先级的任务,因此继续执行。

7 在中断服务程序中使用队列

7.1 相关函数

从前面的介绍可以发现二进制信号量和计数信号量可用于传达事件。而队列则可用于传递事件和传输数据。
xQueueSendToFrontFromISR() 是可在中断服务例程中安全使用的 xQueueSendToFront() 版本,
xQueueSendToBackFromISR() 是可在中断服务例程中安全使用的 xQueueSendToBack() 版本,
xQueueReceiveFromISR() 是 xQueueReceive() 可以在中断服务例程中安全使用。

在这里插入图片描述
在这里插入图片描述
参数

参数说明
xQueue队列句柄
pvItemToQueue要拷贝给队列的数据的指针
pxHigherPriorityTaskWoken看前面的介绍

返回值
pdPASS : 发送成功
errQUEUE_FULL :队列已满,发送失败,返回该值

7.2 使用 ISR 中的队列时的注意事项

队列提供了一种将数据从中断传递到任务的简单方便的方法,但是如果数据到达的频率很高,则使用队列效率不高。
队列的长度毕竟有限,如果数据传入的频率高于数据读出的速度,则很容易造成写入数据丢失或造成写入阻塞。因此,在ISR中使用队列时必须平稀好这个频率。

7.3 例

此示例演示在同一中断中使用 xQueueSendToBackFromISR() 和 xQueueReceiveFromISR()。和以前一样,为方便起见,中断由软件生成。

一、创建一个周期性任务,每 200 毫秒向队列发送五个数字。只有在发送完所有五个值后,它才会生成软件中断。任务实现如下所示

static void vIntegerGenerator( void *pvParameters )
{
	TickType_t xLastExecutionTime;
	uint32_t ulValueToSend = 0;
	int i;
 	/* 用vTaskDelayUntil()函数,获得tick计数. */
 	xLastExecutionTime = xTaskGetTickCount();
 	for( ;; )
 	{
 		/*延迟阻塞200毫秒.  */
 		vTaskDelayUntil( &xLastExecutionTime, pdMS_TO_TICKS( 200 ) );
 		/* 发送5个数字给队列,这些数值由中断服务程序读取。中断服务程序总能清空队列,因此确保了本任务能够在不阻塞的情况下
 		一次写5个数据进队列。*/
 		for( i = 0; i < 5; i++ )
 		{
 			xQueueSendToBack( xIntegerQueue, &ulValueToSend, 0 );
 			ulValueToSend++;
 		}
 		/* 打印一个消息后,生成软中断*/
 		vPrintString( "Generator task - About to generate an interrupt.\r\n" );
 		vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
 		vPrintString( "Generator task - Interrupt generated.\r\n\r\n\r\n" );
 	} 
 }

二、中断服务程序反复调用xQueueReceiveFromISR(),直到周期性任务写入队列的所有值都被读出,队列为空。每个接收到的值的最后两位用作字符串数组的索引。然后使用对 xQueueSendFromISR() 的调用将指向相应索引位置处的字符串的指针发送到不同的队列。中断服务例程的实现如下 所示

static uint32_t ulExampleInterruptHandler( void )
{
	BaseType_t xHigherPriorityTaskWoken;
	uint32_t ulReceivedNumber;
	static const char *pcStrings[] =
	{
 		"String 0\r\n",
 		"String 1\r\n",
 		"String 2\r\n",
 		"String 3\r\n"
	};
 	xHigherPriorityTaskWoken = pdFALSE;  //必须初始化为pdFALSE
 	/* 把队列读到空 */
 	while( xQueueReceiveFromISR( xIntegerQueue, 
 								&ulReceivedNumber, 
 								&xHigherPriorityTaskWoken ) != errQUEUE_EMPTY )
 	{ 
 		ulReceivedNumber &= 0x03;  /*从队列中读到的是数值,只取低三位,用做数组pcStrings的序号 */
 		xQueueSendToBackFromISR( xStringQueue, 
 								&pcStrings[ ulReceivedNumber ], 
 								&xHigherPriorityTaskWoken );
 	}
 	/* 如果从 xIntegerQueue 队列接收数据后导致有与该队列关联的其它任务离开 Blocked 状态,并且离开 Blocked 状态的任务的优先级高于此时处于 Running 状态的任务的优先级,则 xHigherPriorityTaskWoken 将在 xQueueReceiveFromISR() 中设置为 pdTRUE .如果发送到 xStringQueue队列 导致有其它任务任务离开 Blocked 状态,并且如果离开 Blocked 状态的任务的优先级高于处于
Running 状态的任务的优先级,则 xHigherPriorityTaskWoken 将在 xQueueSendToBackFromISR() 中设置为 pdTRUE .
 xHigherPriorityTaskWoken 用作 portYIELD_FROM_ISR() 的参数。如果 xHigherPriorityTaskWoken 等于 pdTRUE,则
调用 portYIELD_FROM_ISR() 将请求上下文切换。如果 xHigherPriorityTaskWoken 仍为 pdFALSE,则调用
portYIELD_FROM_ISR() 将无效。 */
 	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

三、从中断服务例程接收字符串指针的任务在队列没有数据时阻塞,直到消息到达,并在收到每个字符串时打印出来。其实现下 所示。

static void vStringPrinter( void *pvParameters )
{
	char *pcString;
 	for( ;; )
 	{
 		/* Block on the queue to wait for data to arrive. */
 		xQueueReceive( xStringQueue, &pcString, portMAX_DELAY );
 		/* Print out the string received. */
 		vPrintString( pcString );
 	} 
 }

四、主函数

int main( void )
{
 
 	xIntegerQueue = xQueueCreate( 10, sizeof( uint32_t ) ); //创建队列
 	xStringQueue = xQueueCreate( 10, sizeof( char * ) );
 	/* 创建一个用队列向中断服务程序传递整数的任务,该任务的优先级为1 */
 	xTaskCreate( vIntegerGenerator, "IntGen", 1000, NULL, 1, NULL );
 	/* 创建一个任务,把从中断服务程序传送来的这符串打印同来,该任务有较高的优先级2. */
 	xTaskCreate( vStringPrinter, "String", 1000, NULL, 2, NULL );
 	/*安装一个软件中断与服务程序的对应关系 */
 	vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );
 	/* 调度器开始工作 */
 	vTaskStartScheduler();
 	for( ;; );
}

运行结果:
在这里插入图片描述

执行时序分析
在这里插入图片描述

1、大部分时间,空闲任务处于运行状态,位每200毫秒,IntegerGenerator任务抢占执行一次。
2、IntegerGenerator生成5个数字并写入队列。然后生成一次中断。
3、中断每次从队列中读出一个数值,就对应写一个字符串到另一个队列中。这个写入的队列会使StringPrinter任务退出阻塞进入ready状态。
4、在中断服务程序结束后,StringPrinter成为ready状态下最高优先级的任务,因此马上执行,并从队列中读出字符串进行打印。直到把队列读空后,又再一次进入阻塞状态。这样,优先级比它低的IntegerGenerator任务就继续执行。
5、IntegerGenerator是周期性函数,因此进入阻塞,等下一个周期再次执行。

总结

和其它操作系统一样,中断的处理与协调是FreeRTOS中非常重要的一个部分。系统的中断是必须马上执行的,所以即使FreeRTOS中的最高优先级的任务也必须为中断让路。因此中断的执行时间太长,会影响到FreeRTOS的正常调度。所以FreeRTOS对于中断服务程序的要求是就必须尽可能的短,尽可能在最短的时间里执行完毕。为此,FreeRTOS提供了两个机制来解决这个问题。
1、提供了中断中专用的API,这些API 的函数名都带有FromISR后缀。
2、提供了一个优先的上下文切换机制(把任务延迟到任务的机制),使得ISR执行后可以立刻切换到关联的任务中去执行,以免还要按常规方式返回到中断断点处。这个机制的运用到了xHigherPriorityTaskWoken 变量 和 portYIELD_FROM_ISR( xHigherPriorityTaskWoken );函数。

使用FreeRTOS“把任务延迟到任务”的机制,可以通过二进制信号量,计数信号量,守护进程,队列等多种方式实现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【ESP32+freeRTOS学习笔记-(七)中断管理】 的相关文章

  • 视频转换处理软件

    视频转换处理软件是一款专业 易用 高效的视频处理工具 xff0c 软件内置了格式转换 视频剪切 视频转GIF 视频加水印 视频优化 旋转和翻转等六大功能 xff0c 并每个功能模块也都是互相独立的 xff0c 这也就是说 xff0c 用户在
  • 视频剪切软件

    视频剪切是一款功能强势 操作简单的视频剪切工具 xff0c 它不但可以帮助用户将视频中的精彩片段剪切出来 xff0c 轻松制作需要的短片视频 xff0c 而且还支持任意设置输出视频的视频属性和音频属性 xff0c 并支持MP4 rmvb a
  • 神奇电商宝贝下载软件

    神奇电商宝贝下载软件是一款专业 易用 高效的电商软件 xff0c 可帮助用户快速获取1688 天猫 淘宝 速卖通 企业叮咚等平台店铺中的主图视频 详情视频 主图图片 详情图片等所有信息 xff0c 从而轻松满足电商用户的所有需求 而且该软件
  • 布谷鸟算法

    布谷鸟算法是将布谷鸟育雏行为与Levy飞行算法相结合的一种算法 在布谷鸟算法中 xff0c 有两个算法或者说两个位置更新是关键 xff1a 第一个是布谷鸟寻找最优解时的算法 xff1a 一个是布谷鸟寻找鸟窝下蛋的寻找路径是采用早已就有的萊维
  • matlab实现虚拟力策略传感器分布

    第一次写文章 主要想记录一下编程过程中的坎 1 虚拟力策略 虚拟力法原理是 将机器人在环境中的运动视为一种机器人在虚拟的人工受力场的运动 障碍物对机器人产生斥力 目标点对机器人产生引力 引力和斥力的合力作为机器人的加速力 来控制机器人的运动
  • pandas:sample函数解释

    目录 1 函数定义 2 作用 xff1a 3 举个栗子 4 参数解释 1 函数定义 DataFrame sample self FrameOrSeries n 61 None frac 61 None replace 61 False we
  • [Pixhawk/PX4]开发环境搭建(Ubuntu 18.04)和问题总结

    主要记录了PX4环境在Ubuntu 18 04下的搭建过程和问题总结 由于gazebo在ros安装中一般会包括 xff0c 所以gazebo安装没有介绍 一 基础资源下载 这是我第一次接触ubuntu系统的安装 xff0c 所以可能有些步骤
  • 数独基础技巧

    数独基础思想 数独作为一个益智游戏 xff0c 想必大家都或多或少接触过 xff0c 基础思想就是在9 9的方格中 xff0c 每一行每一列每个九宫格都不重复地存在1 9这九个数字 xff0c 这也意味着对一个正确的数独题目都有一个固定的答
  • VM VirtualBox虚拟机路径迁移

    在学习Ubuntu的时候把虚拟机放到了机械硬盘 xff0c 所以系统运行很卡顿 因此尝试了迁移虚拟机到固态硬盘中 方法如下 xff1a 导出需要迁移的虚拟电脑 点击管理 xff0c 点击导出虚拟电脑 选择需要导出的虚拟电脑 xff0c 点击
  • 设计使用 | 四个免费的渐变配色网站

    在进行设计工作时 xff0c 有时候需要一些渐变的配色做底图更好看些 xff0c 本来用Photoshop也可以自己制作 xff0c 但是经常一做就很丑 xff0c 所以还是直接找其他网站上的吧 一 CoolHue 地址 xff1a Gra
  • Ubuntu+Windows双系统,开机默认启动项设置Windows优先

    电脑是Ubuntu18 04 43 Win10双系统 xff0c 开机一直都是默认先Ubuntu xff0c Windows在最下面 xff0c 有时候按下开机键一不留神就进Ubuntu了 xff0c 于是寻思着在启动项中设置为默认优先Wi
  • 解决Gazebo闪退,提示escalating to SIGKILL on server的问题,Melodic更新gazebo9到gazebo11

    0 xff0c 问题描述 今天拿Gazebo自定义一个简单地图 xff0c 建完墙都没什么事 xff0c 结果在墙上加门的时候 xff0c 程序卡死了 xff0c 重复几次都是这样的情况 xff0c 在终端中也出现了提示 escalatin
  • Windows 10 安装 Android 13版本的安卓子系统(带 Google Play 商店和 Magisk) 2210.40000.7.0

    根据微软的说明 xff0c WSA 仅支持 Windows11 xff0c Windows10 及以前的操作系统无缘WSA 不过我们来看一下原理哈 xff1a Android 这个系统 xff0c 它是基于Linux 系统来开发的 xff0
  • Windows 11 安装 Android 13版本的安卓子系统(带 Google Play 商店和 Magisk) 2210.40000.10.0

    微软最近刚刚发布了适用于 Android 的 Windows 子系统 xff0c 构建版本为 2211 xff0c 采用 Android 13 xff0c 目前这还是一个预览版本 xff0c 理论上只有Beta用户才能用 xff0c 但是用
  • 利用 ChatGPT 简化微信聊天内容

    以下文章来源于蓝点网 xff0c 作者山外的鸭子哥 xff0c 侵删 各种聊天软件的群太多 消息太多压根没时间看怎么办 xff1f 那试试使用 ChatGPT 帮你整理群聊记录提取重点和发个总结内容 xff1f ChatGPT目前已经在很多
  • 一步设置,立刻开启New Bing!

    前阵子微软的新必应不是特火嘛 xff0c 我也想试试来着 xff0c 结果除了要一堆设置之外 xff0c 还要等待候补 xff0c 所以也就不了了之了 xff0c 结果今天突然发现 xff0c 只需要进行简单的一步设置 xff0c 就可以立
  • pandas:reset_index及set_index的解释

    目录 1 pandas DataFrame set index 2 pandas DataFrame reset index 1 pandas DataFrame set index 函数原型 xff1a DataFrame set ind
  • New Bing 桌面客户端,支持 Linux/macOS/Win,开源免费,国人开发

    实话实说 xff0c 我并不是很喜欢Edge浏览器 xff0c 但是想用 New Bing 的话 xff0c 就得用它 xff0c 而且我经常使用Linux系统 xff0c 想上 New Bing 就更麻烦了 所以就找到了一个 New Bi
  • 解决“python-roslaunch : 依赖: python-roslib 但是它将不会被安装”问题

    在Ubuntu 18 04安装ROS melodic后 xff0c 测试ros环境 xff1a roscore 发现有提示报错 xff1a Command 39 roscore 39 not found but can be install
  • YOLO V5出现RuntimeError: result type Float can‘t be cast to the desired output type long int解决方法

    在使用YOLO框架训练自己的数据集时候 xff0c 开始跑train py xff0c 出现如下报错 xff1a RuntimeError result type Float can t be cast to the desired out

随机推荐

  • Python程序运行,“libgcc_s.so.1 must be installed for pthread_cancel to work”解决办法

    Ubuntu 18 04 的 ROS 环境下 xff0c 创建了一个Python3 8的环境 xff0c 使用YOLO V5做目标检测 xff0c 然后准备做一些深度图转点云的计算 xff0c 就写了个Python文件 xff0c 用到了下
  • FreeRTOS队列实验时报错Error:..\FreeRTOS\portable\RVDS\ARM_CM3\port.c,699

    FreeRTOS队列实验时报错Error FreeRTOS portable RVDS ARM CM3 port c 699 如下图所示 xff1a 报错的原因是 xff1a 设置处理队列消息的中断优先级不是FreeRTOS可以管理的 xf
  • MySQL储存过程执行报1292 - Truncated incorrect datetime value:

    MySQL储存过程执行报1292 Truncated incorrect datetime value 首先查看数据库的时区查看MySQL数据库的模式最后看一下你复制的储存过程是不是有编码格式问题 xff1f 首先查看数据库的时区 查看数据
  • 8266+DS3231时钟之开发个时钟遇到的N个坑【一】

    这个时钟系列目前五篇分别是 xff1a 8266 43 DS3231时钟之开发个时钟遇到的N个坑 一 8266 43 ds3231时钟之arduino官网发布的DS3231库的分析 二 8266 43 DS3231时钟之DS3231具体实现
  • ILI9341的使用之【一】TFT-LCD原理(转载)

    近期开始研究手上的LCD屏的使用 该LCD屏使用了ILI9341的IC做为驱动 因此边研究边留下记录 与上一个时钟系列类似 xff0c 想必这又是一个大工程 xff0c 因为ILI9341的数据手册就有200多页 xff0c 从硬件原理 x
  • ILI9341的使用之【二】ILI9341介绍

    ILI9341的使用之 一 TFT LCD原理 xff08 转载 xff09 ILI9341的使用之 二 ILI9341介绍 ILI9341的使用之 三 ILI9341系统通信接口模式操作详解 ILI9341的使用之 四 RGB接口操作详解
  • ILI9341的使用之【三】ILI9341系统通信接口模式操作详解

    ILI9341的使用之 一 TFT LCD原理 xff08 转载 xff09 ILI9341的使用之 二 ILI9341介绍 ILI9341的使用之 三 ILI9341系统通信接口模式操作详解 ILI9341的使用之 四 RGB接口操作详解
  • 图说蚁群算法(ACO)附源码

    PS xff1a 再过几天就可以返校收拾东西了 xff0c 想想还有点小激动呐hhh 回想疫情宅家的这半年 xff0c 真是一段充满了焦虑 惊喜 忙碌 充实又时而无聊的时光 返校只能待三天又让人有点小遗憾呐 就想着趁还在家这几天 xff0c
  • ILI9341的使用之【四】RGB接口操作详解

    ILI9341的使用之 一 TFT LCD原理 xff08 转载 xff09 ILI9341的使用之 二 ILI9341介绍 ILI9341的使用之 三 ILI9341系统通信接口模式操作详解 ILI9341的使用之 四 RGB接口操作详解
  • 基于ESP32的开发板的选型介绍

    由于最近重新选择单片机的开发平台 之前用过ESP8266模组和基于8266的NodeMCU开发板 xff0c 而且使用的经历还是很愉快的 因此这次很自然的想到了去找看看是否有基于ESP32的NodeMCU开发板 xff0c 结果没让人失望
  • ESP-IDF的vscode环境编译时出现“fatal error: nvs.h: No such file or directory”的解决方法

    目录 前言ESP IDF编程指南原文解读关于ESP IDF的组件管理模式示例项目的结构层次顶层项目CMakeLists txt文件的解释必要部分可选的项目变量 组件 CMakeLists 文件的解读最小组件 CMakeLists 文件 组件
  • 【ESP32+freeRTOS学习笔记-开篇前言】

    目录 前言的前言RTOS的选择开发与实践环境参考资料笔记的形式专题文章的链接 持续更新中 前言的前言 单片机的开发 xff0c 也有两年多了 xff0c 之前一直是做一些简单应用 xff0c 因此以裸机开发的方式为主 虽然裸机开发简单 xf
  • 【ESP32+freeRTOS学习笔记-(一)freeRTOS介绍】

    目录 FreeRTOS基本情况FreeRTOS的特色发行版的目录结构与文件说明原生程序的下载与目录结构FreeRTOS的主要文件说明头文件说明关于FreeRTOSConfig h的说明 主要的数据类型说明重要数据类型 TickType t重
  • 【ESP32+freeRTOS学习笔记-(二)FreeRTOS运行机制】

    目录 实时操作系统RTOS主要功能RTOS工作概况基本概念 FreeRTOS 心跳 任务优先级调度算法其它资源与概念 FreeRTOS运行机制描述总结 实时操作系统 实时操作系统RTOS是保证在一定时间限制内完成特定功能的操作系统 实时操作
  • 【ESP32+freeRTOS学习笔记-(三)任务】

    目录 1 任务相关基本概念1 1 任务函数原型1 2 任务句柄TaskHandle t 及任务控制块TCB t1 3 任务状态1 4 优先级 Task Priorities 2 创建任务2 1 xTaskCreate2 2 xTaskCre
  • 【ESP32+freeRTOS学习笔记-(四)任务调度机制】

    目录 1 什么是任务的调度机制1 1 概念1 2 三种算法1 3 决定算法的宏 2 基本词条解释3 调度算法解释3 1 具有时间片的优先级抢先调度 Prioritized Pre emptive Scheduling with Time S
  • 【ESP32+freeRTOS学习笔记-(五)队列Queue】

    目录 1 什么是队列Queue2 队列的多任务特性2 1 多任务的访问 xff1a 2 2 队列读取阻塞 xff1a 2 3 写队列阻塞 xff1a 2 4 阻塞于多个队列 xff1a 3 队列的使用3 1 创建队列 The xQueueC
  • 【ESP32+freeRTOS学习笔记-(六)软件定时器】

    目录 1 软件定时器概念2 软件定时器的运行机制2 1 组成2 2 创建2 3 运行 3 软件定时器的属性和状态3 1 定时器的周期3 2 定时器的类型3 3 定时器的状态 4 软件定时器的回调函数原型5 定时器的使用5 1 创建定时器xT
  • 图说粒子群优化算法(PSO)附源码

    前面说了ACO xff0c 这次就来说下PSO吧 目录 一 初识PSO xff1a 这是个啥玩意 xff1f 二 PSO的数学原理 三 MATLAB代码 一 初识PSO xff1a 这是个啥玩意 xff1f 粒子群优化算法是模拟鸟群觅食行为
  • 【ESP32+freeRTOS学习笔记-(七)中断管理】

    目录 1 概述2 在ISR中使用FreeRTOS中专用的API2 1 独立的用于ISR中的API2 2 关于xHigherPriorityTaskWoken 参数的初步理解 3 延迟中断处理的方法 将中断中的处理推迟到任务中去4 方法一 x