一、Cortex-M 中断
-
中断简介 中断是微控制器一个很常见的特性,中断由硬件产生,当中断产生以后 CPU 就会中断当前的流程转而去处理中断服务,Cortex-M 内核的 MCU 提供了一个用于中断管理的嵌套向量中断控制器(NVIC)。 -
中断管理简介 Cortex-M 处理器有多个用于管理中断和异常的可编程寄存器,这些寄存器大多数都在NVIC和系统控制块(SCB)中,CMSIS 将这些寄存器定义为结构体。 以 STM32F103 为例,打开core_cm3.h,有两个结构体,NVIC_Type 和SCB_Type。 NVIC 和 SCB 都位于系统控制空间(SCS)内,SCS 的地址从 0XE000E000 开始,SCB 和 NVIC的地址也在 core_cm3.h 中有定义
我们重点关心的是是三个中断屏蔽寄存器:PRIMASK、FAULTMASK 和 BASEPRI。
- STM32f103优先级设置
在STM32F103系列上面,包括16个内核中断和60个可屏蔽中断,具有16级可编程的中断优先级。 首先,对STM32中断进行分组,组0~4。同时,对每个中断设置一个抢占优先级和一个响应优先级值。
分组配置是在寄存器SCB->AIRCR中配置:
组 | AIRCR[10:8] | IP bit[7:4]分配情况 | 分配结果 |
---|
0 | 111 | 0:4 | 0位抢占优先级,4位响应优先级 | 1 | 110 | 1:3 | 1位抢占优先级,3位响应优先级 | 2 | 101 | 2:2 | 2位抢占优先级,2位响应优先级 | 3 | 100 | 3:1 | 3位抢占优先级,1位响应优先级 | 4 | 011 | 4:0 | 4位抢占优先级,0位响应优先级 |
规则
- 高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。
- 抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断。
- 抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行。
- 如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;
中断优先级分组在msic.h 中有定义:
#define NVIC_PriorityGroup_0 ((uint32_t)0x700)
#define NVIC_PriorityGroup_1 ((uint32_t)0x600)
#define NVIC_PriorityGroup_2 ((uint32_t)0x500)
#define NVIC_PriorityGroup_3 ((uint32_t)0x400)
#define NVIC_PriorityGroup_4 ((uint32_t)0x300)
中断优先级的分组函数:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
调用函数eg:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//第四分组,全是抢占优先级,有16个抢占优先级
设置中断的优先级
定义的结构体
typedef struct
{
uint8_t NVIC_IRQChannel; //设置中断通道
uint8_t NVIC_IRQChannelPreemptionPriority;//设置响应优先级
uint8_t NVIC_IRQChannelSubPriority; //设置抢占优先级
FunctionalState NVIC_IRQChannelCmd; //使能/使能
} NVIC_InitTypeDef;
中断初始化函数
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
举例子:
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=4 ;// 抢占优先级为4
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;// 子优先级位0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据上面指定的参数初始化NVIC寄存器
其他相关指令
static __INLINE void NVIC_SetPendingIRQ(IRQn_Type IRQn);//中断失能寄存器组----->用来失能中断
static __INLINE uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn);//中断挂起寄存器组----->用来挂起中断
static __INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn) //中断解挂寄存器组----->用来解挂中断
static __INLINE uint32_t NVIC_GetActive(IRQn_Type IRQn)//中断激活标志位寄存器组----->如果对应位为1,说明该中断正在执行。
FreeRTOS设置为中断分组4,这样没有响应优先级,只有16个抢占优先级吗,使用起来也比较简单
- 用来屏蔽中断的特殊寄存器
在许多应用中,需要暂时屏蔽所有的中断一执行一些对时序要求严格的任务,这个时候就 可以使用 PRIMASK 寄存器,PRIMASK 用于禁止除 NMI 和 HardFalut 外的所有异常和中断, 汇编编程的时候可以使用 CPS(修改处理器状态)指令修改 PRIMASK 寄存器的数值。 可以用于临界值保护
CPSIE I;
CPSID I;
PRIMASK 和 FAULTMASK 寄存器太粗暴了,直接关闭除复位、NMI 和 HardFault 以外的 其他所有中断,但是在有些场合需要对中断屏蔽进行更细腻的控制,比如只屏蔽优先级低于某 一个阈值的中断。那么这个作为阈值的优先级值存储在哪里呢?在 BASEPRI 寄存器中,不过 如果向 BASEPRI 写 0 的话就会停止屏蔽中断。 注意!FreeRTOS 的开关中断就是操作 BASEPRI 寄存器来实现的!它可以关闭低于某个阈 值的中断,高于这个阈值的中断就不会被关闭!
二、FreeRTOS 中断配置宏
- configPRIO_BITS
此宏用来设置 MCU 使用几位优先级,STM32 使用的是 4 位,因此此宏为 4! - configLIBRARY_LOWEST_INTERRUPT_PRIORITY
此宏是用来设置最低优先级,因此优先级数就是 16 个,最低优先级那就是 15。 - configKERNEL_INTERRUPT_PRIORITY
此宏用来设置内核中断优先级,可以设置滴答定时器和pendSV的优先级。 - configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
此宏用来设置 FreeRTOS 系统可管理的最大优先级,也就是BASEPRI 寄存器说的那个阈值优先级,这个大家可以自由设置,这里我设置为了 5。也就是高于 5 的优先级(优先级数小于 5)不归 FreeRTOS 管理! - configMAX_SYSCALL_INTERRUPT_PRIORITY
此宏是 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 左移 4 位而来的,低于此优先级的中断可以安全的调用 FreeRTOS 的 API 函数,高于此优先级的中断 FreeRTOS 是不能禁止的,中断服务函数也不能调用 FreeRTOS 的 API 函数!
#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
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
配置完成后:
中断优先级越高,数字越小
3、FreeRTOS 开关中断
FreeRTOS 开关中断函数为 portENABLE_INTERRUPTS ()和 portDISABLE_INTERRUPTS(), 这两个函数其实是宏定义,在 portmacro.h 中有定义,如下:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)
4、临界段代码
临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS 系统本身就有很多的临界段代码,这些代码都加了临界段代码保护,我们在写自己的用户程序的时候有些地方也需要添加临界段代码保护。
- 任务级临界段代码保护
taskENTER_CRITICAL()和 taskEXIT_CRITICAL()是任务级的临界代码保护,一个是进入临 界段,一个是退出临界段,这两个函数是成对使用的,这函数的定义如下:
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
- 中断级临界段代码保护
函数 taskENTER_CRITICAL_FROM_ISR()和 taskEXIT_CRITICAL_FROM_ISR()中断级别临界段代码保护,是用在中断服务程序中的,而且这个中断的优先级一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY(系统可管理的优先级)!原因前面已经说了。这两个函数在文件 task.h中有如下定义:
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
强调一下,中断优先级不管设置为多少永远比任务优先级先执行,执行顺序如下图所示
讲解完毕!!!!
|