FreeRTOS中断管理

  嵌入式实时系统需要对整个系统环境产生的事件作出反应。这些事件对处理时间和响应时间都有不同的要求。事件通常采用中断方式检测,中断服务例程(ISR)中的处理量应当越短越好。
  注意:只有以”FromISR”或”FROM_ISR”结束的 API 函数或宏才可以在中断服务例程中。

一、二值信号量

1.1、利用二值信号量对任务和中断进行同步介绍

  二值信号量可以在某个特殊的中断发生时,让任务解除阻塞,相当于让任务与中断同步。这样就可以让中断事件处理量大的工作在同步任务中完成,中断服务例程(ISR)中只是快速处理少部份工作。 如此,中断处理可以说是被”推迟(deferred)”到一个”处理(handler)”任务。
  如果某个中断处理要求特别紧急,其延迟处理任务的优先级可以设为最高,以保证延迟处理任务随时都抢占系统中的其它任务。这样,延迟处理任务就成为其对应的 ISR退出后第一个执行的任务,在时间上紧接着 ISR 执行,相当于所有的处理都在 ISR 中完成一样。这种方案如图:
FreeRTOS中断管理_第1张图片
  创建二值信号量使用vSemaphoreCreateBinary()API 函数

void vSemaphoreCreateBinary( xSemaphoreHandle xSemaphore );

  获取信号量xSemaphoreTake(), xSemaphoreTake()不能在中断服务例程中调用。

portBASE_TYPE xSemaphoreTake( xSemaphoreHandle xSemaphore, portTickType xTicksToWait );

  中断中设置二值信号量xSemaphoreGiveFromISR()

portBASE_TYPE xSemaphoreGiveFromISR( xSemaphoreHandle xSemaphore,portBASE_TYPE *pxHigherPriorityTaskWoken );

1.2、利用二值信号量对任务和中断进行同步实现

  main()函数很简单,创建二值信号量及任务,安装中断服务例程,然后启动调度器。

int main( void )
{
	init();
	/* 信号量在使用前都必须先创建。本例中创建了一个二值信号量 */
	vSemaphoreCreateBinary( xBinarySemaphore );
	/* 检查信号量是否成功创建 */
	if( xBinarySemaphore != NULL )
	{
		/* 创建延迟处理任务。此任务将与中断同步。延迟处理任务在创建时使用了一个较高的优先级,以保证
		中断退出后会被立即执行。在本例中,为延迟处理任务赋予优先级3 */
		xTaskCreate( My_TaskTest, "My_TaskTest", 1000, NULL, 3, NULL );
		/* Start the scheduler so the created tasks start executing. */
		vTaskStartScheduler();
	}
}

  延迟处理任务My_TaskTest使用二值信号量与中断进行同步。这个任务也在每次循环中打印输出一个信息,这样做的目的同样是可以在程序的执行输出结果中直观地看出任务与中断的执行流程。

static void My_TaskTest( void *pvParameters )
{
	/* As per most tasks, this task is implemented within an infinite loop. */
	while(true)
	{
		/* 使用信号量等待一个事件。信号量在调度器启动之前,也即此任务执行
		之前就已被创建。任务被无超时阻塞,所以此函数调用也只会在成功获取信号量之后才会返回。此处也没有必要检测返回值 */
		xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );
		/* 程序运行到这里时,事件必然已经发生。本例的事件处理只是简单地打印输出一个信息 */
		vPrintString( "My_TaskTest work!\r\n" );
	}
}

  中断服务处理程序做的事情非常少,仅仅是给出一个信号量,以让延迟处理任务解除阻塞。注意这里是如何使用参数 pxHigherPriorityTaskWoken 的。这个参数在调用 xSemaphoreGiveFromISR()前被设置为 pdFALSE,如果在调用完成后被置为 pdTRUE,则需要进行一次上下文切换。

void My_IRQHandler(void)
{
	xHigherPriorityTaskWoken = pdFALSE;
	//释放二值信号量
	xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
	if( xHigherPriorityTaskWoken == pdTRUE )
	{
		/* 给出信号量以使得等待此信号量的任务解除阻塞。如果解出阻塞的任务的优先级高于当前任务的优先级 – 强制进行一次任务切换,
		以确保中断直接返回到解出阻塞的任务(优选级更高)。
		*/
		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
	}
}

  这个 演示了一个二值信号量被用于让任务和中断进行同步。整个执行流程可以描述为:
  1、中断产生。
  2、中断服务例程启动,给出信号量以使延迟处理任务解除阻塞。
  3、 当中断服务例程退出时,延迟处理任务得到执行。延迟处理任务做的第一件事便是获取信号量。
  4、 延迟处理任务完成中断事件处理后,试图再次获取信号量——如果此时信号量无效,任务将切入阻塞待等待事件发生。

二、计数信号量

  在中断以相对较慢的频率发生的情况下,二值信号量同步任务和中断可以有效的工作。但是,一个二值信号量最多只可以锁存一个中断事件。在锁存的事件还未被处理之前,如果还有中断事件发生,那么后续发生的中断事件将会丢失。如果用计数信号量代替二值信号量,那么,这种丢中断的情形将可以避免。

