细说STM32单片机FreeRTOS中断管理及其应用方法

目录

一、FreeRTOS与中断NVIC

二、FreeRTOS任务与中断服务ISR

1、任务与中断服务例程的关系

2、中断屏蔽和临界代码段

3、在ISR中使用FreeRTOS API函数

4、中断及其ISR设计原则

三、任务和中断程序设计示例

1、示例功能与CubeMX项目设置

(1)RCC、SYS、Code Generator

(2)RTC

(3)USART3

(4)GPIO

(5) FreeRTOS

(6)NVIC

2、软件设计

(1)main.c

(2)ISR设计:rtc.c

(3)Task设计:freertos.c

3、运行

4、测试其它各种情形

(1)中断ISR长时间占用CPU对任务的影响

(2)在任务中屏蔽中断

(3)修改RTC为不可屏蔽中断

(4)使用TaskENTER_CRITICAL()对

(5)任务函数中误用vTaskDelay()等开启了中断


        FreeRTOS的任务有优先级,MCU的硬件中断也有中断优先级,这是两个不同的概念。FreeRTOS的任务管理要用到硬件中断,使用FreeRTOS时也可以使用硬件中断,但是硬件中ISR的设计要注意一些设计原则。本文章将介绍FreeRTOS与硬件中断的关系,及如何正确使用硬件中断。

一、FreeRTOS与中断NVIC

        中断是MCU的硬件特性,STM32 MCU的NVIC管理硬件中断。STM32F4使用4个位置优先级分组策略,用于设置中断的抢占优先级和次优先级,优先级数字越小,优先级越高。每个中断有一个中断服务例程,即ISR,用于对中断做出响应。

        FreeRTOS的运行要用到中断,FreeRTOS的上下文切换就是在PendSV中断里进行的,FreeRTOS还需要一个基础时钟产生嘀嗒信号。在CubeMX中启用FreeRTOS后,系统会自动对NVIC做一些设置,中断优先级分组策略自动设置为4位全部用于抢占优先级,所以抢占优先级编号是0到15。这个设置对应于文件FreeRTOSConfig.h中的参数configPRIO_BITS,默谈定义如下:

#define configPRIO_BITS 4

        这个参数在CubeMX中不能修改,固定为4,也就是分组策略使用4位抢占优先级。在CubeMX中设置FreeRTOS的“config”参数时,有2个与中断相关的参数设置

  • configLIBRARY_LOWEST_INTERRUPT_PRIORITY,表示中断的最低优先级数值。因为中断分组策略是4位全用于抢占优先级,所以这个数值为15。
  • configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY,表示FreeRTOS可管理的最高优先级,默认值为5。只有在中断优先级数值≥5的中断ISR里,才可以调FreeRTOS的中断安全API函数,也就是带"FromISR"后缀的函数,使用taskDISABLE_INTERRUPTS()函数也只能屏蔽优先级数值≥5的中断。参数configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY绝不允许设置为0,绝对不要在高于此优先级的中断ISR里调用FreeRTOS的API函数,即便是带"FromISR"的中断安全函数也不可以。

        NVIC最右边一列Uses FreeRTOS functions,表示是否要在中断的ISR里使用FreeRTOS的API函数。如果勾选了此列的复选框,那么这个中断的优先级数值就不能小于5(根据本示的设置参数)。这个复选项并不会对生成的代码产生任何影响,只是改变了NVIC中某个中断的抢占式优先级可设置范围。

        除了上述两个参数以及参数configPRIO_BITS的设置,文件FreeRTOSConfig.h还定了一个参数configKERNEL_INTERRUPT_PRIORITY,用于写入寄存器的表示最低优先级的数值。其定义如下:

/* Interrupt priorities used by the kernel port layer itself. These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY 
( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!

        经过计算,configKERNEL_INTERRUPT_PRIORITY的值是0xF0(=15*2^4=240)参数configKERNEL_INTERRUPT_PRIORITY用于定义PendSV和SysTick的中断优先级,文件port.c中的定义如下:

#define portNVIC_PENDSV_PRI
 ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 16UL )
#define portNVIC_SYSTICK_PRI 
( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )

        portNVIC_PENDSV_PRI和portNVIC_SYSTICK_PRI是用于写入寄存器的值,其数值与中断优先级的表示有关。直观的就是与NVIC设置的PendSV优先级为15和SysTick的优先级为15有关。

        PendSV(Pendable request for system service,可挂起的系统服务请求)中断用于上下文切换,也就是在这个中断ISR里决定哪个任务占用CPU。

  • PendSV中断的抢占优先级为15,是最低的。所以,只有在没有其他中断ISR运行的情况下,FreeRTOS才会执行上下文切换。
  • SysTick中断的抢占优先级为15,是最低的。系统在SysTick中断里发出任务调度请求,所以,只有在没有其他中断ISR运行的情况下,任务调度请求才会被及时响应
  • 根据NVIC管理中断的特点,同等抢占优先级的中断是不能发生抢占的,所以,只要有一个抢占优先级为15的中断ISR在运行,SysTick和PendSV的中断就无法被及时响应也就是不会发生任务调度,任务函数也不会被执行
  • 相反地,如果,指定了定时器TIM6作为HAL基础时钟源。TIM6中断的抢占优先级为0,也就是最高优先级,FreeRTOS无法屏蔽HAL的基础时钟中断

        当参数configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY设置为5时,系统中各优先级中断的作用和分类如下图所示,归纳起来的要点如下: 

  • TIM6是HAL基础时钟,其中断优先级为0,所以HAL基础时钟中断不会被FreeRTOS屏蔽。
  • SysTick定时器是FreeRTOS的基础时钟,其中断优先级为15。FreeRTOS在SysTick中断里发出任务切换请求,也就是将PendSV的中断挂起标志位置1。
  • PendSV的中断优先级为15,FreeRTOS在PendSV中断里执行任务切换,所以,只有在没有其他中断ISR在运行的情况下,才会发生任务切换
  • 中断分为2组:优先级0至4的中断不受FreeRTOS的管理,称为FreeRTOS不可屏蔽中断,优先级5至15的中断是FreeRTOS可屏蔽中断,可以用函数taskDISABLE_INTERRUPTS()屏蔽这些级别的中断。这里说的“不可屏蔽中断”是指FreeRTOS不可屏蔽的中断,不要与MCU硬件系统的不可屏蔽中断混淆。STM32F4的中断向量表里也有3个不可屏蔽中断,分别是Reset、NMI和HardFault。

二、FreeRTOS任务与中断服务ISR

1、任务与中断服务例程的关系

        MCU的中断有中断优先级,有中断服务例程(ISR);FreeRTOS的任务有任务优先级,有任务函数。这两者的特点和区别具体如下。

  • 中断是MCU的硬件特性,由硬件事件或软件信号引起中断,运行哪个ISR是由硬件决定的。中断的优先级数字越小,表示优先级越高,所以中断的最高优先级为0
  • FreeRTOS的任务是一个纯软件的概念,与硬件系统无关。任务的优先级是开发者在软件中赋予的,任务的优先级数字越小,表示优先级越低,所以任务的最低优先级为0。
  • FreeRTOS的任务调度器决定哪个任务处于运行状态,FreeRTOS在中断优先级为15的PendSV中断里进行上下文切换,所以,只要有中断ISR在运行,FreeRTOS就无法进行任务切换
  • 任务只有在没有ISR运行的时候才能运行,即使优先级最低的中断,也可以抢占高优先级的任务的执行,而任务不能抢占ISR的运行

        任务函数与中断的ISR运行时的关系: 

 

  • 在t1时刻,User Task进入运行状态,占用CPU;在t2时刻,发生了一个中断ISR1,不管User Task的任务优先级有多高,ISR1都会抢占CPU。ISR1执行完成后,User Task才可以继续执行。
  • 在t6时刻,发生了中断2,ISR2抢占了CPU。但是ISR2占用CPU的时间比较长,导致User Task执行时间变长,
  • 图中,ISR执行时,就无法执行任务函数。所以,如果一个ISR执行的时间比较长,任务函数无法及时执行,FreeRTOS也无法进行任务调度,就会导致软件响应变远钝。

        在实际的软件设计中,一般要尽量简化ISR的功能,使其尽量少占用CPU的时间。一般的硬件中断都是处理一些数据的接收或发送工作,例如,采用中断方式进行ADC数据采集时,只需在ADC的中断里将数据读取到缓冲区,而对数据进行滤波、频谱计算等耗时间的工作,就转移到任务函数里处理(只要符合进程通信的需要就行)。

2、中断屏蔽和临界代码段

        一个任务函数在执行的时候,可能会被其他高优先级的任务抢占CPU,也可能被任何一个中断的ISR抢占CPU。在某些时候,任务的某段代码可能很关键,需要连续执行完,不希望被其他任务或中断打断,这种程序段称为临界段(critical section)。在FreeRTOS中,有函数定义临界代码段,也可以屏蔽系统的部分中断。

        文件task.h定义了几个宏函数,定义代码如下,函数功能见代码中的注释:

#define taskDISABLE_INTERRUPTS()	    portDISABLE_INTERRUPTS()		//屏蔽MCU的部分中断
#define taskENABLE_INTERRUPTS()	        portENABLE_INTERRUPTS()			//解除中断屏蔽
#define taskENTER_CRITICAL()		    portENTER_CRITICAL()			//开始临界代码段
#define taskENTER_CRITICAL_FROM_ISR()	portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL()		        portEXIT_CRITICAL()				//结束临界代码段
#define taskEXIT_CRITICAL_FROM_ISR(x)	portCLEAR_INTERRUPT_MASK_FROM_ISR(x)
  • 宏函数taskDISABLE_INTERRUPTS()用于屏蔽MCU中的可屏蔽的中断。
  • 宏函数taskENABLE_INTERRUPTS()用于解除中断屏蔽。
  • 函数taskENTER_CRITICAL()和taskEXIT_CRITICAL()用于界定一个临界代码段,在临界代码段内,FreeRTOS会暂停任务调度,所以正在执行的任务不会被更高优先级的任务抢占,能保证代码执行的连续性。
  • taskENTER_CRITICAL_FROM_ISR()是taskENTER_CRITICAL()的ISR版本,用于在ISR中调用。

        这些宏函数实际上是执行了另外一些函数,如taskENTER_CRITICAL()实际上是执行了函数portENTER_CRITICAL()。跟踪代码会发现,这些“port”前缀的函数是在文件portmacro.h中定义的宏,部分底层的代码是用汇编语言写的,是根据具体的MCU型号移植的代码。

        定义临界代码段和屏蔽中断在功能上是类同的,因为函数taskENTER_CRITICAL()里调用了portDISABLE_INTERRUPTS(),taskEXIT_CRITICAL()里调用了portENABLE_INTERRUPTS()。

        实现临界代码段的两个函数的底层代码如下:

void vPortEnterCritical( void )
{
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;

    /* This is not the interrupt safe version of the enter critical function so
    assert() if it is being called from an interrupt context. Only API
    functions that end in "FromISR" can be used in an interrupt. Only assert if
    the critical nesting count is 1 to protect against recursive calls if the
    assert function also uses a critical section. */
    if( uxCriticalNesting == 1 )
    {
        configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
    }
}

void vPortExitCritical( void )
{
    configASSERT( uxCriticalNesting );
    uxCriticalNesting--;
    if( uxCriticalNesting == 0 )
    {
        portENABLE_INTERRUPTS();
    }
}

        函数taskENTER_CRITICAL()和taskEXIT_CRITICAL()使用了嵌套计数器,所以这一对函数可以嵌套使用。函数taskDISABLE_INTERRUPTS()和taskENABLE_INTERRUPTS()不能嵌套使用,只能成对使用。

3、在ISR中使用FreeRTOS API函数

        在中断的ISR里,有时会调用FreeRTOS的API函数,但是调用普通的API函数可能会存在问题。例如,在ISR里调用vTaskDelay()就会出问题,因为vTaskDelay()会使任务进入阻塞状态,而ISR根本就不是任务,ISR运行的时候,也不能进行任务调度

        为此,FreeRTOS的API函数分为两个版本:一个称为“任务级”,即普通名称的API函数;另一个称为“中断级”,即带后缀“FromISR”的函数或带后缀“FROM_ISR”的宏函数,中断级API函数也称为中断安全API函数

        如,对应taskENTER_CRITICAL()的中断级宏函数是taskENTER_CRITICALFROM_ISR(),对应函数xTaskGetTickCount()的中断级函数是xTaskGetTickCountFromISR()。

        FreeRTOS将API函数分为两个版本的好处是:在API的实现代码中,无须判断调用这个API函数的是一个ISR,还是一个任务函数,否则需要增加额外的代码,而且不同的MCU判断ISR和任务函数的机制可能不一样。所以,使用两种版本的API函数,使FreeRTOS的代码效率更高。

        在ISR中,绝对不能使用任务级API函数,但是在任务函数中,可以使用中断级API函数。此外,在FreeRTOS不能管理的高优先级中断的ISR里,连中断级API函数也不能调用。

4、中断及其ISR设计原则

        根据FreeRTOS管理中断的特点,中断的优先级和ISR程序设计应该遵循如下原则:

  • 根据参数configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY的设置,MCU的优先级为0到15的中断,分为FreeRTOS不可屏蔽中断和可屏蔽中断,要根据中断的重要性和功能,为其设置合适的中断优先级,使其成为FreeRTOS不可屏蔽中断或可屏蔽中断。
  • ISR的代码应该尽量简短,应该将比较耗时的处理功能转移到任务函数里实现。
  • 在可屏蔽中断的ISR里,能调用中断级的FreeRTOS API函数,绝对不能调用普通的FreeRTOS API函数。在不可屏蔽中断的ISR里,不能调用任何的FreeRTOS API函数。

三、任务和中断程序设计示例

1、示例功能与CubeMX项目设置

        设计一个示例用到了RTC的唤醒中断,在此中断里,读取RTC的当前时间,并在串口助手上显示。还在FreeRTOS中设计了一个任务Task_LED1。通过各种参数设置和稍微修改代码,测试和验证任务函数与ISR的关系特点。

        继续使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。 

(1)RCC、SYS、Code Generator

        HSE,使用外部晶振25MHz,HCLK=168MHz;

        LSE,使用外部晶振32.768KHz,为RTC的时钟源;

        Code Generator页面,勾选自动生成.c\.h数据对;

        SYS,选择Serial wire,选择TIM6为Time Base基础时钟定时器;

(2)RTC

        在RTC的模式设置中,启用时钟源和日历,并设置WakeUp为Internal WakeUp。

        在RTC的参数设置部分,随便设置日期和时间的初始值。Wake Up Clock设置为1Hz,Wake Up Counter设置为1,这样唤醒周期是2s

(3)USART3

         配置USART3,异步,管脚PB10、PB11。默认其它全部的参数。

(4)GPIO

        配置PA6为GPIO output,默认高电平,高速,No PP,别名LED1;

 

(5) FreeRTOS

  • 启用FreeRTOS并设置为CMSIS_V2接口。
  • 参数与包含页,默认。特别地与中断相关的参数,默认FreeRTOS可屏蔽中断的最高优先级是5,默认最低中断优先级为15;
  • 创建任务Task_LED1,设置任务函数名为AppTask_LED1。

 

(6)NVIC

        在RTC的NVIC设置部分,启用RTC唤醒中断。设置其抢占式优先级为1,不要勾选用于FreeRTOS,这样,它就是FreeRTOS不可屏蔽的中断了。其它的默认; 

  • 中断优先级分组策略被设置为4位全用于抢占优先级,且不能修改。
  • TIM6作为HAL基础时钟源,中断优先级修改为0。
  • PendSV和SysTick的优先级被设置为15,且不能修改。

2、软件设计

(1)main.c

        完成设置后,我们在CubeMX中生成代码。我们在CubelDE中打开项目,在初始代码的基础上,添加用户功能代码,主程序代码如下:

  /* USER CODE BEGIN 2 */
  uint8_t startstr[] = "Demo3-1_RTOS_Interrupt: First, RTC priority=1.\r\n\r\n";
  HAL_UART_Transmit(&huart3,startstr,sizeof(startstr),0xFFFF);

  //以下显示用于提示各种特性的测试
  uint8_t startstr1[] = "HAL_Delay(1000) in RTC ISR.\r\n";
  HAL_UART_Transmit(&huart3,startstr1,sizeof(startstr1),0xFFFF);

  uint8_t startstr2[] = "RTC priority=7.\r\n";
  HAL_UART_Transmit(&huart3,startstr2,sizeof(startstr2),0xFFFF);

  uint8_t startstr3[] = "Disable interrupt in task.\r\n";
  HAL_UART_Transmit(&huart3,startstr3,sizeof(startstr3),0xFFFF);

  uint8_t startstr4[] = "vTaskDelay(2000) in task.\r\n\r\n";
  HAL_UART_Transmit(&huart3,startstr4,sizeof(startstr4),0xFFFF);

  /* USER CODE END 2 */

(2)ISR设计:rtc.c

/* USER CODE BEGIN 0 */
#include <stdio.h>
#include "usart.h"
/* USER CODE END 0 */

        重写RTC唤醒中断事件的回调函数HAL_RTCEx_WakeUpTimerEventCallback() :

/* USER CODE BEGIN 1 */
///<RTC Interrupt Wake up Callback
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
	RTC_TimeTypeDef sTime;
	RTC_DateTypeDef sDate;
	if (HAL_RTC_GetTime(hrtc, &sTime,  RTC_FORMAT_BIN) == HAL_OK)
	{
		HAL_RTC_GetDate(hrtc, &sDate,  RTC_FORMAT_BIN);

		//显示日期
		char str[40];
		sprintf(str,"RTC Date= %4d-%2d-%2d",2000+sDate.Year,sDate.Month,sDate.Date);
		printf(" %s.\r\n",str);

		//显示时间hh:mm:ss
		sprintf(str,"RTC Time = %2d:%2d:%2d",sTime.Hours,sTime.Minutes,sTime.Seconds);
		printf(" %s.\r\n",str);
	}
	//	HAL_Delay(1000);
}

int __io_putchar(int ch)
{
	HAL_UART_Transmit(&huart3,(uint8_t*)&ch,1,0xFFFF);
	return ch;
}
/* USER CODE END 1 */

(3)Task设计:freertos.c

        根据FreeRTOS任务函数框架,重写任务函数AppTask_LED1(): 

/* USER CODE BEGIN Header_AppTask_LED1 */
/**
  * @brief  Function implementing the Task_LED1 thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_AppTask_LED1 */
void AppTask_LED1(void *argument)
{
  /* USER CODE BEGIN AppTask_LED1 */
  /* Infinite loop */
  for(;;)
  {
	  //taskDISABLE_INTERRUPTS();
	  //taskENTER_CRITICAL();
	  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_6);		///<PA6=LED1 flashing
	  vTaskDelay(pdMS_TO_TICKS(500));				///<周期500ms
	  //HAL_Delay(2000);
	  //taskEXIT_CRITICAL();
	  //taskENABLE_INTERRUPTS();
  }
  /* USER CODE END AppTask_LED1 */
}

3、运行

        构建项目后,我们将其下载到开发板上并加以测试,运行时,可以发现RTC唤醒中断和任务函数都能按期望运行,每隔2s在串口助手上刷新显示当前日期和时间,LED1也是规律性地快速闪烁。

        在这个程序中,RTC唤醒中断的ISR处理速度很快,占用CPU时间很短,所以不影响任务函数AppTask_LED1()的执行效果。

4、测试其它各种情形

(1)中断ISR长时间占用CPU对任务的影响

        对RTC唤醒中断回调函数HAL_RTCEx_WakeUpTimerEventCallback()的代码稍作修改,取消最后一行上延时HAL_Delay(1000)的注释。这样,RTC唤醒中断ISR执行的时间就达到1000ms,远大于执行一次任务函数AppTask_LED1()内部循环的周期200ms(如果是500ms则几乎看不到影响)中断的ISR时间占用CPU,导致任务函数不能及时执行。

/* USER CODE BEGIN 1 */
///<RTC Interrupt Wake up Callback
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
	RTC_TimeTypeDef sTime;
	RTC_DateTypeDef sDate;
	if (HAL_RTC_GetTime(hrtc, &sTime,  RTC_FORMAT_BIN) == HAL_OK)
	{
		HAL_RTC_GetDate(hrtc, &sDate,  RTC_FORMAT_BIN);

		//显示日期
		char str[40];
		sprintf(str,"RTC Date= %4d-%2d-%2d",2000+sDate.Year,sDate.Month,sDate.Date);
		printf(" %s.\r\n",str);

		//显示时间hh:mm:ss
		sprintf(str,"RTC Time = %2d:%2d:%2d",sTime.Hours,sTime.Minutes,sTime.Seconds);
		printf(" %s.\r\n",str);
	}
	HAL_Delay(1000);  //测试ISR长时间占用CPU,影响Task不能正常规律地运行
}

        在RTC唤醒中断回调函数的代码里,绝不能将函数HAL_Delay()替换为vTaskDelay(),因为ISR里不能调用FreeRTOS的普通API函数,回调函数是由中断的ISR调用的。

        构建项目后,将其下载到开发板上并运行,发现LED1不能像前面那样规律性地快速闪烁,而是闪烁几次后停顿约1000ms,这是因为CPU被RTC的唤醒中断ISR占用了约1000ms。即使将RTC唤醒中断的优先级修改为15,程序运行的结果也是一样的。

        所以,在使用FreeRTOS时,中断ISR的代码要尽量简化,尽量少占用CPU时间,要把要进行大量处理的功能转移到任务里去处理。

(2)在任务中屏蔽中断

        在FreeRTOS中,使用函数taskDISABLE_INTERRUPTS()屏蔽图3-3中的可屏中断。在本示例中,参数configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY设置为5,所以,中断优先级数字5的中断可以被屏蔽

        为测试在任务中屏蔽中断的效果,我们对RTC做如下的设置或修改,修改设置后,CubeMX中重新生成代码。

  • 将RTC唤醒中断的优先级设置为7;
  • 将RTC唤醒的周期设置为1s,将参数Wake Up Counter修为0;
  • 在RTC唤醒中断的回调函数代码中,将最后一行的HAL_Delay(1000)注释掉。将任务Task_LED1的任务函数修改为如下的内容:
/* USER CODE END Header_AppTask_LED1 */
void AppTask_LED1(void *argument)
{
  /* USER CODE BEGIN AppTask_LED1 */
  /* Infinite loop */
  for(;;)
  {
	  taskDISABLE_INTERRUPTS();
	  //taskENTER_CRITICAL();
	  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_6);		///<PA6=LED1 flashing
	  //vTaskDelay(pdMS_TO_TICKS(200));				///<延迟200ms
	  HAL_Delay(2000);								///<连续运行2000ms,任务处于运行状态
	  //taskEXIT_CRITICAL();
	  taskENABLE_INTERRUPTS();
  }
  /* USER CODE END AppTask_LED1 */
}

        在任务函数的for循环内,使用了taskDISABLE_INTERRUPTS()和taskENABLE_INTERRUPTS()函数对,所以在执行中间的代码时会屏蔽中断。中间的代码功是使LED1闪烁,每次调用HAL_Delay(2000)延时2000ms。注意,函数HAL_Delay()是连运行的,任务仍然处于运行状态。

        构建项目后,将其下载到开发板上并予以测试,运行时,会发现串口助手上大约间隔2s才变化一次,而不是设置的1s周期。这是因为在任务函数中屏蔽了中断(不许中断),而RTC唤醒中优先级为7,被屏蔽了。现象:窗口助手上数据2s更新一次,LED1 2s闪烁一次;

        进一步地,对这个测试再做修改,以便测试其它情况: 

(3)修改RTC为不可屏蔽中断

        将RTC唤醒中断优先级设置为1,其他设置和程序不变。下载并测试会发现,串口助手上的时间是每1s刷新一次了。这是因为RTC唤醒中断优先级1,是FreeRTOS不可屏蔽中断。所以,在任务函数里执行函数taskDISABLE_INTERRUPTS也无法关闭这个中断。现象:窗口助手上数据1s更新一次,LED1 2s闪烁一次;

(4)使用TaskENTER_CRITICAL()对

        将RTC唤醒中断优先级重新设置为7,将任务函数中的taskDISABLE_INTERRUPTS()和taskENABLE_INTERRUPTS()相应地替换为taskENTER_CRITICAL()和taskEXIT_CRITICAL ()。会发现,运行效果与使用taskDISABLE_INTERRUPTS()和taskENABLE_INTERRUPTS()是一样的,这是因为在函数taskENTER_CRITICAL()中也会屏蔽中断。现象:窗口助手上数据2s更新一次,LED1 2s闪烁一次;

(5)任务函数中误用vTaskDelay()等开启了中断

        将RTC唤醒中断优先级设置为7,任务函数中的HAL_Delay()函数替换为vTaskDelay()。任务函数完整代码如下:

/* USER CODE END Header_AppTask_LED1 */
void AppTask_LED1(void *argument)
{
  /* USER CODE BEGIN AppTask_LED1 */
  /* Infinite loop */
  for(;;)
  {
	  taskDISABLE_INTERRUPTS();
	  //taskENTER_CRITICAL();
	  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_6);///<PA6=LED1 flashing
	  vTaskDelay(pdMS_TO_TICKS(2000));		///<The delay is 2000ms, but the task also enters a blocking state, which will inevitably lead to the interruption of PendSV for task scheduling.
	  //HAL_Delay(2000);					///<Run continuously for 2000ms, the task is in operation
	  //taskEXIT_CRITICAL();
	  taskENABLE_INTERRUPTS();
  }
  /* USER CODE END AppTask_LED1 */
}

        构建项目后,将其下载到开发板并运行测试,会发现串口助手上每1s刷新一次,任务函数的临界代码段里,延时2000ms对RTC的唤醒中断响应没有影响,而使用函数HAL_Delay()是有影响的。现象:窗口助手上数据1s更新一次,LED1 2s闪烁一次;

        这是因为执行函数vTaskDelay()会使当前任务进入阻塞状态,FreeRTOS要进行任务调度。而任务的切换是在PendSV的中断里发生的,所以FreeRTOS必须要打开中断,只要打开中断,RTC唤醒中断的ISR就能及时执行。

        所以,在使用taskDISABLE_INTERRUPTS()和taskENABLE_INTERRUPTS()定义的代码段内,或taskENTER_CRITICAL()和taskEXIT_CRITICAL()定义的临界代码段内,不能调用触发任务调度的函数,如延时函数vTaskDelay(),或申请信号量等进行进程间同步的函数。因为发生任务调度时,就会打开中断,从而失去了定义中断屏蔽代码段或临界代码段的意义