FreeRTOS中断管理--二值信号量

二值信号量同步

二值信号量(binary semaphroe api)的中断版本(ISR)可用于在每次发生特定中断来退出任务的阻塞状态,从而有效地将任务与中断同步。这允许大多数中断事件处理在同步任务中实现,而只有非常快和非常短的部分直接保留在ISR中。如前一部分《FreeRTOS中断管理–概述》所述,二进制信号量用于将延迟处理“推迟”到任务。

如图 所示,如果中断处理的时间特别紧迫,则可以设置延迟处理任务的优先级,以确保该任务始终抢占系统中的其他任务。然后可以将ISR实现为包括对portYIELD_FROM_ISR()的调用,以确保ISR直接返回到将中断处理推迟到的任务。这具有确保整个事件处理在时间上连续执行(不间断)的效果,就像所有事件处理都是在ISR本身中实现的一样。就如图所描述如何使用信号量来控制延迟处理任务的执行。

FreeRTOS中断管理--二值信号量_第1张图片
延迟处理任务是一种使用对信号量的阻塞获取(take)调用作为进入阻塞状态以等待事件发生的方式。事件发生时,ISR使用对同一信号量执行“提供(give)”操作以取消任务的阻塞,以便可以继续进行所需的事件处理。

“获取(take)信号量”和“提供信号量(give)”是根据使用情况而具有不同含义的概念。在此中断同步方案中,二进制信号量在概念上可以视为长度为1的队列。队列在任何时候最多可以包含一个项目,因此始终为空或已满(因此为二进制)。通过调用xSemaphoreTake(),将中断处理有效地推迟到的任务,而任务执行是当在阻塞时间内从队列中读取令牌(即二值信号量),如果队列为空,则使该任务进入“阻塞”状态。事件发生时,ISR使用xSemaphoreGiveFromISR()函数将令牌(信号量)放入队列中,使队列已满。这将导致任务退出阻塞状态并删除令牌,从而使队列再次为空。任务完成处理后,它将再次尝试从队列中读取数据,并发现队列为空,则重新进入阻塞状态以等待下一个事件。下图演示了此过程。
FreeRTOS中断管理--二值信号量_第2张图片

xSemaphoreCreateBinary()API 函数

NOTE:FreeRTOS V9.0.0还包含xSemaphoreCreateBinaryStatic()函数,该函数在编译时静态分配创建二进制信号量所需的内存
各种FreeRTOS信号量的句柄存储在SemaphoreHandle_t类型的变量中。

在使用信号量之前,必须先创建它。要创建二进制信号量,则使用xSemaphoreCreateBinary()API函数去创建它。

函数原型
SemaphoreHandle_t xSemaphoreCreateBinary(void);
FreeRTOS中断管理--二值信号量_第3张图片
xSemaphoreCreateBinary函数实际上是一个宏,如下所示:

#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )

xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )的源码如下所示:

	QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
	{
	QueueHandle_t xNewQueue;
	const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;

		xNewQueue = xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );  //二值信号量的创建实际上是创建了一个长度为1,队列项为0的队列
		prvInitialiseMutex( ( Queue_t * ) xNewQueue );

		return xNewQueue;
	}
xSemaphoreTake()API 函数

“接收(take)”信号量意味着“获取”或“接收”信号量。信号量只有在可用时才能使用。使用递归互斥锁,可以使用xSemaphoreTake()函数“获取”所有各种类型的FreeRTOS信号量。
NOTE:不得在中断服务程序中使用xSemaphoreTake()。
函数原型
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait);
FreeRTOS中断管理--二值信号量_第4张图片

xSemaphoreGiveFromISR()API 函数

可以使用xSemaphoreGiveFromISR()函数“提供” 二值计数信号量 。
xSemaphoreGiveFromISR()是xSemaphoreGive()的中断安全版本
函数原型
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore, BaseType_t * pxHigherPriorityTaskWoken);
FreeRTOS中断管理--二值信号量_第5张图片
示例:使用二值信号量的任务与中断
本示例使用二进制信号量从中断服务例程中改变任务的阻塞状态-有效地将任务与中断同步。
一个简单的定期任务用于每500毫秒生成一次软件中断。由于在某些目标环境中挂接到实际中断的复杂性,因此使用软件中断是为了方便。示例代码中显示了定期任务的实现。请注意,该任务在生成中断之前和之后都打印出一个字符串。这样可以在执行示例时在输出中观察执行顺序。
请注意如何使用xHigherPriorityTaskWoken变量。在调用xSemaphoreGiveFromISR()之前将其设置为pdFALSE,然后在调用portYIELD_FROM_ISR()时用作参数。如果xHigherPriorityTaskWoken等于pdTRUE,则将在portYIELD_FROM_ISR()宏内请求上下文切换。

/*在此示例中使用的软件中断号。所示代码来自Windows项目,其中FreeRTOS Windows端口本身使用数字0到2,因此3是应用程序可用的第一个数字*/

