FreeRTOS的中断管理

server/2024/10/22 15:11:47/

事件

嵌入式实时系统必须对源自环境的事件采取行动。例如,到达以太网外围设备的数据包(事件)可能需要传递到TCP/IP堆栈进行处理(动作)。非普通系统必须为来自多个来源的事件提供服务,所有这些事件都有不同的处理开销和响应时间要求。在每种情况下,都必须判断最佳的事件处理实施策略:
1.应该如何检测事件?通常使用中断,但也可以轮询输入。
2.使用中断时,应在中断服务例程(ISR)内部执行多少处理,在外部执行多少处理?通常希望每个ISR尽可能短。
3.事件如何传达给主(非ISR)代码,以及如何构建此代码以最好地适应潜在异步事件的处理?

FreeRTOS不会对应用程序设计器强加任何特定的事件处理策略,但确实提供了允许以简单和可维护的方式实现所选策略的功能。

区分任务的优先级和中断的优先级非常重要:
 任务是一种与运行FreeRTOS的硬件无关的软件功能。任务的优先级由应用程序编写者在软件中分配,软件算法(调度器)决定哪个任务将处于运行状态。
 虽然是用软件编写的,但中断服务例程是一种硬件功能,因为硬件控制着哪个中断服务例程将运行,以及何时运行。
任务仅在没有ISR运行时运行,因此最低优先级的中断将中断最高优先级的任务,任务无法抢先ISR。
FreeRTOS运行的所有架构都能够处理中断,但与中断输入和中断优先级分配相关的细节因架构而异。

从ISR中使用FreeRTOS API

中断安全API

通常需要使用中断服务例程(ISR)中的FreeRTOS API函数提供的功能,但许多FreeRTOS API函数执行的操作在ISR内部无效,其中最值得注意的是将调用API函数的任务置于阻塞状态;如果API函数是从ISR调用的,那么它不是从任务调用的,因此没有可以置于阻塞状态的调用任务。FreeRTOS通过提供一些API函数的两个版本来解决这个问题;一个版本供任务使用,一个版本由ISR使用。用于ISR的函数名称后附了“FromISR”。

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

使用独立中断安全API的好处

在中断中使用单独的API可以提高任务代码的效率,提高ISR代码的效率并简化中断条目。要了解原因,请考虑另一种解决方案,即提供每个API函数的单一版本,这些函数可以从任务和ISR中调用。如果可以从任务和ISR调用相同版本的API函数,则:

API函数将需要额外的逻辑来确定它们是从任务还是从ISR调用的。额外的逻辑将通过函数引入新的路径,使函数更长、更复杂、更难测试。
 当从任务调用函数时,某些API函数参数将过时,而当从ISR调用函数时其他参数将过时。
 每个FreeRTOS端口都需要提供一种确定执行上下文(任务或ISR)的机制。
 不容易确定执行上下文(任务或ISR)的架构将需要额外的、浪费的、更复杂的使用和非标准的中断条目代码,这些代码允许软件提供执行上下文。

使用独立中断安全API的缺点

拥有两个版本的某些API函数可以提高任务和ISR的效率,但会带来新的问题;有时需要从任务和ISR调用一个不属于FreeRTOS API的函数,但使用FreeRTOS API。
这通常只在集成第三方代码时出现问题,因为这是软件设计不受应用程序编写者控制的唯一一次。如果这确实成为一个问题,那么可以使用以下技术之一来克服这个问题:

1.将中断处理推迟到任务1,因此API函数仅从任务的上下文中调用。
2.如果您使用的是支持中断嵌套的FreeRTOS端口,请使用以“FromISR”结尾的API函数版本,因为该版本可以从任务和ISR调用(相反,不以“FromISR”结尾的API函数不能从ISR调用)。
3.第三方代码通常包括RTOS抽象层,可以实现该层来测试调用函数的上下文(任务或中断),然后调用适合于该上下文的API函数。

xHigherPriorityTaskWoken 参数

本节介绍xHigherPriorityTaskWoken参数的概念。
如果上下文切换是由中断执行的,那么在中断退出时运行的任务可能与在中断进入时正在运行的任务不同——中断将中断一个任务,但返回到另一个任务。
某些FreeRTOS API函数可以将任务从阻止状态移动到就绪状态。
在xQueueSendToBack()等函数中已经看到了这一点,如果有任务处于“已阻塞”状态等待主题队列上的数据可用,则该函数将取消阻塞任务。

如果被FreeRTOS API函数解除阻止的任务的优先级高于处于运行状态的任务的优先权,则根据FreeRTOS调度策略,应该切换到优先级更高的任务。实际切换到更高优先级任务的时间取决于调用API函数的上下文:

如果API函数是从任务调用的
如果在FreeRTOSConfig.h中将configUSE_PREEMPTION设置为1,则在API函数退出之前,在API函数-so内自动切换到更高优先级的任务。

如果API函数是从中断调用的
在中断期间,不会自动切换到更高优先级的任务。相反,设置一个变量来通知应用程序编写者应该执行上下文切换。
中断安全API函数(以“FromISR”结尾的函数)有一个名为pxHigherPriorityTaskWoken的指针参数,用于此目的。

如果要执行上下文切换,那么中断安全API函数会将*pxHigherPriorityTaskWoken设置为pdTRUE。为了能够检测到这种情况的发生,pxHigherPriorityTaskWoken指向的变量在首次使用之前必须初始化为pdFALSE。

如果应用程序编写器选择不向ISR请求上下文切换,则较高优先级的任务将保持在就绪状态,直到下一次调度器运行为止——在最坏的情况下,这将是在下一个滴答tick期间。

FreeRTOS API函数只能将*pxHighPriorityTaskWoken设置为pdTRUE。如果一个ISR调用多个FreeRTOS API函数,那么在每个API函数调用中都可以传递相同的变量作为pxHigherPriorityTaskWoken参数,并且该变量只需要在首次使用之前初始化为pdFALSE。

在API函数的中断安全版本中,上下文切换不会自动发生,原因如下:
1.避免不必要的上下文切换
在任务需要执行任何处理之前,中断可能会执行多次。例如,考虑一个场景,其中任务处理由中断驱动的UART接收的字符串;UART ISR在每次接收到字符时切换到任务是浪费的,因为任务只有在接收到完整字符串后才能执行处理。

2.执行顺序的控制
中断可能偶尔发生,并且发生在不可预测的时间。FreeRTOS专家用户可能希望暂时避免在应用程序的特定点意外切换到不同的任务,尽管这也可以通过使用FreeRTOS调度器锁定机制来实现

3.便携性
这是可以在所有FreeRTOS端口上使用的最简单的机制。

4.效率
针对较小处理器架构的端口只允许在ISR的最后请求上下文切换,而消除这一限制将需要额外的、更复杂的代码。它还允许在同一个ISR内对FreeRTOS API函数进行多个调用,而不会在同一ISR内生成多个上下文切换请求。

5.RTOS滴答中断中的执行
可以将应用程序代码添加到RTOS滴答中断中。在滴答中断内尝试上下文切换的结果取决于使用的FreeRTOS端口。充其量,这将导致对调度器的不必要调用。

pxHigherPriorityTaskWoken参数的使用是可选的。如果不需要,则将pxHigherPriorityTaskWoken设置为NULL。

portYIELD_FROM_ISR()和portEND_SWITCHING_ISR()宏

本节介绍用于从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参数可以直接用作portYIELD_FROM_ISR()调用中的参数。
如果portYIELD_FROM_ISR()xHigherPriorityTaskWoken参数为pdFALSE(零),则不会请求上下文切换,宏也不会生效。如果portYIELD_FROM_ISR()xHigherPriorityTaskWoken参数不是pdFALSE,则请求上下文切换,处于运行状态的任务可能会发生变化。即使在执行中断时处于运行状态的任务发生了变化,中断也将始终返回到处于运行状态中的任务。

大多数FreeRTOS端口允许在ISR内的任何地方调用portYIELD_FROM_ISR()。一些FreeRTOS端口(主要是用于较小架构的端口)只允许在ISR的最后调用portYIELD_FROM_ISR()。

推迟中断处理

通常认为,保持ISR尽可能短是最佳做法。原因包括:
 即使任务被分配了很高的优先级,它们也只会在硬件没有中断的情况下运行。
 ISR会中断(增加“抖动”)任务的开始时间和执行时间。
 根据FreeRTOS运行的架构,在ISR执行时,可能无法接受任何新的中断,或者至少是新中断的子集。
 应用程序编写者需要考虑任务和ISR同时访问变量、外围设备和内存缓冲区等资源的后果,并加以防范。
 一些FreeRTOS端口允许中断嵌套,但中断嵌套会增加复杂性并降低可预测性。中断时间越短,嵌套的可能性就越小。

中断服务程序必须记录中断的原因,并清除中断。
中断所需的任何其他处理通常都可以在任务中执行,从而允许中断服务例程尽可能快地退出。这被称为“延迟中断处理”,因为中断所需的处理从ISR“延迟”到任务。

将中断处理延迟到任务还允许应用程序编写器相对于应用程序中的其他任务对处理进行优先级排序,并使用所有FreeRTOS API函数。

如果延迟中断处理的任务的优先级高于任何其他任务的优先级,则处理将立即执行,就像处理是在ISR本身中执行的一样。此场景如图48所示,其中任务1是一个正常的应用程序任务,任务2是中断处理被推迟的任务。

在这里插入图片描述
在图48中,中断处理从时间t2开始,实际上在时间t4结束,但ISR中只花费了时间t2和t3之间的时间段。如果没有使用延迟中断处理,那么时间t2和t4之间的整个时间段都将用于ISR。
对于何时最好执行ISR中断所需的所有处理,以及何时最好将部分处理推迟到任务,没有绝对的规则。在以下情况下,将处理推迟到任务最有用:

中断所需的处理并非微不足道。例如,如果中断只是存储模数转换的结果,那么几乎可以肯定这最好在ISR内部执行,但如果转换的结果也必须通过软件过滤器,那么最好在任务中执行过滤器。
 中断处理执行ISR内部无法执行的操作很方便,例如写入控制台或分配内存。
 中断处理不是确定性的,这意味着事先不知道处理需要多长时间。

用于同步的二进制信号量

中断安全版本的二进制信号API可用于在每次发生特定中断时取消阻塞任务,从而有效地将任务与中断同步。这允许在同步任务中实现大部分中断事件处理,只有非常快速和短暂的部分直接留在ISR中。二进制信号量用于将中断处理“推迟”到任务

如图48所示,如果中断处理对时间特别关键,则可以设置延迟处理任务的优先级,以确保该任务始终优先于系统中的其他任务。然后,ISR可以被实现为包括对portYIELD_FROM_ISR()的调用,以确保ISR直接返回到中断处理被推迟的任务。这具有确保整个事件处理的效果
在时间上连续执行(没有中断),就像它都是在ISR本身中实现的一样。图49重复了图48所示的场景,但更新了文本,以描述如何使用信号量控制延迟处理任务的执行。

在这里插入图片描述

“获取信号量”和“释放信号量”是根据其使用场景具有不同含义的概念。在这种中断同步场景中,二进制信号量在概念上可以被视为长度为1的队列。队列在任何时候最多只能包含一个项目,因此总是空的或满的(因此是二进制的)。
通过调用xSemaphoreTake(),中断处理被推迟的任务有效地尝试以块时间从队列中读取,导致任务进入阻塞状态
如果队列为空。当事件发生时,ISR使用xSemaphoreGiveFromISR()函数将一个令牌(信号量)放入队列,使队列满。这将导致任务退出“阻塞”状态并删除令牌,使队列再次为空。
当任务完成处理后,它再次尝试从队列中读取,发现队列为空,重新进入“阻塞”状态以等待下一个事件。该序列如图50所示
图50显示了中断“释放”信号量,即使它没有首先“获取”它,以及任务“获取”信号量但从不返回。这就是为什么该场景被描述为在概念上类似于向队列写入和从队列读取。它经常引起混淆,因为它不遵循与其他信号量使用场景相同的规则,在这些场景中,接收信号量的任务必须始终将其返回。

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

xSemaphoreCreateBinary() API函数

所有不同类型的FreeRTOS信号量的句柄都存储在SemaphoreHandle_t类型的变量中。
在使用信号量之前,必须先创建它。要创建二进制信号量,请使用xSemaphoreCreateBinary()API函数

SemaphoreHandle_t xSemaphoreCreateBinary( void );

返回值
如果返回NULL,则无法创建信号量,因为FreeRTOS没有足够的堆内存来分配信号量数据结构。
返回的非NULL值表示信号量已成功创建。返回的值应作为创建的信号量的句柄存储。

xSemaphoreTake()API函数

“获取”信号量意味着“获取”或“接收”信号量。信号量只有在可用时才能使用。
除了递归互斥之外,所有不同类型的FreeRTOS信号量都可以使用xSemaphoreTake()函数“获取”。
xSemaphoreTake()不能从中断服务例程中使用。

BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

xSemaphore
被“占用”的信号量。
信号量由SemaphoreHandle_t类型的变量引用。必须显式创建它才能使用。

xTicksToWait
如果信号量不可用,任务应保持在“阻塞”状态以等待信号量的最长时间。

如果xTicksToWait为零,则xSemaphoreTake()将在以下情况下立即返回
信号量不可用。

块时间以滴答周期指定,因此它表示的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为以刻度为单位的时间。

如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(没有超时)。

