第四章 FreeRTOS中断配置和临界段

warren
2025-02-24 / 0 评论 / 8 阅读 / 正在检测是否收录...

4.1 Cortex-M中断

4.1.1 中断简介

 Cortex-M 内核的 MCU 提供了一个用于中断管理的嵌套向量中断控制器(NVIC).
 Cortex-M3内核支持256个中断,其中包括240个外部中断+16个内核中断
 其中16个内核中断包括一个不可屏蔽中断(NMI)、一个Systick定时器中断(滴答定时器)和多个系统异常中断

4.1.2 中断管理简介

 Cortex-M 处理器有多个用于管理中断和异常的可编程寄存器。这些寄存器大多数都在NVIC 和系统控制块(SCB)

typedef struct
{
  __IO uint32_t ISER[8];                      /*!< Offset: 0x000  Interrupt Set Enable Register           */
       uint32_t RESERVED0[24];                                   
  __IO uint32_t ICER[8];                      /*!< Offset: 0x080  Interrupt Clear Enable Register         */
       uint32_t RSERVED1[24];                                    
  __IO uint32_t ISPR[8];                      /*!< Offset: 0x100  Interrupt Set Pending Register          */
       uint32_t RESERVED2[24];                                   
  __IO uint32_t ICPR[8];                      /*!< Offset: 0x180  Interrupt Clear Pending Register        */
       uint32_t RESERVED3[24];                                   
  __IO uint32_t IABR[8];                      /*!< Offset: 0x200  Interrupt Active bit Register           */
       uint32_t RESERVED4[56];                                   
  __IO uint8_t  IP[240];                      /*!< Offset: 0x300  Interrupt Priority Register (8Bit wide) */
       uint32_t RESERVED5[644];                                  
  __O  uint32_t STIR;                         /*!< Offset: 0xE00  Software Trigger Interrupt Register     */
}  NVIC_Type;  
  • ISER:向该寄存器的对应bit写入1,则会使能对应的中断(256位)
  • ICER:向该寄存器的对应bit写入1,则会除能对应的中断(256位)
  • ISPR:向该寄存器的对应bit写入1,则会挂起对应的中断(256位)
  • ICPR:向该寄存器的对应bit写入1,则会解挂对应的中断(256位)
  • IABR:只读属性,若对应bit为1,则表示该中断正在执行(256位)
  • IP:中断优先级寄存器,只可以配置外部中断(240位)
  • STIR:软件中断触发中断寄存器,向该寄存器写入标号,则标号对应的中断会被悬起。

    typedef struct
    {
    __I  uint32_t CPUID;                        /*!< Offset: 0x00  CPU ID Base Register                                  */
    __IO uint32_t ICSR;                         /*!< Offset: 0x04  Interrupt Control State Register                      */
    __IO uint32_t VTOR;                         /*!< Offset: 0x08  Vector Table Offset Register                          */
    __IO uint32_t AIRCR;                        /*!< Offset: 0x0C  Application Interrupt / Reset Control Register        */
    __IO uint32_t SCR;                          /*!< Offset: 0x10  System Control Register                               */
    __IO uint32_t CCR;                          /*!< Offset: 0x14  Configuration Control Register                        */
    __IO uint8_t  SHP[12];                      /*!< Offset: 0x18  System Handlers Priority Registers (4-7, 8-11, 12-15) */
    __IO uint32_t SHCSR;                        /*!< Offset: 0x24  System Handler Control and State Register             */
    __IO uint32_t CFSR;                         /*!< Offset: 0x28  Configurable Fault Status Register                    */
    __IO uint32_t HFSR;                         /*!< Offset: 0x2C  Hard Fault Status Register                            */
    __IO uint32_t DFSR;                         /*!< Offset: 0x30  Debug Fault Status Register                           */
    __IO uint32_t MMFAR;                        /*!< Offset: 0x34  Mem Manage Address Register                           */
    __IO uint32_t BFAR;                         /*!< Offset: 0x38  Bus Fault Address Register                            */
    __IO uint32_t AFSR;                         /*!< Offset: 0x3C  Auxiliary Fault Status Register                       */
    __I  uint32_t PFR[2];                       /*!< Offset: 0x40  Processor Feature Register                            */
    __I  uint32_t DFR;                          /*!< Offset: 0x48  Debug Feature Register                                */
    __I  uint32_t ADR;                          /*!< Offset: 0x4C  Auxiliary Feature Register                            */
    __I  uint32_t MMFR[4];                      /*!< Offset: 0x50  Memory Model Feature Register                         */
    __I  uint32_t ISAR[5];                      /*!< Offset: 0x60  ISA Feature Register                                  */
    } SCB_Type;    
  • 该寄存器暂未学习清除,不记录笔记;待后续补充!!!
     内嵌向量中断控制器(NVIC)和系统控制块(SCB)在地址分配上都属于系统控制空间(SCS)内;SCS的基地址为0xE000E000。NVIC的偏移为0x100;SCB的偏移为0xD00.

    4.1.3 优先级分区定义

     STM32中的每个中断都有一个优先级编号。优先级编号越小的中断优先执行。高优先级的中断可以抢占低优先级的中断。并且Cortex-M 处理器的有些中断是具有固定的优先级的,比如复位、 NMI、HardFault,这些中断的优先级都是负数,优先级也是最高的。
     Cortex-M 处理器有三个固定优先级和 256 个可编程的优先级(并不是指有256个优先级可编程的中断),最多有 128 个抢占等级。

  • 256个可编程的优先级指IP寄存器的位宽为8Bit;2^8=256
  • 128个抢占等级是2^7=128,因为相应优先级(亚优先级)至少1Bit
    上面这些对于STM32而言就是扯淡。因为STM32只有4个bit来表示优先级。所以对于STM32而言就没有那么多了。不管怎么裁剪。有效位永远是高位对齐。
    在NVIC中有一个应用程序中断及复位控制寄存器(AIRCR),AIRCR 寄存器里面有个位段名为“优先级组。如下图
    m7j5hfmf.png
    可以看到它的位宽为3bit。可以设置0-7共8个分组。不同的分组对应的优先级和亚优先级分组如下
    m7j5lq6y.png
    m7j65jbl.png
    可以看到抢占优先级最大7bit,最少0bit
    但是在STM32中,我们只有高4bit来配置中断的优先级。所以我们只选中分组3-分组7。共5组来配置中断优先级。则对STM32的优先级分组就只有5个。
    STM32中分组0-------PRIGROUP中的分组7
    STM32中分组1-------PRIGROUP中的分组6
    STM32中分组2-------PRIGROUP中的分组5
    STM32中分组3-------PRIGROUP中的分组4
    STM32中分组4-------PRIGROUP中的分组3
    【注意】 FreeRTOS得到中断配置没有处理亚优先级这种情况。所以我们只能给STM32配置分区4。

    4.1.4 优先级设置

     4个相临的寄存器可以拼成一个32位的寄存器,因此地址0xE000_ED20~0xE000_ED23 这四个寄存器就可以拼接成一个地址为0xE000_ED20的32位寄存器。 这一点很重要!
     因为 FreeRTOS 在设置 PendSV 和 SysTick 的中断优先级的时候都是直接操作的地址 0xE000_ED20

    4.1.5 用于中断屏蔽的特殊寄存器

    PRIMASK、FAULTMASK 和 BASEPRI 这三个寄存器用于控制中断的屏蔽。三个有一些不同如下:
    PRIMASK: 置1后 禁止除 NMI 和 HardFalut 外的所有异常和中断。
    FAULTMASK: 置1后,禁止除 NMI外的所有异常和中断。
    BASEPRI:填入设定的值,只屏蔽优先级低于该值的中断。向 BASEPRI 写 0 的话就会停止屏蔽中断。

    4.2 FreeRTOS中断配置宏

  • configPRIO_BITS
    该宏用来设置MCU使用几位优先级。在STM32中使用4BIT。所以该宏为4
  • configLIBRARY_LOWEST_INTERRUPT_PRIORITY
    此宏用来设置最低优先级。前面配置了STM32使用4bit来表示优先级;则最低优先级为2^4-1;即此宏为15
  • configKERNEL_INTERRUPT_PRIORITY
    此宏用来设置内核中断优先级;内核的中断优先级就是系统滴答定时器的优先级和PendSV中断的优先级。
  • configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
    此宏用来设置 FreeRTOS 系统可管理的最大优先级;也就是高于该值的优先级(优先级数小于该值)不归 FreeRTOS 管理
  • configMAX_SYSCALL_INTERRUPT_PRIORITY
    低于此优先级的中断可以安全的调用 FreeRTOS 的 API 函数,高于此优先级的中断 FreeRTOS 是不能禁止的,中断服务函数也不能调用 FreeRTOS 的 API 函数。
    目的:确保 FreeRTOS 内核的关键部分不会被更高优先级的中断打断,这些中断可能会调用 FreeRTOS API 函数,从而导致竞态条件或数据损坏

    4.3 FreeRTOS开关中断

    FreeRTOS 开关中断函数为 portENABLE_INTERRUPTS ()和 portDISABLE_INTERRUPTS()
    它通过往BASEPRI写入0来实现打开中断;
    通过往BASEPRI中写入configMAX_SYSCALL_INTERRUPT_PRIORITY来实现屏蔽configMAX_SYSCALL_INTERRUPT_PRIORITY优先级以下的中断

    4.4 临界段代码

    临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段!
    FreeRTOS 与 临 界 段 代 码 保 护 有 关 的 函 数 有 4 个 : taskENTER_CRITICAL() 、taskEXIT_CRITICAL() 、 taskENTER_CRITICAL_FROM_ISR() 和taskEXIT_CRITICAL_FROM_ISR()
    分别为:任务级临界段代码保护 和 中断级临界段代码保护

  • 任务级临界段代码保护是通过一个全局变量uxCriticalNesting来实现中断管理。uxCriticalNesting==1时候关闭中断;uxCriticalNesting==0时候开启中断;
    即保证了在有多个临界段代码的时候不会因为某一个临界段代码的退出而打乱其他临界段的保护,只有所有的临界段代码都退出以后才会使能中断。
  • 中断级临界段代码保护
    这个就比较粗暴了。直接向BASEPRI寄存器中写入configMAX_SYSCALL_INTERRUPT_PRIORITY来实现关闭中断。
    进入时候读出BASEPRI的值并保存该值,同时向BASEPRI寄存器写入configMAX_SYSCALL_INTERRUPT_PRIORITY
    退出时候向BASEPRI寄存器写入进入时刻读出来的值。

    4.5 FreeRTOS中断测试实验

    实验目的:用来验证FreeRTOS的APIportENABLE_INTERRUPTS ()和 portDISABLE_INTERRUPTS()以及宏configMAX_SYSCALL_INTERRUPT_PRIORITY。
    实验方法:创建两个任务task1;在task1中在运行5秒后关闭中断。在运行10秒后打开中断。并且开启两个定时器中断。在中断服务函数中打印"timer1 is runing" 和 "timer2 is runing"。
    configMAX_SYSCALL_INTERRUPT_PRIORITY配置为5;定时器1中断优先级为4;定时器2中断优先级为5;
    实验设想:前5秒,有定时器1和定时器2的输出。5-10秒只有定时器1的输出;10秒以后定时器12都有输出。
    实验代码如下

#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);//设置系统中断优先级分组4     
    delay_init();                        //延时函数初始化     
    uart_init(115200);                    //初始化串口
    LED_Init();                              //初始化LED
    TIM3_Int_Init(10000-1,7200-1);        //初始化定时器3,定时器周期1S
    TIM5_Int_Init(10000-1,7200-1);        //初始化定时器5,定时器周期1S
    
    //创建开始任务
    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)
    {
        total_num+=1;
        if(total_num==5) 
        {
            printf("关闭中断.............\r\n");
            portDISABLE_INTERRUPTS();                //关闭中断
            delay_xms(5000);                        //延时5s
            printf("打开中断.............\r\n");    //打开中断
            portENABLE_INTERRUPTS();
        }
        LED0=~LED0;
        vTaskDelay(1000);
    }
} 
#include "timer.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???.......\r\n");
    }
    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}


void TIM5_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM5,TIM_IT_Update)==SET) //????ж?
    {
        printf("Timer 5 ???\r\n");
    }
    TIM_ClearITPendingBit(TIM5, TIM_IT_Update);
}
0

评论 (0)

取消