与二值信号量类似,计数信号量可以看作是长度大于1的队列,任务只关心队列中的项数,而不关心队列中的数据。要使用计数信号量需将FreeRTOSConfig.h中的configUSE_COUNTING_SEMAPHORES设置为1。每次"given"信号量时,使用其队列中的一处空间,队列中的项数就是信号量的计数值。计数信号量常用于下列两种情况:
在使用计数信号量之前需要先创建它,创建计数信号量的API原型如下:
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount );
参数:
返回值:
/* 产生软件中断的任务,示例中用于模拟,实际使用中可能是硬件中断 */
static void vPeriodicTask( void *pvParameters )
{
const TickType_t xDelay500ms = pdMS_TO_TICKS( 500UL );
for( ;; )
{
/* 阻塞500ms,然后发送软件中断 */
vTaskDelay( xDelay500ms );
/* 发送软件中断,输出提示信息 */
vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
}
}
/* 延迟处理任务 */
static void vHandlerTask( void *pvParameters )
{
for( ;; )
{
/* 获取信号量,如果信号量不可用,则阻塞等待 */
xSemaphoreTake( xCountingSemaphore, portMAX_DELAY );
/* 处理事件 */
vPrintString( "Handler task - Processing event.\r\n" );
}
}
/* ISR */
static uint32_t ulExampleInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
/* xHigherPriorityTaskWoken 指向的变量在使用前需要初始化为pdFALSE */
xHigherPriorityTaskWoken = pdFALSE;
/* 发送信号量 */
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
/* 根据xHigherPriorityTaskWoken 判断是否需要执行调度 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
int main( void )
{
/* 创建计数信号量 */
xCountingSemaphore = xSemaphoreCreateCounting( 10, 0 );
/* 创建成功 */
if( xCountingSemaphore!= NULL )
{
/* 创建延迟处理任务 */
xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );
/* 创建产生软件中断的任务,示例中用于模拟,实际使用中可能是硬件中断 */
xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );
/* 设置ISR函数,示例中用于模拟,实际使用中可能是硬件中断 */
vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );
/* 启动调度 */
vTaskStartScheduler();
}
for( ;; );
}
到目前为止提出的延迟中断处理的示例都要求应用程序为每个需要延迟处理的中断创建延迟处理任务。这里介绍一种无需为每个中断创建单独任务的方法,使用xTimerPendFunctionCallFromISR() API函数将中断处理延迟到RTOS的守护程序任务中,这种方式称为集中式延迟中断处理。
在FreeRTOS学习笔记九【软件定时器】中描述了与软件定时器相关的API函数如何向定时器命令队列上的守护程序任务发送命令。 xTimerPendFunctionCall()和xTimerPendFunctionCallFromISR()函数使用相同的计时器命令队列向守护程序任务发送“执行函数”命令。然后,在守护程序任务的上下文中执行发送到守护程序任务的函数。
集中式延迟中断处理有以下几个优点:
但集中式延迟中断处理也有以下几个缺点:
不同的中断具有不同的约束,因此通常在应用程序中将两个方式结合使用。
xTimerPendFunctionCallFromISR()是xTimerPendFunctionCall()的中断安全版本。 这两个API函数都允许将提供的函数放在RTOS守护程序任务中执行,要执行的函数和函数的输入参数都将发送到定时器命令队列上。它的原型如下:
BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend,
void *pvParameter1,
uint32_t ulParameter2,
BaseType_t *pxHigherPriorityTaskWoken );
参数:
返回值:
/* 产生软件中断的任务,示例中用于模拟,实际使用中可能是硬件中断 */
static void vPeriodicTask( void *pvParameters )
{
const TickType_t xDelay500ms = pdMS_TO_TICKS( 500UL );
for( ;; )
{
/* 阻塞500ms,然后发送软件中断 */
vTaskDelay( xDelay500ms );
/* 发送软件中断,输出提示信息 */
vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
}
}
/* 守护程序任务中执行的函数 */
static void vDeferredHandlingFunction( void *pvParameter1, uint32_t ulParameter2 )
{
/* 处理事件 */
vPrintStringAndNumber( "Handler function - Processing event ", ulParameter2 );
}
/* ISR函数 */
static uint32_t ulExampleInterruptHandler( void )
{
static uint32_t ulParameterValue = 0;
BaseType_t xHigherPriorityTaskWoken;
/* 使用前需要初始为pdFALSE */
xHigherPriorityTaskWoken = pdFALSE;
/* 将指定的函数传入守护程序任务中执行 */
xTimerPendFunctionCallFromISR( vDeferredHandlingFunction, /* 执行的函数 */
NULL, /* 未使用 */
ulParameterValue, /* 递增 */
&xHigherPriorityTaskWoken );
ulParameterValue++;
/* 通过xHigherPriorityTaskWoken判断是否需要重新调度程序 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
int main( void )
{
const UBaseType_t ulPeriodicTaskPriority = configTIMER_TASK_PRIORITY - 1;
/* 创建模拟中断的任务,实际中为处理的硬件中断 */
xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, ulPeriodicTaskPriority, NULL );
vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );
vTaskStartScheduler();
for( ;; );
}
二值信号量和计数信号量用于同步(传递)事件。 队列用于传递事件和传输数据。在ISR中使用队列,必须使用它安全版的API,它的使用和普通股版类似,但是没有阻塞操作,同时多了一个判断是否需要重新调度程序的参数,前面已有相应介绍,这里就不在介绍API,介绍一下在ISR中使用队列的注意事项。
队列提供了一种简单方便的方法将数据从ISR传递到任务中,但是如果中断的发送频率高,则队列的效率不高。因此,建议只在中断发生频率不高的ISR中使用队列来传输数据。下面介绍几种在ISR更高效的方法:
有时会将任务优先级与中断优先级混淆,这里将介绍中断优先级,它是ISR之间执行的优先级,和分配给任务的优先级无关。ISR何时执行有硬件决定,任务何时执行有软件决定,当执行ISR时任务会被中断(暂停执行的任务,去执行ISR),但任务不能抢占ISR。
支持中断嵌套的移植需要在FreeRTOSConfig.h中定义一个或两个常量,其中configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY都定义了相同的属性。 较旧的FreeRTOS使用configMAX_SYSCALL_INTERRUPT_PRIORITY,较新的FreeRTOS使用configMAX_API_CALL_INTERRUPT_PRIORITY。
常量 | 描述 |
---|---|
configMAX_SYSCALL_INTERRUPT_PRIORITY | 设置中断安全的API函数的最高中断优先级 |
configMAX_API_CALL_INTERRUPT_PRIORITY | 同上 |
KERNEL_INTERRUPT_PRIORITY | 设置滴答中断中断优先级,并且必须设置为最低中断优先级。如果也没有使用configMAX_SYSCALL_INTERRUPT_PRIORITY(或configMAX_API_CALL_INTERRUPT_PRIORITY)常量,然后任何中断安全的API函数的中断也必须以configKERNEL_INTERRUPT_PRIORITY定义的优先级执行。 |
每个中断都有一个数字优先级和逻辑优先级:
中断的数字优先级和逻辑优先级之间的关系取决于处理的构架,在某些处理器上,分配给中断的数字优先级越高,中断的逻辑优先级就越高,而在一些处理器上,分配的数字优先级越高,中断的逻辑优先级就越低。
如下图描述的场景: