06.FreeRTOS中断管理

06. FreeRTOS中断管理

1. 中断的基础知识

  • 什么是中断:

    在这里插入图片描述

    ARM Cortex-M的NVIC最大可支持256个中断源,其中包括16个系统中断和240个外部中断。然而芯片厂商一般情况下都用不完这些资源,以正点原子的战舰开发板为例,所使用的 STM32F103ZET6芯片就只用到了10个系统中断和60个外部中断。

  • 中断优先级管理:

    NVIC在CMSIS中的定义

    typedef struct
    {
         __IOM uint32_t ISER[8U]; /* 中断使能寄存器 */
         uint32_t RESERVED0[24U];
         __IOM uint32_t ICER[8U]; /* 中断除能寄存器 */
         uint32_t RSERVED1[24U];
         __IOM uint32_t ISPR[8U]; /* 中断使能挂起寄存器 */
         uint32_t RESERVED2[24U];
         __IOM uint32_t ICPR[8U]; /* 中断除能挂起寄存器 */
         uint32_t RESERVED3[24U];
         __IOM uint32_t IABR[8U]; /* 中断有效位寄存器 */
         uint32_t RESERVED4[56U];
         __IOM uint8_t IP[240U];  /* 中断优先级寄存器 */
         uint32_t RESERVED5[644U];
         __OM uint32_t STIR;      /* 软件触发中断寄存器 */
    } NVIC_Type;
    

    其中,__IOM uint8_t IP[240U];为uint8_t类型的数组,数组一共有240个元素,数组中每一个8bit的元素就用来配置对应的外部中断的优先级。

    在这里插入图片描述

    STM32中每个中断的优先级就由抢占优先级和子优先级共同组成,使用中断优先级配置寄存器的高4位来配置抢占优先级和子优先级,一共由5种分配方式,对应这中断优先级分组的5个组,优先级分组的5种分组情况在HAL中进行了定义,如下所示:

    #define NVIC_PRIORITYGROUP_0 0x00000007U /* 优先级分组 0 */
    #define NVIC_PRIORITYGROUP_1 0x00000006U /* 优先级分组 1 */
    #define NVIC_PRIORITYGROUP_2 0x00000005U /* 优先级分组 2 */
    #define NVIC_PRIORITYGROUP_3 0x00000004U /* 优先级分组 3 */
    #define NVIC_PRIORITYGROUP_4 0x00000003U /* 优先级分组 4 */
    

    在这里插入图片描述

  • 三个系统中断优先级配置寄存器:

    在这里插入图片描述

    1. SHPR1

      在这里插入图片描述

    2. SHPR2

      在这里插入图片描述

    3. SHPR3

      在这里插入图片描述

  • 三个中断屏蔽寄存器:

    在这里插入图片描述

    1. PRIMASK

      作用: PRIMASK寄存器有32bit,但只有bit0有效,是可读可写的,将PRIMASK寄存器设置为1用于屏蔽除NMIHardFault外的所有异常和中断,将PRIMASK寄存器清0用于使能中断。

      //用法一:
      CPSIE I /* 清除 PRIMASK(使能中断) */
      CPSID I /* 设置 PRIMASK(屏蔽中断) */
      
      //用法二:
      MRS R0, PRIMASK /* 读取 PRIMASK 值 */
      MOV R0, #0
      MSR PRIMASK, R0 /* 清除 PRIMASK(使能中断) */
      MOV R0, #1
      MSR PRIMASK, R0 /* 设置 PRIMASK(屏蔽中断) */
      
      //用法三:
      __get_PRIMASK();   /* 读取 PRIMASK 值 */
      __set_PRIMASK(0U); /* 清除 PRIMASK(使能中断) */
      __set_PRIMASK(1U); /* 设置 PRIMASK(屏蔽中断) */
      
    2. FAULTMASK

      作用: FAULTMASK寄存器有32bit,但只有bit0有效,也是可读可写的,将FAULTMASK寄存器设置为1用于屏蔽除NMI外的所有异常和中断,将FAULTMASK寄存器清零用于使能中断。

      //用法一:
      CPSIE F /* 清除 FAULTMASK(使能中断) */
      CPSID F /* 设置 FAULTMASK(屏蔽中断) */
      
      //用法二:
      MRS R0, FAULTMASK /* 读取 FAULTMASK 值 */
      MOV R0, #0
      MSR FAULTMASK, R0 /* 清除 FAULTMASK(使能中断) */
      MOV R0, #1
      MSR FAULTMASK, R0 /* 设置 FAULTMASK(屏蔽中断) */
      
      //用法三:
      __get_FAULTMASK();   /* 读取 FAULTMASK 值 */
      __set_FAULTMASK(0U); /* 清除 FAULTMASK(使能中断) */
      __set_FAULTMASK(1U); /* 设置 FAULTMASK(屏蔽中断) */
      
    3. BASEPRI

      作用: BASEPRI有32bit,但只有低8位[7:0]有效,也是可读可写的。BASEPRI寄存器比起PRIMASK和FAULTMASK寄存器直接屏蔽掉大部分中断的方式,BASEPRI寄存器的功能显得更加细腻,BASEPRI用于设置一个中断屏蔽的阈值,设置好BASEPRI后,中断优先级低于BASEPRI的中断就都会被屏蔽掉FreeRTOS就是使用BASEPRI寄存器来管理受FreeRTOS管理的中断的,而不受FreeRTOS管理的中断,则不受FreeRTOS的影响。

      //用法一:
      MRS R0, BASEPRI /* 读取 BASEPRI 值 */
      MOV R0, #0
      MSR BASEPRI, R0 /* 清除 BASEMASK(使能中断) */
      MOV R0, #0x60 /* 举例 */
      MSR BASEPRI, R0 /* 设置 BASEMASK(屏蔽优先级低于 0x60 的中断) */
      
      //用法二:
      __get_BASEPRI(); /* 读取 BASEPRI 值 */
      __set_BASEPRI(0); /* 清除 BASEPRI(使能中断) */
      __set_BASEPRI(0x60); /* 设置 BASEPRI(屏蔽优先级小于 0x60 的中断) */
      

