zl程序教程

您现在的位置是:首页 >  其他

当前栏目

FreeRTOS(五):中断配置和临界段

2023-04-18 14:58:51 时间

FreeRTOS 的中断配置是一个很重要的内容,需要根据所使用的 MCU 来具体配置。这需要 了解 MCU 架构中有关中断的知识,本文结合 Cortex-M 的 NVIC 来讲解 STM32 平台下的 FreeRTOS 中断配置,分为如下几部分:

1、Cortex-M 中断

2、FreeRTOS 中断配置宏

3、FreeRTOS 开关中断

4、临界段代码

5、FreeRTOS 中断测试实验

1、Cortex-M 中断

Cortex-M 内核(STM32)的 MCU 提供了一个用于中断管理的嵌套向量中断控制器(NVIC)。Cotex-M3 的 NVIC 最多支持 240 个 IRQ(中断请求)、1 个不可屏蔽中断(NMI)、1 个 Systick(滴答定时器)定时器中断和多个系统异常。

Cortex-M 处理器有多个用于管理中断和异常的可编程寄存器,这些寄存器大多数都在 NVIC 和系统控制块(SCB)中,CMSIS 将这些寄存器定义为结构体。以 STM32F103 为例,打开 core_cm3.h,有两个结构体,NVIC_Type 和 SCB_Type,就存储了这些信息。

优先级分组定义

当多个中断来临的时候处理器应该响应哪一个中断是由中断的优先级来决定的,高优先级的中断(优先级编号小)肯定是首先得到响应,而且高优先级的中断可以抢占低优先级的中断,这个就是中断嵌套。

Cortex-M 处理器的有些中断是具有固定的优先级的,比如复位、NMI、HardFault,这些中断的优先级都是负数,优先级也是最高的。

Cortex-M 处理器有三个固定优先级和 256 个可编程的优先级,最多有 128 个抢占等级,但是实际的优先级数量是由芯片厂商来决定的。但是,绝大多数的芯片都会精简设计的,以致实际上支持的优先级数会更少,如 8 级、16 级、32 级等,比如 STM32 就只有 16 级优先级

2、FreeRTOS 中断配置宏

1、configPRIO_BITS:设置 MCU 使用几位优先级,STM32 使用的是 4 位,因此此宏为 4

2、configLIBRARY_LOWEST_INTERRUPT_PRIORITY :设置最低优先级。

3、configKERNEL_INTERRUPT_PRIORITY:此宏用来设置内核中断优先级。

4、configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY:来设置 FreeRTOS 系统可管理的最大优先级,是高于 x 的优先级不归 FreeRTOS 管理!

5、configMAX_SYSCALL_INTERRUPT_PRIORITY:低于此优先级的中断可以安全的调用 FreeRTOS 的 API 函数,高于此优先级的中断 FreeRTOS 是不能禁止的,中断服务函数也不能调用 FreeRTOS 的 API 函数!

3、FreeRTOS 开关中断

FreeRTOS 开关中断函数为 portENABLE_INTERRUPTS ()和 portDISABLE_INTERRUPTS(),这两个函数其实是宏定义,在 portmacro.h 中有定义,如下:

#define portDISABLE_INTERRUPTS()    vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS()    vPortSetBASEPRI(0)

可以看出开关中断实际上是通过函数 vPortSetBASEPRI(0)和 vPortRaiseBASEPRI()来实现的。

函数 vPortSetBASEPRI()是向寄存器 BASEPRI 写入一个值,此值作为参数 ulBASEPRI 传 递进来,portENABLE_INTERRUPTS()是开中断,它传递了个 0 给 vPortSetBASEPRI(),根据我们前面讲解 BASEPRI 寄存器可知,结果就是开中断。

函 数 vPortRaiseBASEPRI() 是 向 寄 存 器 BASEPRI 写 入 宏configMAX_SYSCALL_INTERRUPT_PRIORITY , 那 么 优 先 级 低 于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断就会被屏蔽!

4、临界段代码

临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS 系统本身就有很多的临界段代码,这些代码都加了临界段代码保护,我们在写自己的用户程序的时候有些地方也需要添加临界段代码保护。

FreeRTOS 与 临 界 段 代 码 保 护 有 关 的 函 数 有 4 个 :taskENTER_CRITICAL() 、taskEXIT_CRITICAL() 、 taskENTER_CRITICAL_FROM_ISR() 和taskEXIT_CRITICAL_FROM_ISR(),这四个函数其实是宏定义,在 task.h 文件中有定义。这四个函数的区别是前两个是任务级的临界段代码保护,后两个是中断级的临界段代码保护。

任务级临界代码保护使用方法如下:

中断级临界代码保护使用方法如下:

5、FreeRTOS 中断测试实验

设定:FreeRTOS 中优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY的中断会被屏蔽掉,高于的就不会,那么我们就写个简单的例程测试一下。

使用两个定时器,一个优先级为 4,一个优先级为 5,两个定时器每隔 1s 通过串口输出一串字符串。然后在某个任务中关闭中断一段时间,查看两个定时器的输出情况。

main.c

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "FreeRTOS.h"
#include "task.h"

#define START_TASK_PRIO   1
#define START_STK_SIZE    256  
TaskHandle_t StartTask_Handler;
void start_task(void *pvParameters);

#define INTERRUPT_TASK_PRIO  2
#define INTERRUPT_STK_SIZE   256  
TaskHandle_t INTERRUPTTask_Handler;
void interrupt_task(void *p_arg);

int main(void)
{
 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); 
 delay_init();         
 uart_init(115200);     
 LED_Init();
 //起了两个定时器,不停的打印,除非中断被关闭
 TIM3_Int_Init(10000-1,7200-1);  
 TIM5_Int_Init(10000-1,7200-1);  
 
    xTaskCreate((TaskFunction_t )start_task,            
                (const char*    )"start_task",        
                (uint16_t       )START_STK_SIZE,       
                (void*          )NULL,                 
                (UBaseType_t    )START_TASK_PRIO,      
                (TaskHandle_t*  )&StartTask_Handler);              
    vTaskStartScheduler();    
}

void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();         
 
    xTaskCreate((TaskFunction_t )interrupt_task,     
                (const char*    )"interrupt_task",    
                (uint16_t       )INTERRUPT_STK_SIZE, 
                (void*          )NULL,      
                (UBaseType_t    )INTERRUPT_TASK_PRIO,  
                (TaskHandle_t*  )&INTERRUPTTask_Handler); 
 vTaskDelete(StartTask_Handler); 
    taskEXIT_CRITICAL(); 
}

void interrupt_task(void *pvParameters)
{
 static u32 total_num=0;
    while(1)
    {
   printf("秒数",total_num);
   total_num+=1;
   if(total_num==5) 
   {
    printf("关闭中断.............
");
    portDISABLE_INTERRUPTS();   
    delay_xms(5000);      
    printf("打开中断.............
"); 
    portENABLE_INTERRUPTS();
   }
     LED0=~LED0;
     vTaskDelay(1000);
    }
} 

timer.c

#include "timer.h"
#include "led.h"
#include "led.h"
#include "usart.h"

void TIM3_Int_Init(u16 arr,u16 psc)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
 NVIC_InitTypeDef NVIC_InitStructure;

 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); 
 
 TIM_TimeBaseStructure.TIM_Period = arr; 
 TIM_TimeBaseStructure.TIM_Prescaler =psc; 
 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 
 
 TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); 

 NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4;  
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; 
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
 NVIC_Init(&NVIC_InitStructure);  

 TIM_Cmd(TIM3, ENABLE);      
}

void TIM5_Int_Init(u16 arr,u16 psc)
{
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
 NVIC_InitTypeDef NVIC_InitStructure;

 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); 
 
 TIM_TimeBaseStructure.TIM_Period = arr;      
 TIM_TimeBaseStructure.TIM_Prescaler =psc;      
 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;  
 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
 TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);    
 
 TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE );      

 NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;     
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5;   
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;    
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   
 NVIC_Init(&NVIC_InitStructure);         

 TIM_Cmd(TIM5, ENABLE);            
}

void TIM3_IRQHandler(void)
{
 if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) 
 {
  printf("TIM3输出.......
");
 }
 TIM_ClearITPendingBit(TIM3,TIM_IT_Update);  
}

void TIM5_IRQHandler(void)
{
 if(TIM_GetITStatus(TIM5,TIM_IT_Update)==SET) 
 {
  printf("TIM5输出.......
");
 }
 TIM_ClearITPendingBit(TIM5,TIM_IT_Update);  
}

另外还有一些延时函数和串口初始化,这个都是基础的文件,可以直接copy的,就不放出来了。

编译并下载代码到开发板中,打开串口调试助手查看数据输出:

一开始没有关闭中断,所以 TIM3 和 TIM5 都正常运行,红框所示部分。当任务 interrupt_task()运行了 5 次以后就关闭了中断,此时由于 TIM5 的中断优先级为 5,等于 configMAX_SYSCALL_INTERRUPT_PRIORITY,因此 TIM5 被关闭。但是,TIM3 的中断优先级高于 configMAX_SYSCALL_INTERRUPT_PRIORITY,不会被关闭,所以 TIM3 正常运行,绿框所示部分。中断关闭 5S 以后就会调用函数 portENABLE_INTERRUPTS()重新打开中断,重新打开中断以后 TIM5 恢复运行,蓝框所示部分。