#define mainINTERRUPT_NUMBER 3
static void vPeriodicTask(void * pvParameters)
{
	const TickType_t xDelay500ms = pdMS_TO_TICKS(500UL);
	/*对于大多数任务,此任务是在无限循环内实现的*/
	for;;{
		/*阻塞,直到需要再次生成软件中断为止*/
		vTaskDelay(xDelay500ms);
		/*生成中断,在之前和之后打印一条消息中断已生成,因此从输出中可以明显看出执行的顺序。产生软件中断的语法取决于所使用的FreeRTO端口。下面使用的语法只能与FreeRTOS Windows端口一起使用,在该端口中仅模拟此类中断*/
		 vPrintString("periodic task - about to generate an interrupt.\r\n");
		 vPortGenerateSimulatedInterrupt(mainINTERRUPT_NUMBER);
		 vPrintString("periodic task--interrupt generated.\r\n\r\n\r\r\n");

	}

}

static void vHandlerTask(void * pvParameters)
{
	/*对于大多数任务,此任务是在无限循环内实现的*/
	for;;{
		/*使用信号量等待事件。信号灯已创建在调度程序启动之前,因此在第一次运行此任务之前。任务会无限期阻塞,这意味着仅在成功获取信号量后,此函数调用才会返回-因此,无需检查xSemaphoreTake()返回的值*/
		xSemaphoreTake(xBinarySemaphore,portMAX_DELAY);
		/*要到达此处,必须已发生该事件。处理事件(在这种情况下,只需打印一条消息)*/
		vPrintString("handler task --processing event.\r\n");
	}
}
static uint32_t ulExampleInterruptHandler(void)
{
	BaseType_t xHigherPriorityTaskWoken;
	/*xHigherPriorityTaskWoken参数必须初始化为pdFALSE,因为如果需要上下文切换,它将在中断安全API函数中设置为pdTRUE*/
	xHigherPriorityTaskWoken = pdFALSE;
	/*“给予”信号量以解除阻止任务,将xHigherPriorityTaskWoken的地址作为中断安全API函数的pxHigherPriorityTaskWoken参数传递*/
	xSemaphoreGiveFromISR(xBinarySemaphore,&xHigherPriorityTaskWoken);
	/*将xHigherPriorityTaskWoken值传递到portYIELD_FROM_ISR()。如果在xSemaphoreGiveFromISR()中将xHigherPriorityTaskWoken设置为pdTRUE,则调用portYIELD_FROM_ISR()将请求上下文切换。如果xHigherPriorityTaskWoken仍然是pdFALSE,则调用portYIELD_FROM_ISR()将无效。与大多数FreeRTOS端口不同,Windows端口要求ISR返回一个值-return语句位于Windows版本的portYIELD_FROM_ISR()中*/
	portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

}

int main(void{
	/*在使用信号灯之前,必须显式创建它。在此示例中,将创建一个二进制信号量*/
	xBinarySemaphore = xSemaphoreCreateBinary();
	/*检查信号量是否已成功创建*/
	if(xBinarySemaphore!= NULL)
	{
	/*创建“处理程序”任务,这是将中断处理推迟到的任务。这是将与中断同步的任务。创建的处理程序任务具有高优先级,以确保其在中断退出后立即运行。在这种情况下,
选择3*/
	xTaskCreate(vHandlerTask,"Handler",1000,NULL3,NULL);
	/*创建将定期生成软件中断的任务。在处理程序任务下方创建具有此优先级的文件,以确保每次处理程序任务退出“阻塞”状态时它将被抢占*/
	xTaskCreate(vPeriodicTask,"Periodic",1000,NULL,1,NULL);
	/*安装用于软件中断的处理程序。执行此操作所需的语法取决于所使用的FreeRTOS端口。此处显示的语法只能与FreeRTOS Windows端口一起使用,该端口仅模拟此类中断*/
	vPortSetInterruptHandler(mainINTERRUPT_NUMBER,ulExampleInterruptHandler());
	/*启动调度程序,以便创建的任务开始执行*/
	vTaskStartScheduler();

}
for;;;

}

上述代码的执行顺序如下图所示
FreeRTOS中断管理--二值信号量_第6张图片
上述代码使用二进制信号量将任务与中断同步。执行顺序如下:

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

NOTE:仅当中断以相对较低的频率发生时,上述代码中使用的任务结构才足够
要了解原因,请考虑如果在任务完成对第一个中断的处理之前发生了第二个然后是第三个中断,将会发生什么情况:
当第二个ISR执行时,信号量将为空,因此ISR将给出信号量,并且该任务将在完成处理第一个事件后立即处理第二个事件。如下图所示:
FreeRTOS中断管理--二值信号量_第7张图片
当执行第三个ISR时,该信号量将已经可用,从而阻止了ISR再次提供该信号量,因此该任务将不知道发生了第三个事件。如下图所示:
FreeRTOS中断管理--二值信号量_第8张图片

你可能感兴趣的:(FreeRTOS)