二值信号量可以在某个特殊的中断发生时,让任务解除阻塞,相当于让任务与中断同步。这样就可以让中断事件处理量大的工作在同步任务中完成,中断服务例程(ISR)中只是快速处理少部份工作。如此,中断处理可以说是被”推迟(deferred)”到一个”处理handler)”任务。
延迟处理任务对一个信号量进行带阻塞性质的”take”调用,意思是进入阻塞态以等待事件发生。当事件发生后,ISR 对同一个信号量进行”give”操作,使得延迟处理任务解除阻塞,从而事件在延迟处理任务中得到相应的处理。“获取(Taking,带走,按通常的说法译为获取)”和”给出(Giving)”信号量从概念上讲,在不同的应用场合有不同的含义。在经典的信号量术语中,获取信号量等同于一个 P()操作,而给出信号量等同于一个 V()操作。
在这种中断同步的情形下,信号量可以看作是一个深度为 1 的队列。这个队列由于最多只能保存一个数据单元,所以其不为空则为满(所谓”二值”)。
FreeRTOS 中各种信号量的句柄都存储在 xSemaphoreHandle 类型的变量中。
在使用信号量之前,必须先创建它 。创建二值信号量使用vSemaphoreCreateBinary()API 函数
void vSemaphoreCreateBinary( xSemaphoreHandle xSemaphore )
xSemaphore 创建的信号量
需要说明的是 vSemaphoreCreateBinary()在实现上是一个宏,所以
信号量变量应当直接传入,而不是传址。本章中包含本函数调用的示
例可用于参考进行复制。
“带走(Taking)”一个信号量意为”获取(Obtain)”或”接收(Receive)”信号量。只有当信号量有效的时候才可以被获取。在经典信号量术中,xSemaphoreTake()等同于一次 P()操作。除互斥信号量(RecursiveSemaphore,直译为递归信号量,按通常的说法译为互斥信号量)外,所有类型的信号量都可以调用函数 xSemaphoreTake()来获取。但 xSemaphoreTake()不能在中断服务例程中调用,在中断中有专门的函数来使用
portBASE_TYPE xSemaphoreTake( xSemaphoreHandle xSemaphore, portTickType xTicksToWait )
xSemaphore 获取得到的信号量
信号量由定义为 xSemaphoreHandle 类型的变量引用。信号量在使
用前必须先创建。
xTicksToWait 阻塞超时时间。任务进入阻塞态以等待信号量有效的最长时间。
如果 xTicksToWait 为 0,则 xSemaphoreTake()在信号量无效时会
立即返回。
阻塞时间是以系统心跳周期为单位的,所以绝对时间取决于系统心
跳频率。常量 portTICK_RATE_MS 可以用来把心跳时间单位转换
为毫秒时间单位。
如果把 xTicksToWait 设置为 portMAX_DELAY ,并且在
FreeRTOSConig.h 中设定 INCLUDE_vTaskSuspend 为 1,那么阻
塞等待将没有超时限制。
返回值 有两个可能的返回值:
1. pdPASS
只有一种情况会返回 pdPASS,那就是成功获得信号量。
如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任务
将被转移到阻塞态以等待信号量有效。如果在超时到来前信号量变
为有效,亦可被成功获取,返回 pdPASS。
2. pdFALSE
未能获得信号量。
如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任
务将被转移到阻塞态以等待信号量有效。但直到超时信号量也没有
变为有效,所以不会获得信号量,返回 pdFALSE。
xSemaphoreGiveFromISR() API 函数
除互斥信号量外, FreeRTOS 支持的其它类型的信号量都可以通过调用xSemaphoreGiveFromISR()给出。xSemaphoreGiveFromISR()是 xSemaphoreGive()的特殊形式,专门用于中断服务例程中。
portBASE_TYPE xSemaphoreGiveFromISR( xSemaphoreHandle xSemaphore, portBASE_TYPE *pxHigherPriorityTaskWoken )
xSemaphore 给出的信号量
信号量由定义为 xSemaphoreHandle 类型的变量引用。
信号量在使用前必须先创建。
pxHigherPriorityTaskWoken 对某个信号量而言,可能有不止一个任务处于阻塞态在
等待其有效。调用 xSemaphoreGiveFromISR()会让信
号量变为有效,所以会让其中一个等待任务切出阻塞
态。如果调用 xSemaphoreGiveFromISR()使得一个任
务解除阻塞,并且这个任务的优先级高于当前任务(也就
是被中断的任务),那么 xSemaphoreGiveFromISR()会
在函数内部将 *pxHigherPriorityTaskWoken 设 为
pdTRUE。
如 果 xSemaphoreGiveFromISR() 将此值设为
pdTRUE,则在中断退出前应当进行一次上下文切换。
这样才能保证中断直接返回到就绪态任务中优先级最
高的任务中。
返回值 有两个可能的返回值:
1. pdPASS
xSemaphoreGiveFromISR()调用成功。
2. pdFAIL
如果信号量已经有效,无法给出,则返回 pdFAIL。
例 . 利用二值信号量对任务和中断进行同步
本例在中断服务例程中使用一个二值信号量让任务从阻塞态中切换出来——从效果上等同于让任务与中断进行同步。
xSemaphoreHandle xBinarySemaphore;
int main( void )
{
/* 信号量在使用前都必须先创建。本例中创建了一个二值信号量 */
vSemaphoreCreateBinary( xBinarySemaphore );
/* 安装中断服务例程 */
_dos_setvect( 0x82, vExampleInterruptHandler ); //这个是在中断调用方法是在于 Open Watcom DOS 平台的调用方法,不同平台须得采用正确的调用方法来实现
/* 检查信号量是否成功创建 */
if( xBinarySemaphore != NULL )
{
/* 创建延迟处理任务。此任务将与中断同步。延迟处理任务在创建时使用了一个较高的优先级,以保证
中断退出后会被立即执行。在本例中,为延迟处理任务赋予优先级3 */
xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );
/* 创建一个任务用于周期性产生软件中断。此任务的优先级低于延迟处理任务。每当延迟处理任务切出
阻塞态,就会抢占周期任务*/
xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );
/* Start the scheduler so the created tasks start executing. */
vTaskStartScheduler();
}
/* 如果一切正常,main()函数不会执行到这里,因为调度器已经开始运行任务。但如果程序运行到了这里,
很可能是由于系统内存不足而无法创建空闲任务。第五章会提供更多关于内存管理的信息 */
for( ;; );
}
static void vHandlerTask( void *pvParameters )
{
/* As per most tasks, this task is implemented within an infinite loop. */
for( ;; )
{
/* 使用信号量等待一个事件。信号量在调度器启动之前,也即此任务执行之前就已被创建。任务被无超
时阻塞,所以此函数调用也只会在成功获取信号量之后才会返回。此处也没有必要检测返回值 */
xSemaphoreTake( xBinarySemaphore, portMAX_DELAY ); //执行到这个地方,会进入阻塞态,等待中断函数给到信号量
/* 程序运行到这里时,事件必然已经发生。本例的事件处理只是简单地打印输出一个信息 */
vPrintString( "Handler task - Processing event.\r\n" );
}
}
//一个周期性的任务,每隔 500ms 定时产生一个软件中断:
//
static void vPeriodicTask( void *pvParameters )
{
for( ;; )
{
/* 此任务通过每500毫秒产生一个软件中断来”模拟”中断事件 */
vTaskDelay( 500 / portTICK_RATE_MS );
/* 产生中断,并在产生之前和之后输出信息,以便在执行结果中直观直出执行流程 */
vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
__asm{ int 0x82 } /* 这条语句产生中断 */
vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
}
}
static void __interrupt __far vExampleInterruptHandler( void )
{
static portBASE_TYPE xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
/* 'Give' the semaphore to unblock the task. */
xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
if( xHigherPriorityTaskWoken == pdTRUE )
{
/* 给出信号量以使得等待此信号量的任务解除阻塞。如果解出阻塞的任务的优先级高于当前任务的优先
级 – 强制进行一次任务切换,以确保中断直接返回到解出阻塞的任务(优选级更高)。
说明:在实际使用中,ISR中强制上下文切换的宏依赖于具体移植。此处调用的是基于Open Watcom DOS
移植的宏。其它平台下的移植可能有不同的语法要求。对于实际使用的平台,请参如数对应移植自带的示
例程序,以决定正确的语法和符号。
*/
portSWITCH_CONTEXT(); //切换任务,保证中断结束后去运行vHandlerTask,
}
}
/*
由于 vHandlerTask 优先级为最高,所以他会先运行,并阻塞在二值信号量的获取上;
vHandlerTask 进入阻塞后,vPeriodicTask 会周期性的去拉一个中断,导致进入 ISR;
在 ISR 中设置了信号量,导致 vHandlerTask 被解除阻塞,进入运行,抢占 vPeriodicTask;
vHandlerTask 运行完后,再次进入阻塞;
*/
1.事件计数
在这种用法中,每次事件发生时,中断服务例程都会“给出(Give)”信号量——信号量在每次被给出时其计数值加 1。延迟处理任务每处理一个任务都会”获取(Take)”一次信号量——信号量在每次被获取时其计数值减 1。信号量的计数值其实就是已发生事件的数目与已处理事件的数目之间的差值。 用于事件计数的计数信号量,在被创建时其计数值被初始化为 0。
2.资源管理
在这种用法中,信号量的计数值用于表示可用资源的数目。一个任务要获取资源的控制权,其必须先获得信号量——使信号量的计数值减 1。当计数值减至 0,则表示没有可用资源。当任务利用资源完成工作后,将给出(归还)信号量——使信号量的计数值加 1。
用于资源管理的信号量,在创建时其计数值被初始化为可用资源总数。
FreeRTOS 中所有种类的信号量句柄都由声明为 xSemaphoreHandle 类型的变量
保存。
信号量在使用前必须先被创建。使用 xSemaphoreCreateCounting() API 函数来创建一个计数信号量。
xSemaphoreHandle xSemaphoreCreateCounting( unsigned portBASE_TYPE uxMaxCount,
unsigned portBASE_TYPE uxInitialCount );
uxMaxCount 最大计数值。如果把计数信号量类比于队列的话,uxMaxCount 值
就是队列的最大深度。
当此信号量用于对事件计数或锁存事件的话,uxMaxCount 就是可
锁存事件的最大数目。
当此信号量用于对一组资源的访问进行管理的话,uxMaxCount 应
当设置为所有可用资源的总数。
uxInitialCount 信号量的初始计数值。
当此信号量用于事件计数的话,uxInitialCount 应当设置为 0——因
为当信号量被创建时,还没有事件发生。
当此信号量用于资源管理的话, uxInitialCount 应当等于
uxMaxCount——因为当信号量被创建时,所有的资源都是可用的。
返回值 如果返回 NULL 值,表示堆上内存空间不足,所以 FreeRTOS 无法
为信号量结构分配内存导致信号量创建失败。
如果返回非 NULL 值,则表示信号量创建成功。此值应当被保存起
来作为这个的信号量的句柄。
例: 利用计数信号量对任务和中断进行同步
用计数信号量代替二值信号量对例 12 的实现进行了改进。修改 main()函数调用 xSemaphoreCreateCounting(),以代替对 xSemaphoreCreateBinary()的调用。
xSemaphoreHandle xCountingSemaphore;
int main( void )
{
/* 信号量在使用前都必须先创建。本例中创建了一个二值信号量 */
xCountingSemaphore = xSemaphoreCreateCounting( 10, 0 );
/* 安装中断服务例程 */
_dos_setvect( 0x82, vExampleInterruptHandler ); //这个是在中断调用方法是在于 Open Watcom DOS 平台的调用方法,不同平台须得采用正确的调用方法来实现
/* 检查信号量是否成功创建 */
if( xBinarySemaphore != NULL )
{
/* 创建延迟处理任务。此任务将与中断同步。延迟处理任务在创建时使用了一个较高的优先级,以保证
中断退出后会被立即执行。在本例中,为延迟处理任务赋予优先级3 */
xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );
/* 创建一个任务用于周期性产生软件中断。此任务的优先级低于延迟处理任务。每当延迟处理任务切出
阻塞态,就会抢占周期任务*/
xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );
/* Start the scheduler so the created tasks start executing. */
vTaskStartScheduler();
}
/* 如果一切正常,main()函数不会执行到这里,因为调度器已经开始运行任务。但如果程序运行到了这里,
很可能是由于系统内存不足而无法创建空闲任务。第五章会提供更多关于内存管理的信息 */
for( ;; );
}
static void vHandlerTask( void *pvParameters )
{
/* As per most tasks, this task is implemented within an infinite loop. */
for( ;; )
{
/* 使用信号量等待一个事件。信号量在调度器启动之前,也即此任务执行之前就已被创建。任务被无超
时阻塞,所以此函数调用也只会在成功获取信号量之后才会返回。此处也没有必要检测返回值 */
xSemaphoreTake( xBinarySemaphore, portMAX_DELAY ); //执行到这个地方,会进入阻塞态,等待中断函数给到信号量
/* 程序运行到这里时,事件必然已经发生。本例的事件处理只是简单地打印输出一个信息 */
vPrintString( "Handler task - Processing event.\r\n" );
}
}
//一个周期性的任务,每隔 500ms 定时产生一个软件中断:
//
static void vPeriodicTask( void *pvParameters )
{
for( ;; )
{
/* 此任务通过每500毫秒产生一个软件中断来”模拟”中断事件 */
vTaskDelay( 500 / portTICK_RATE_MS );
/* 产生中断,并在产生之前和之后输出信息,以便在执行结果中直观直出执行流程 */
vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
__asm{ int 0x82 } /* 这条语句产生中断 */
vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
}
}
static void __interrupt __far vExampleInterruptHandler( void )
{
static portBASE_TYPE xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
/* 多次给出信号量。第一次给出时使得延迟处理任务解除阻塞。后续给出用于演示利用被信号量锁存事件,
以便延迟处理任何依序对这些中断事件进行处理而不会丢中断。用这种方式来模拟处理器产生多个中断,尽管
这些事件只是在单次中断中模拟出来的 */
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken ); //这边给了三次信号量,所以会使得vHandlerTask这个执行三次之后在进入阻塞态,等待新的信号量的进入
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken ); //最多可以给出10次信号量
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
if( xHigherPriorityTaskWoken == pdTRUE )
{
/* 给出信号量以使得等待此信号量的任务解除阻塞。如果解出阻塞的任务的优先级高于当前任务的优先
级 – 强制进行一次任务切换,以确保中断直接返回到解出阻塞的任务(优选级更高)。
说明:在实际使用中,ISR中强制上下文切换的宏依赖于具体移植。此处调用的是基于Open Watcom DOS
移植的宏。其它平台下的移植可能有不同的语法要求。对于实际使用的平台,请参如数对应移植自带的示
例程序,以决定正确的语法和符号。
*/
portSWITCH_CONTEXT();
}
}
xQueueSendToFrontFromISR(),xQueueSendToBackFromISR()与 xQueueReceiveFromISR()分别是 xQueueSendToFront(),xQueueSendToBack()与 xQueueReceive()的中断安全版本,专门用于中断服务例程中。信号量用于事件通信。而队列不仅可以用于事件通信,还可以用来传递数据。
xQueueSendFromISR()完全等同于 xQueueSendToBackFromISR()。
portBASE_TYPE xQueueSendToFrontFromISR( xQueueHandle xQueue, void *pvItemToQueue, portBASE_TYPE *pxHigherPriorityTaskWoken );
portBASE_TYPE xQueueSendToBackFromISR( xQueueHandle xQueue, void *pvItemToQueue, portBASE_TYPE *pxHigherPriorityTaskWoken);
xQueue 目标队列的句柄。这个句柄即是调用 xQueueCreate()
创建该队列时的返回值。
pvItemToQueue 发送数据的指针。其指向将要复制到目标队列中的数据单元。
由于在创建队列时设置了队列中数据单元的长度,所以
会从该指针指向的空间复制对应长度的数据到队列的
存储区域。
pxHigherPriorityTaskWoken 对某个队列而言,可能有不止一个任务处于阻塞态在等
待其数据有效。调用 xQueueSendToFrontFromISR()
或 xQueueSendToBackFromISR()会使得队列数据变
为有效,所以会让其中一个等待任务切出阻塞态。如果
调用这两个 API 函数使得一个任务解除阻塞,并且这个
任务的优先级高于当前任务(也就是被中断的任务),那
么 API 会在函数内部将*pxHigherPriorityTaskWoken 设 为 pdTRUE。
如果这两个 API 函数将此值设为 pdTRUE,则在中断退
出前应当进行一次上下文切换。这样才能保证中断直接
返回到就绪态任务中优先级最高的任务中。
返回值 有两个可能的返回值:
1. pdPASS
返回 pdPASS 只会有一种情况,那就是数据被成功发送
到队列中。
2. errQUEUE_FULL
如果由于队列已满而无法将数据写入,则将返回
errQUEUE_FULL。
FreeRTOS 的大多数 demo 应用程序中都包含一个简单的 UART 驱动,其通过队列将字符传递到发送中断例程,也使用队列将字符从接收中断例程中传递出来。发送或接收的每个字符都通过队列单独传递。这些 UART 驱动的这种实现方式只是单纯了为了演示如何在中断中使用队列。实际上利用队列传递单个字符是极其低效的,特别是在波特率较高的时后,所以这种方式并不建议用在产品代码中。实际应用中可以采用下述
更有效的方式:
· 将接收到的字符先缓存到内存中。当接收到一个传输完成消息,或是检测到传输中断后,使用信号量让某个任务解除阻塞,这个任务将对字符缓存进行处理。
· 在中断服务中直接解析接收到的字符,然后通过队列将解析后经解码得到的命令发送到处理任务。这种技术仅适用于数据流能够快速解析的场合,这样整个数据解析工作才可以放在中断服务中完成。
例:利用队列在中断服务中发送或接收数据
本例演示在同一个中断服务中使用 xQueueSendToBackFromISR() 和xQueueReceiveFromISR()。和之前一样,采用软件中断以方便实现。创建一个周期任务用于每 200 毫秒往队列中发送五个数值,五个数值都发送完后便产生一个软件中断
xQueueHandle xIntegerQueue;
xQueueHandle xStringQueue;
static void vIntegerGenerator( void *pvParameters )
{
portTickType xLastExecutionTime;
unsigned portLONG ulValueToSend = 0;
int i;
/* 初始化变量,用于调用 vTaskDelayUntil(). */
xLastExecutionTime = xTaskGetTickCount();
for( ;; )
{
/* 这是个周期性任务。进入阻塞态,直到该再次运行的时刻。此任务每200毫秒执行一次 */
vTaskDelayUntil( &xLastExecutionTime, 200 / portTICK_RATE_MS );
/* 连续五次发送递增数值到队列。这此数值将在中断服务例程中读出。中断服务例程会将队列读空,所
以此任务可以确保将所有的数值都发送到队列。因此不需要指定阻塞超时时间 */
for( i = 0; i < 5; i++ )
{
xQueueSendToBack( xIntegerQueue, &ulValueToSend, 0 );
ulValueToSend++; //这个位置在不断加,所以结果的显示才会是那样的,并不是一直穿0,1,2,3,4,的
}
/* 产生中断,以让中断服务例程读取队列 */
vPrintString( "Generator task - About to generate an interrupt.\r\n" );
__asm{ int 0x82 } /* This line generates the interrupt. */
vPrintString( "Generator task - Interrupt generated.\r\n\r\n\r\n" );
}
}
static void __interrupt __far vExampleInterruptHandler( void )
{
static portBASE_TYPE xHigherPriorityTaskWoken;
static unsigned long ulReceivedNumber;
/* 这些字符串被声明为static const,以保证它们不会被定位到ISR的栈空间中,即使ISR没有运行它们也是存
在的 */
static const char *pcStrings[] =
{
"String 0\r\n",
"String 1\r\n",
"String 2\r\n",
"String 3\r\n"
};
xHigherPriorityTaskWoken = pdFALSE;
/* 重复执行,直到队列为空 */
while( xQueueReceiveFromISR( xIntegerQueue, &ulReceivedNumber, &xHigherPriorityTaskWoken ) != errQUEUE_EMPTY )
{
/* 截断收到的数据,保留低两位(数值范围0到3).然后将索引到的字符串指针发送到另一个队列 */
ulReceivedNumber &= 0x03; //0X03 0000 0011;
xQueueSendToBackFromISR( xStringQueue, &pcStrings[ ulReceivedNumber ], &xHigherPriorityTaskWoken );
}
/* 被队列读写操作解除阻塞的任务,其优先级是否高于当前任务?如果是,则进行任务上下文切换 */
if( xHigherPriorityTaskWoken == pdTRUE )
{
/* 说明:在实际使用中,ISR中强制上下文切换的宏依赖于具体移植。此处调用的是基于Open Watcom
DOS移植的宏。其它平台下的移植可能有不同的语法要求。对于实际使用的平台,请参如数对应移植自带
的示例程序,以决定正确的语法和符号。 */
portSWITCH_CONTEXT();
}
}
static void vStringPrinter( void *pvParameters )
{
char *pcString;
for( ;; )
{
/* 阻塞队列以等待数据到达 */
xQueueReceive( xStringQueue, &pcString, portMAX_DELAY );
/* Print out the string received. */
vPrintString( pcString );
}
}
int main( void )
{
/* 队列使用前必须先创建。本例中创建了两个队列。一个队列用于保存类型为unsigned long的变量,另一
个队列用于保存类型为char*的变量。两个队列的深度都为10。在实际应用中应当检测返回值以确保队列创建
成功 */
xIntegerQueue = xQueueCreate( 10, sizeof( unsigned long ) );
xStringQueue = xQueueCreate( 10, sizeof( char * ) );
/* 安装中断服务例程。 */
_dos_setvect( 0x82, vExampleInterruptHandler );
/* 创建任务用于往中断服务例程中发送数值。此任务优先级为1 */
xTaskCreate( vIntegerGenerator, "IntGen", 1000, NULL, 1, NULL );
/* 创建任务用于从中断服务例程中接收字符串,并打印输出。此任务优先级为2 */
xTaskCreate( vStringPrinter, "String", 1000, NULL, 2, NULL );
/* Start the scheduler so the created tasks start executing. */
vTaskStartScheduler();
/* 如果一切正常,main()函数不会执行到这里,因为调度器已经开始运行任务。但如果程序运行到了这里,
很可能是由于系统内存不足而无法创建空闲任务。 */
for( ;; );
}
最新的 FreeRTOS 移植中允许中断嵌套。中断嵌套需要在 FreeRTOSConfig.h 中定义
configKERNEL_INTERRUPT_PRIORITY 设置系统心跳时钟的中断优先级。
如果在移植中没有使用常量
configMAX_SYSCALL_INTERRUPT_PRIORITY,那
么需要调用中断安全版本 FreeRTOS API
的中断都必须运行在此优先级上。
configMAX_SYSCALL_INTERRUPT_PRIORITY 设置中断安全版本 FreeRTOS API 可以运
行的最高中断优先级。
建立一个全面的中断嵌套模型需要设置 configMAX_SYSCALL_INTERRUPT_PRIRITY为比configKERNEL_INTERRUPT_PRIORITY更高的优先级。这种模型在图35中有所展示。图 35 所示的情形假定常量 configMAX_SYSCALL_INTERRUPT_PRIRITY 设置为 3,configKERNEL_INTERRUPT_PRIORITY 设置为 1。同时也假定这种情形基于一个具有七个不同中断优先及的微控制器。这里的七个优先级仅仅是本例的一种假定,并非对应于任何一种特定的微控制器架构。
在任务优先级和中断优先级之间常常会产生一些混淆。图 35 所示的中断优先级是由微控制器架构体系所定义的。中断优先级是硬件控制的优先级,中断服务例程的执行会与之关联。任务并非运行在中断服务中,所以赋予任务的软件优先级与赋予中断源的硬件优先级之间没有任何关系。
如图 35 所示:
· 处于中断优先级 1 到 3(含)的中断会被内核或处于临界区的应用程序阻塞执行,但是它们可以调用中断安全版本的 FreeRTOS API 函数
· 处于中断优先级 4 及以上的中断不受临界区影响,所以其不会被内核的任何行为阻塞,可以立即得到执行——这是由微控制器本身对中断优先级的限定所决定的。通常需要严格时间精度的功能(如电机控制)会使用高于configMAX_SYSCALL_INTERRUPT_PRIRITY 的优先级,以保证调度器不会对其中断响应时间造成抖动。
· 不需要调用任何 FreeRTOS API 函数的中断,可以自由地使用任意优先级。