2.1、利用计数信号量对任务和中断进行同步介绍

  计数信号量有以下两种典型用法:
  1.事件计数
  在这种用法中,每次事件发生时,中断服务例程都会“给出(Give)”信号量,信号量在每次被给出时其计数值加 1。延迟处理任务每处理一个任务都会”获取(Take)”一次信号量,信号量在每次被获取时其计数值减 1。信号量的计数值其实就是已发生事件的数目与已处理事件的数目之间的差值。这
  用于事件计数的计数信号量,在被创建时其计数值被初始化为 0。
  2.资源管理
  在这种用法中,信号量的计数值用于表示可用资源的数目。一个任务要获取资源的控制权,其必须先获得信号量,使信号量的计数值减 1。当计数值减至 0,则表示没有可用资源。当任务利用资源完成工作后,将给出(归还)信号量,使信号量的计数值加 1。
  用于资源管理的信号量,在创建时其计数值被初始化为可用资源总数。
  信号量在使用前必须先被创建。使用 xSemaphoreCreateCounting() API 函数来创建一个计数信号量。

xSemaphoreHandle xSemaphoreCreateCounting( unsigned portBASE_TYPE uxMaxCount,unsigned portBASE_TYPE uxInitialCount );

2.2、利用计数信号量对任务和中断进行同步实现

  main()函数很简单,计数信号量及任务,安装中断服务例程,然后启动调度器。

int main( void )
{
	init();
	/* 在信号量使用之前必须先创建。本例中创建了一个计数信号量。此信号量的最大计数值为10,初始计数值为0 */
	xCountingSemaphore = xSemaphoreCreateCounting( 10, 0 );
	/* 检查信号量是否成功创建 */
	if( xCountingSemaphore != NULL )
	{
		/* 创建延迟处理任务。此任务将与中断同步。延迟处理任务在创建时使用了一个较高的优先级,以保证
		中断退出后会被立即执行。在本例中,为延迟处理任务赋予优先级3 */
		xTaskCreate( My_TaskTest, "My_TaskTest", 1000, NULL, 3, NULL );
		/* Start the scheduler so the created tasks start executing. */
		vTaskStartScheduler();
	}
}

  延迟处理任务My_TaskTest使用计数信号量与中断进行同步。这个任务也在每次循环中打印输出一个信息,这样做的目的同样是可以在程序的执行输出结果中直观地看出任务与中断的执行流程。

static void My_TaskTest( void *pvParameters )
{
	/* As per most tasks, this task is implemented within an infinite loop. */
	while(true)
	{
		/* 使用信号量等待一个事件。信号量在调度器启动之前,也即此任务执行
		之前就已被创建。任务被无超时阻塞,所以此函数调用也只会在成功获取信号量之后才会返回。此处也没有必要检测返回值 */
		xSemaphoreTake( xCountingSemaphore , portMAX_DELAY );
		/* 程序运行到这里时,事件必然已经发生。本例的事件处理只是简单地打印输出一个信息 */
		vPrintString( "My_TaskTest work!\r\n" );
	}
}

  中断服务处理程序做的事情非常少,仅仅是给出一个信号量,以让延迟处理任务解除阻塞。注意这里是如何使用参数 pxHigherPriorityTaskWoken 的。这个参数在调用 xSemaphoreGiveFromISR()前被设置为 pdFALSE,如果在调用完成后被置为 pdTRUE,则需要进行一次上下文切换。

void My_IRQHandler(void)
{
	xHigherPriorityTaskWoken = pdFALSE;
	/* 多次给出信号量。第一次给出时使得延迟处理任务解除阻塞。后续给出用于演示利用被信号量锁存事件,
以便延迟处理任何依序对这些中断事件进行处理而不会丢中断。用这种方式来模拟处理器产生多个中断,尽管
这些事件只是在单次中断中模拟出来的 */
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
	if( xHigherPriorityTaskWoken == pdTRUE )
	{
		/* 给出信号量以使得等待此信号量的任务解除阻塞。如果解出阻塞的任务的优先级高于当前任务的优先级 – 强制进行一次任务切换,
		以确保中断直接返回到解出阻塞的任务(优选级更高)。
		*/
		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
	}
}

  演示了一个计数信号量被用于任务和中断进行同步。整个执行流程可以描述为:
  1、中断产生。
  2、中断服务例程启动,给出信号量以使延迟处理任务解除阻塞。
  3、 当中断服务例程退出时,延迟处理任务得到执行。延迟处理任务做的第一件事便是获取信号量。每次中断发生后,延迟处理任务处理了中断生成的全部三个事件[模拟出来的]。 这些事件被锁存到信号量的计数值中,以使得延迟处理任务可以对它们依序进行处理。
  4、 延迟处理任务完成中断事件处理后,试图再次获取信号量——如果此时信号量无效,任务将切入阻塞待等待事件发生。

三、队列

  xQueueSendToFrontFromISR(), xQueueSendToBackFromISR()与 xQueueReceiveFromISR()分别是 xQueueSendToFront(), xQueueSendToBack()与 xQueueReceive()的中断安全版本,专门用于中断服务例程中。
  信号量用于事件通信。而队列不仅可以用于事件通信,还可以用来传递数据。

你可能感兴趣的:(FreeRTOS,freertos)