返回值
有两种可能的返回值:
1.pdPASS
只有当xSemaphoreTake()的调用成功获取信号量时,才会返回pdPASS。
如果指定了块时间(xTicksToWait不为零),则如果信号量不是立即可用的,但信号量在块时间到期之前可用,则调用任务可能会被置于“已阻止”状态以等待信号量。

2.pdFALSE
信号量不可用。
如果指定了块时间(xTicksToWait不为零),则调用任务将被置于“已阻止”状态以等待信号量可用,但块时间在此之前已过期。

xSemaphoreGiveFromISR() API 函数
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken );

xSemaphore
信号量被“给定”。
信号量由SemaphoreHandle_t类型的变量引用,在使用之前必须显式创建。

pxHigherPriorityTaskWoken

一个信号量可能会有一个或多个任务被阻塞,等待信号量可用。调用xSemaphoreGiveFromISR()可以使信号量可用,从而导致等待信号量的任务离开阻塞状态。如果调用 xSemaphoreGiveFromISR()会使任务离开阻塞状态,并且未阻塞的任务的优先级高于当前正在执行的任务(被中断的任务),然后,在内部,xSemaphoneGiveFromISR()会将*pxHigherPriorityTaskWoken设置为pdTRUE。

如果xSemaphoreGiveFromISR()将此值设置为pdTRUE,则通常应在退出中断之前执行上下文切换。这将确保中断直接返回到最高优先级的就绪状态任务。

返回值
有两种可能的返回值:
1.pdPASS
只有当xSemaphoreGiveFromISR()的调用成功时,才会返回pdPASS。
2.pdFAIL
如果信号量已经可用,则无法给出,xSemaphoreGiveFromISR()将返回pdFAIL。

示例16。使用二进制信号量将任务与中断同步
此示例使用二进制信号量来解除对中断服务例程的阻塞,从而有效地将任务与中断同步。
一个简单的周期性任务用于每500毫秒生成一个软件中断。由于在某些目标环境中挂接到真实中断的复杂性,因此使用软件中断是为了方便。清单92显示了周期性任务的实现。请注意,该任务在生成中断之前和之后都会打印出一个字符串。
这允许在执行示例时产生的输出中观察执行顺序。

在这里插入图片描述
清单93显示了延迟中断处理的任务的实现——通过使用二进制信号量与软件中断同步的任务。同样,在任务的每次迭代中都会打印出一个字符串,因此从执行示例时产生的输出中可以明显看出任务和中断执行的顺序。

应该注意的是,虽然清单93中所示的代码对于由软件生成中断的示例16来说是足够的,但对于由硬件外围设备生成中断的场景来说是不够的。以下小节描述了如何更改代码的结构,使其适合与硬件生成的中断一起使用。

在这里插入图片描述
清单94显示了ISR。除了“释放”信号量来解除对延迟中断处理的任务的阻止外,这几乎没有什么作用。
请注意xHigherPriorityTaskWoken变量的使用方式。在调用xSemaphoreGiveFromISR()之前,它被设置为pdFALSE,然后在调用portYIELD_FROM_ISR()时用作参数。如果xHigherPriorityTaskWoken等于pdTRUE,则将在portYIELD_FROM_ISR()宏内请求上下文切换。

与FreeRTOS运行的大多数架构不同,FreeRTOS Windows端口需要ISR返回值。Windows端口提供的portYIELD_FROM_ISR()宏的实现包括return语句,因此清单94没有显示显式返回的值。

在这里插入图片描述
main()函数创建二进制信号量,创建任务,安装中断处理程序,并启动调度程序。实现如清单95所示。
在这里插入图片描述
示例16产生了图51所示的输出。正如预期的那样,vHandlerTask()在中断生成后立即进入运行状态,因此任务的输出会拆分周期性任务产生的输出。图52提供了进一步的解释。

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

改进实施例16中使用的任务的执行

示例16使用二进制信号量将任务与中断同步。执行顺序如下:
1.中断发生了。
2.ISR执行并“释放”信号量以解锁任务。
3.ISR后立即执行的任务,并“获取”信号量。
4.任务处理了事件,然后再次尝试“获取”信号量——由于信号量尚不可用(尚未发生另一个中断),因此进入“阻塞”状态。

仅当中断以相对较低的频率发生时,示例16中使用的任务结构才足够。要理解原因,请考虑如果在任务完成对第一个中断的处理之前发生了第二个中断,然后是第三个中断,会发生什么:

当第二个ISR执行时,信号量将为空,因此ISR将给出信号量,任务将在完成处理第一个事件后立即处理第二个事件。该场景如图53所示。