2. FreeRTOS中断配置项

配置项功能
configPRIO_BITSMCU的8位优先级配置寄存器实际使用的位数
具体配置为4
configLIBRARY_LOWEST_INTERRUPT_PRIORITYMCU的最低中断优先等级,对于STM32,在使用FreeRTOS时,建议将中断优先级分组设置为组4,此时中断的最低优先级为15。此宏定义用于辅助配置宏 configKERNEL_INTERRUPT_PRIORITY
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY用于设置FreeRTOS可管理中断的最高优先级,当中断的优先级数值小于 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY时,此中断不受FreeRTOS管理。此宏定义用于辅助配置宏configMAX_SYSCALL_INTERRUPT_PRIORITY
configKERNEL_INTERRUPT_PRIORITYMCU的最低中断优先等级在中断优先级配置寄存器中的值,对于STM32,即宏configLIBRARY_LOWEST_INTERRUPT_PRIORITY偏移4bit的值。
configMAX_SYSCALL_INTERRUPT_PRIORITYFreeRTOS可管理中断的最高优先等级在中断优先级配置寄存器中的值,对于STM32,即宏configLIBRARY_LOWEST_INTERRUPT_PRIORITY偏移4bit的值。
configMAX_API_CALL_INTERRUPT_PRIORITY与宏configMAX_SYSCALL_INTERRUPT_PRIORITY是等价的。

FreeRTOSConfig.h中具体配置:

/* 中断嵌套行为配置 */
#ifdef __NVIC_PRIO_BITS
    #define configPRIO_BITS __NVIC_PRIO_BITS
#else
    #define configPRIO_BITS 4
#endif

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY         15                  // 中断最低优先级 
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY    5                   // FreeRTOS可管理的最高中断优先级 
#define configKERNEL_INTERRUPT_PRIORITY                 ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY            ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_API_CALL_INTERRUPT_PRIORITY           configMAX_SYSCALL_INTERRUPT_PRIORITY

FreeRTOS配置PendSV和Systick中断优先级:
在这里插入图片描述

FreeRTOS中开关中断的宏定义:

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

函数vPortRaiseBASEPRI()

{
 uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
 
 __asm
 {
 /* 设置 BasePRI 寄存器 */
 msr basepri, ulNewBASEPRI
 dsb    //数据同步隔离
 isb    //指令同步隔离

BASEPRI 寄存器设置为宏configMAX_SYSCALL_INTERRUPT_PRIORITY配置的制值。

函数vPortSetBASEPRI()

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
	 __asm
 	{
 	/* 设置 BasePRI 寄存器 */
 	msr basepri, ulBASEPRI
 	}
}

函数vPortSetBASEPRI()BASEPRI寄存器设置为指定的值

  • 临界区:

    临界区是指那些必须完整运行的区域,在临界区中的代码必须完整运行,不能被打断。例如一些使用软件模拟的通信协议,通信协议在通信时,必须严格按照通信协议的时序进行,不能被打断。FreeRTOS在进出临界区的时候,通过关闭和打开受FreeRTOS管理的中断,以保护临界区中的代码。FreeRTOS的源码中就包含了许多临界区的代码,这部分代码都是用临界区进行保护,用户在使用FreeRTOS编写应用程序的时候,也要注意一些不能被打断的操作,并为这部分代码加上临界区进行保护。

    /* 进入临界区 */
    #define  taskENTER_CRITICAL()    portENTER_CRITICAL()
    #define  portENTER_CRITICAL()    vPortEnterCritical()
    
    /* 中断中进入临界区 */
    #define  taskENTER_CRITICAL_FROM_ISR()       portSET_INTERRUPT_MASK_FROM_ISR()
    #define  portSET_INTERRUPT_MASK_FROM_ISR()   ulPortRaiseBASEPRI()
    
    /* 退出临界区 */
    #define  taskEXIT_CRITICAL()     portEXIT_CRITICAL()
    #define  portEXIT_CRITICAL()     vPortExitCritical()
    
    /* 中断中退出临界区 */
    #define  taskEXIT_CRITICAL_FROM_ISR(x)         portCLEAR_INTERRUPT_MASK_FROM_ISR(x)
    #define  portCLEAR_INTERRUPT_MASK_FROM_ISR(x)  vPortSetBASEPRI(x)
    
    void vPortEnterCritical( void )
    {
    	 /* 关闭受 FreeRTOS 管理的中断 */
    	 portDISABLE_INTERRUPTS();
    	 /* 临界区支持嵌套 */
    	 uxCriticalNesting++;
     
     	if( uxCriticalNesting == 1 )
     	{
     		/* 这个函数不能在中断中调用 */
     		configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
     	}
    }
    

    函数 vPortEnterCritical()进入临界区就是关闭中断,支持嵌套

    static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
    {
     	uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
     
    	 __asm
    	 {
     		/* 读取 BASEPRI 寄存器 */
            mrs ulReturn, basepri
            /* 设置 BASEPRI 寄存器 */
             msr basepri, ulNewBASEPRI
            dsb
            isb
     	}
     
     	return ulReturn;
    }
    

    将BASEPRI寄存器设置为宏 configMAX_SYSCALL_INTERRUPT_PRIORITY的值,以达到关闭中断的效果,不支持嵌套。

    void vPortExitCritical( void )
    {
    	 /* 必须是进入过临界区才能退出 */
         configASSERT( uxCriticalNesting );
     	uxCriticalNesting--;
     
    	 if( uxCriticalNesting == 0 )
    	 {
     		/* 打开中断 */
     		portENABLE_INTERRUPTS();
     	}
    }
    

3. 代码实现

  • 程序流程图:
    在这里插入图片描述

  • 代码实现

    定时器相关配置:

    /*定时器6中断初始化*/
    void btim_tim6_int_init(uint16_t psc, uint16_t per)
    {
        g_tim6_handle.Instance = TIM6;
        g_tim6_handle.Init.Prescaler = psc;
        g_tim6_handle.Init.Period = per;
        
        HAL_TIM_Base_Init(&g_tim6_handle);
        
        HAL_TIM_Base_Start_IT(&g_tim6_handle);
    }
    
    /*定时器7中断初始化*/
    void btim_tim7_int_init(uint16_t psc, uint16_t per)
    {
        g_tim7_handle.Instance = TIM7;
        g_tim7_handle.Init.Prescaler = psc;
        g_tim7_handle.Init.Period = per;
        
        HAL_TIM_Base_Init(&g_tim7_handle);
        
        HAL_TIM_Base_Start_IT(&g_tim7_handle);  //使能定时器和定时器更新中断
    }
    
    /*定时器基础MSP初始化函数*/
    void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
    {
        if (htim->Instance == TIM6)
        {
            __HAL_RCC_TIM6_CLK_ENABLE();
            HAL_NVIC_SetPriority(TIM6_IRQn, 6, 0);
            HAL_NVIC_EnableIRQ(TIM6_IRQn);
        }
    	
        if(htim->Instance == TIM7)
        {
            __HAL_RCC_TIM7_CLK_ENABLE();
            HAL_NVIC_SetPriority(TIM7_IRQn, 4, 0);
            HAL_NVIC_EnableIRQ(TIM7_IRQn);
        }
    }
    
    /*定时器6中断服务函数*/
    void TIM6_IRQHandler()
    {
        HAL_TIM_IRQHandler(&g_tim6_handle);
    }
    
    /*定时器7中断服务函数*/
    void TIM7_IRQHandler()
    {
        HAL_TIM_IRQHandler(&g_tim7_handle);
    }
    
    /*定时器溢出中断回调函数*/
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
    	if (htim->Instance == TIM6)
        {
            printf("优先级6的正在运行!\r\n");
        }
    	
        if(htim->Instance == TIM7)
        {
            printf("优先级为4的中断正在运行!\r\n");
        }
    }
    

    相关任务的创建:

    /*创建开始任务*/
    void freertos_Dynamic_Create(void)
    {
    	xTaskCreate((TaskFunction_t        )   start_task,           //指向任务函数的指针
    				(char *                )   "start_task",         //任务名称
    				(configSTACK_DEPTH_TYPE)   START_TASK_STACK_SIZE,//任务堆栈大小,字节为单位
    				(void *                )   NULL,                 //传递给任务函数的参数
    				(UBaseType_t           )   START_TASK_PRIO,      //任务优先级
    				(TaskHandle_t *        )   &start_task_handler    //任务句柄:任务控制块
    	);
    				
        vTaskStartScheduler();  //开启任务调度
    }
    
    /*开始任务*/
    void start_task(void* pvParamter)
    {
    	taskENTER_CRITICAL();   // 进入临界区 
    	
    	xTaskCreate((TaskFunction_t        )   task1,                 //指向任务函数的指针
    				(char *                )   "task1",               //任务名称
    				(configSTACK_DEPTH_TYPE)   TASK1_TASK_STACK_SIZE, //任务堆栈大小,字节为单位
    				(void *                )   NULL,                  //传递给任务函数的参数
    				(UBaseType_t           )   TASK1_TASK_PRIO,       //任务优先级
    				(TaskHandle_t *        )   &task1_task_handler    //任务句柄:任务控制块
    	);
    				
    	vTaskDelete(NULL);      //删除开始任务
    				
    	taskEXIT_CRITICAL();    // 退出临界区 
    }c
    
    /*任务一:开关中断*/
    void task1(void* pvParamter)
    {
    	uint32_t task1_num = 0;
    	
    	while(1)
    	{
    		if(++task1_num == 5)
    		{
    			printf("关中断!!!\r\n");
    			portDISABLE_INTERRUPTS();
    			delay_ms(5000);
    			printf("开中断!!!\r\n");
    			portENABLE_INTERRUPTS();
    		}
    		vTaskDelay(1000);
    	}
    }
    
  • 实验结果:

    在这里插入图片描述

  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值