当第三个ISR执行时,信号量已经可用,防止ISR再次发出信号量,因此任务不会知道第三个事件已经发生。该场景如图54所示。

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

在这里插入图片描述在这里插入图片描述
示例16中使用的延迟中断处理任务(如清单93所示)的结构使其在每次调用xSaphoreTake()之间只处理一个事件。这对于示例16来说是足够的,因为生成事件的中断是由软件触发的,并且发生在可预测的时间。在实际应用中,中断是由硬件产生的,并且发生在不可预测的时间。因此,为了尽量减少错过中断的机会,必须对延迟中断处理任务进行结构化,使其在每次调用xSaphoreTake()1之间处理所有已经可用的事件。
清单96演示了这一点,它显示了如何构造UART的延迟中断处理程序。在清单96中,假设UART在每次接收到字符时都会生成一个接收中断,并且UART将接收到的字符放入硬件FIFO(硬件缓冲区)。

示例16中使用的延迟中断处理任务还有另一个弱点;它在调用xSemaphoreTake()时没有使用超时。相反,任务将portMAX_DELAY作为xSemaphoreTake()xTicksToWait参数传递,这导致任务无限期地等待(没有超时)信号量可用。示例代码中经常使用不确定超时,因为它们的使用简化了示例的结构,因此
使示例更易于理解。然而,在实际应用程序中,无限期超时通常是不好的做法,因为它们使从错误中恢复变得困难。例如,考虑一个任务正在等待中断发出信号量,但硬件中的错误状态阻止了中断的生成:

如果任务正在等待而没有超时,它将不知道错误状态,并将永远等待。
 如果任务正在等待超时,则xSemaphoreTake()将在超时到期时返回pdFAIL,然后任务可以在下次执行时检测并清除错误。清单96也演示了这个场景。

在这里插入图片描述

计数信号量

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

每次“给定”一个计数信号量时,都会使用其队列中的另一个空间。队列中的项目数是信号量的“计数”值。
计数信号量通常用于两件事:

1.计数事件
在这种情况下,每次发生事件时,事件处理程序都会“释放”一个信号量——导致信号量的计数值在每次“释放”时递增。任务每次处理事件时都会“获取”一个信号量,从而导致信号量的计数值在每次“获取”时递减。计数值是已发生的事件数与已处理的事件数之间的差值。该机制如图55所示。

用于计数事件的计数信号量的初始计数值为零。

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

创建用于管理资源的计数信号量,使其初始计数值等于可用资源的数量。第7章介绍了使用信号量来管理资源。

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

xSemaphoreCreateCounting()API函数

所有不同类型的FreeRTOS信号量的句柄都存储在SemaphoreHandle_t类型的变量中。

在使用信号量之前,必须先创建它。要创建计数信号量,请使用xSemaphoreCreateCount()API函数。

SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount );

uxMaxCount
信号量计数的最大值。为了继续队列类比,uxMaxCount值实际上是队列的长度。
当信号量用于计数或锁存事件时,uxMaxCount是可以锁存的最大事件数。
当信号量用于管理对资源集合的访问时,uxMaxCount应设置为可用资源的总数。

uxInitialCount
信号量创建后的初始计数值。
当信号量用于计数或锁存事件时,uxInitialCount应设置为零,因为据推测,在创建信号量时,还没有发生任何事件。
当信号量用于管理对资源集合的访问时,uxInitialCount应设置为等于uxMaxCount——因为,据推测,当创建信号量时,所有资源都是可用的。

返回值
如果返回NULL,则无法创建信号量,因为FreeRTOS没有足够的堆内存来分配信号量数据结构。第2章提供了有关堆内存管理的更多信息。
返回的非NULL值表示信号量已成功创建。返回的值应作为创建的信号量的句柄存储。

例17。使用计数信号量将任务与中断同步
示例17通过使用计数信号量代替二进制信号量改进了示例16的实现。main()已更改为包含对xSaphoreCreateCounting()的调用,以代替对xSaphoneCreateBinary()的呼叫。新的API调用如清单98所示。

在这里插入图片描述
为了模拟高频发生的多个事件,中断服务例程被更改为在每个中断中多次“释放”信号量。每个事件都被锁在信号量的计数值中。修改后的中断服务例程如清单99所示。

在这里插入图片描述
所有其他功能与示例16中使用的功能保持不变。
执行示例17时产生的输出如图56所示。可以看出,每次产生中断时,延迟中断处理的任务都会处理所有三个[模拟]事件。事件被锁存到信号量的计数值中,允许任务依次处理它们。

在这里插入图片描述

将工作推迟到RTOS守护进程任务

到目前为止,所展示的延迟中断处理示例要求应用程序编写者为每个使用延迟处理技术的中断创建一个任务。还可以使用xTimerEndFunctionCallFromISR()API函数将中断处理推迟到RTOS守护进程任务,从而无需为每个中断创建单独的任务。将中断处理推迟到守护进程任务称为“集中式延迟中断处理”。

第5章描述了与软件定时器相关的FreeRTOS API函数如何将命令发送到定时器命令队列上的守护进程任务。xTimerEndFunctionCall()和xTimerendFunctionCallFromISR()API函数使用相同的计时器命令队列向守护进程任务发送“执行函数”命令。发送给守护进程任务的函数是
然后在守护进程任务的上下文中执行。

集中式延迟中断处理的优点包括:
 降低资源使用率
它消除了为每个延迟中断创建单独任务的需要。
 简化的用户模型
延迟中断处理函数是一个标准的C函数。

集中式延迟中断处理的缺点包括:
 灵活性较低
无法单独设置每个延迟中断处理任务的优先级。
每个延迟中断处理函数都以守护进程任务的优先级执行。守护进程任务的优先级由FreeRTOSConfig.h中的configTIMER_task_priority编译时配置常量设置。

较少确定性
xTimerPendFunctionCallFromISR()将命令发送到计时器命令队列的后面。计时器命令队列中已有的命令将在xTimerPendFunctionCallFromISR()发送到队列的“execute function”命令之前由守护进程任务处理。

不同的中断具有不同的时序约束,因此在同一应用程序中使用这两种延迟中断处理的方法是很常见的。

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

BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend,
void *pvParameter1,
uint32_t ulParameter2,
BaseType_t *pxHigherPriorityTaskWoken );
void vPendableFunction( void *pvParameter1, uint32_t ulParameter2 );

xFunctionToPend
指向将在守护进程任务中执行的函数的指针(实际上,只是函数名)。

pvParameter1
将作为函数的pvParameter1参数传递给守护进程任务执行的函数的值。
该参数具有void类型,允许它用于传递任何数据类型。例如,整数类型可以直接转换为void,或者void*可以用来指向一个结构。

ulParameter2
将作为函数的ulParameter2参数传递给守护进程任务执行的函数的值。

pxHigherPriorityTaskWoken
xTimerPendFunctionCallFromISR()写入计时器命令队列。如果RTOS守护进程任务处于“阻止”状态,等待计时器命令队列上的数据可用,则写入计时器命令队列将导致守护进程任务离开“阻止”模式。如果守护进程任务的优先级高于当前正在执行的任务(被中断的任务)的优先级,则xTimerPendFunctionCallFromISR()将在内部将*pxHigherPriorityTaskWoken设置为pdTRUE。
如果xTimerPendFunctionCallFromISR()将此值设置为pdTRUE,则必须在退出中断之前执行上下文切换。这将确保中断直接返回到守护进程任务,因为守护进程任务将是最高优先级的就绪状态任务。

返回值
有两种可能的返回值:
1.pdPASS
如果“execute function”命令被写入计时器命令队列,则将返回pdPASS。
2.pdFAIL
如果由于计时器命令队列已满而无法将“execute function”命令写入计时器命令队列,则将返回pdFAIL。

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

在这里插入图片描述
vDeferredHandlingFunction()的实现如清单103所示。它打印出一个固定字符串及其ulParameter2参数的值。
vDeferredHandlingFunction()必须具有清单101所示的原型,尽管在这个例子中,实际上只使用了它的一个参数。

在这里插入图片描述

示例18使用的main()函数如清单104所示。它比示例16中使用的main()函数更简单,因为它既不创建信号量也不创建任务来执行延迟中断处理。

vPeriodicTask()是定期生成软件中断的任务。它的创建优先级低于守护进程任务的优先级,以确保一旦守护进程任务离开“阻塞”状态,它就会被守护进程任务抢占。

在这里插入图片描述

示例18产生了图57所示的输出。守护进程任务的优先级高于生成软件中断的任务的优先级,因此一旦生成中断,守护进程任务就会执行vDeferredHandlingFunction()。这导致vDeferredHandlingFunction()输出的消息出现在周期性任务输出的两条消息之间,就像使用信号量来解除阻止专用延迟中断处理任务时一样。图58提供了进一步的解释。

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

在中断服务例程中使用队列

二进制和计数信号量用于传递事件。队列用于传递事件和传输数据。
xQueueSendToFrontFromISR是xQueueSendToFront的版本,可在中断服务例程中安全使用,xQueueSendTo BackFromISR是xQueue SendToBack的版本,可以在中断服务例行程序中安全使用。

xQueueSendToFrontFromISR()和xQueueSendToBackFromISR)API函数
BaseType_t xQueueSendToFrontFromISR( QueueHandle_t xQueue,
void *pvItemToQueue
BaseType_t *pxHigherPriorityTaskWoken
);
BaseType_t xQueueSendToBackFromISR( QueueHandle_t xQueue,
void *pvItemToQueue
BaseType_t *pxHigherPriorityTaskWoken
);

xQueueSendFromISR和xQueueSendToBackFromISR在功能上是等效的。

xQueue
数据被发送(写入)到的队列的句柄。队列句柄将从用于创建队列的xQueueCreate()调用中返回。

pvItemToQueue
指向将被复制到队列中的数据的指针。
队列可以容纳的每个项目的大小是在创建队列时设置的,因此这许多字节将从pvItemToQueue复制到队列存储区域。

pxHigherPriorityTaskWoken
单个队列上可能有一个或多个任务被阻塞,等待数据可用。调用xQueueSendToFrontFromISR()或
xQueueSendToBackFromISR()可以使数据可用,从而使此类任务离开“阻止”状态。如果调用API函数导致任务离开阻塞状态,并且未阻塞的任务的优先级高于当前正在执行的任务(被中断的任务),则在内部,
API函数将*pxHigherPriorityTaskWoken设置为pdTRUE。

如果xQueueSendToFrontFromISR()或xQueueSendToBackFromISR()将此值设置为pdTRUE,则应在中断之前执行上下文切换
退出。这将确保中断直接返回到最高优先级的就绪状态任务。

返回值
有两种可能的返回值:
1.pdPASS
只有当数据已成功发送到队列时,才会返回pdPASS。
2.错误队列_满
如果由于队列已满而无法将数据发送到队列,则返回errQUEUE_FULL。

使用ISR队列时的注意事项

队列提供了一种将数据从中断传递到任务的简单方便的方法,但如果数据以高频率到达,则使用队列效率不高。

FreeRTOS下载中的许多演示应用程序都包含一个简单的UART驱动程序,该驱动程序使用队列将字符从UART的接收ISR中传递出去。在这些演示中,使用队列有两个原因:一是演示ISR使用队列,二是故意加载系统以测试FreeRTOS端口。以这种方式使用队列的ISR绝对不是为了表示高效的设计,除非数据到达速度变慢,否则建议生产代码不要复制该技术。适用于生产代码的更有效的技术包括:

使用直接内存访问(DMA)硬件接收和缓冲字符。这种方法几乎没有软件开销。然后,可以使用直接到任务通知1来解锁仅在检测到传输中断后才处理缓冲区的任务。
 将每个接收到的字符复制到线程安全RAM缓冲区2中。同样,可以使用直接到任务通知来取消阻止在收到完整消息后或检测到传输中断后将处理缓冲区的任务。
 直接在ISR中处理接收到的字符,然后使用队列将处理数据的结果(而不是原始数据)发送给任务。图34之前已经证明了这一点。

例19。从中断中在队列上发送和接收
此示例演示了在同一中断中使用xQueueSendToBackFromISR()和xQueueReceiveFromISR()。为了方便起见,中断是由软件生成的。

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

在这里插入图片描述

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

static uint32_t ulExampleInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t ulReceivedNumber;
/* The strings are declared static const to ensure they are not allocated on the
interrupt service routine's stack, and so exist even when the interrupt service
routine is not executing. */
static const char *pcStrings[] =
{
"String 0\r\n",
"String 1\r\n",
"String 2\r\n",
"String 3\r\n"
};
/* As always, xHigherPriorityTaskWoken is initialized to pdFALSE to be able to
detect it getting set to pdTRUE inside an interrupt safe API function. Note that
as an interrupt safe API function can only set xHigherPriorityTaskWoken to
pdTRUE, it is safe to use the same xHigherPriorityTaskWoken variable in both
the call to xQueueReceiveFromISR() and the call to xQueueSendToBackFromISR(). */
xHigherPriorityTaskWoken = pdFALSE;
/* Read from the queue until the queue is empty. */
while( xQueueReceiveFromISR( xIntegerQueue,
&ulReceivedNumber,
&xHigherPriorityTaskWoken ) != errQUEUE_EMPTY )
{
/* Truncate the received value to the last two bits (values 0 to 3
inclusive), then use the truncated value as an index into the pcStrings[]
array to select a string (char *) to send on the other queue. */
ulReceivedNumber &= 0x03;
xQueueSendToBackFromISR( xStringQueue,
&pcStrings[ ulReceivedNumber ],
&xHigherPriorityTaskWoken );
}
/* If receiving from xIntegerQueue caused a task to leave the Blocked state, and
if the priority of the task that left the Blocked state is higher than the
priority of the task in the Running state, then xHigherPriorityTaskWoken will
have been set to pdTRUE inside xQueueReceiveFromISR().
If sending to xStringQueue caused a task to leave the Blocked state, and if the
priority of the task that left the Blocked state is higher than the priority of
the task in the Running state, then xHigherPriorityTaskWoken will have been set
to pdTRUE inside xQueueSendToBackFromISR().
xHigherPriorityTaskWoken is used as the parameter to portYIELD_FROM_ISR(). If
xHigherPriorityTaskWoken equals pdTRUE then calling portYIELD_FROM_ISR() will
request a context switch. If xHigherPriorityTaskWoken is still pdFALSE then
calling portYIELD_FROM_ISR() will have no effect.
The implementation of portYIELD_FROM_ISR() used by the Windows port includes a
return statement, which is why this function does not explicitly return a
value. */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

从中断服务例程接收字符指针的任务会阻塞队列,直到消息到达,并在收到每个字符串时打印出来。它的实现如清单109所示。

在这里插入图片描述
正常情况下,main()在启动调度程序之前创建所需的队列和任务。其实现如清单110所示。

在这里插入图片描述
执行示例19时产生的输出如图59所示。可以看出,中断接收所有五个整数,并产生五个字符串作为响应。图60给出了更多解释。

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


http://www.ppmy.cn/server/133933.html

相关文章

React 进阶阶段学习计划

React 进阶阶段学习计划 目标 掌握自定义Hooks的创建和使用。深入理解上下文(Context)和Redux的高级用法。学会服务端渲染(SSR)。深入探讨性能优化技巧。 学习内容 自定义Hooks 创建和使用自定义Hooks 自定义Hooks&#xff1…

全天候风险平价策略下载 | Quantlab AI v0.2:OpenAI的Swarm适配国内大模型(附python代码下载)

原创内容第679篇,专注量化投资、个人成长与财富自由。 今天我们来实现服务端策略下载,下载后支持在本地调试运作,及查看源代码。 通过服务器下载策略的代码: login_required def down_strategy(request, task_id: str):task m…

基于深度学习的设备异常检测与预测性维护

基于深度学习的设备异常检测与预测性维护是一项利用深度学习技术分析设备运行数据,实时检测设备运行过程中的异常情况,并预测未来可能的故障,以便提前进行维护,防止意外停机和生产中断。它在工业领域应用广泛,特别是在…

SpringCloud-持久层框架MyBatis Plus的使用与原理详解

在现代微服务架构中,SpringCloud 是一个非常流行的解决方案。而在数据库操作层面,MyBatis Plus 作为 MyBatis 的增强工具,能够简化开发,提升效率,特别是在开发企业级应用和分布式系统时尤为有用。本文将详细介绍 MyBat…

c语言基础程序——经典100道实例。

c语言基础程序——经典100道实例 001, 组无重复数字的数002,企业发放的奖金根据利润提成003,完全平方数004,判断当天是这一年的第几天005,三个数由小到大输出006,输出字母C图案007,特殊图案008&…

使用ollama本地部署qwen2并api调用

目录 一、下载ollama 二、安装qwen大模型 三、Api调用 四、尝试apifox调用 一、下载ollama Ollama 是一个开源的、本地运行的 AI 聊天模型,允许在自己的设备上运行 LLM,无需依赖云服务。它支持多种 LLM。目前Ollama library已经支持Qwen2&#xf…

ASP.NET.Web应用程序(.NET Framework)添加Swagger本地Debuge成功打开接口展示界面,发布服务器无法打开接口展示界面

前言 提示:项目使用ASP.NET.Web应用程序(.NET Framework4.6.1)创建WEB API接口供外部系统调用。本地Debug运行可支持https://localhost:44374/swagger/打开界面展示操作,发布使用Release部署服务器时打开界面展示失败。 一、Swag…

一文掌握Cephadm部署Ceph存储集群

📚 博客主页: StevenZeng学堂 🎉 本文专栏: 一文读懂Kubernetes一文读懂Harbor云原生安全实战指南云原生存储实践指南 ❤️ 摘要:随着企业数据量的增长和存储需求的复杂化,Ceph因其高可扩展性和灵活性,能…