FreeRtos详解学习日记5:FreeRTOS中断配置

一、Cortex-M 中断

1.0 中断简介

中断是微控制器一个很常见的特性,中断由硬件产生,当中断产生以后 CPU 就会中断当前
的流程转而去处理中断服务,Cortex-M 内核的 MCU 提供了一个用于中断管理的嵌套向量中断
控制器(NVIC)。
Cotex-M3 和 M4 的 NVIC 最多支持 240 个 IRQ(中断请求)、1 个不可屏蔽中断(NMI)、1 个
Systick(滴答定时器)定时器中断和多个系统异常。

2.0 中断管理简介

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

typedef struct
{
__IOM uint32_t ISER[8U]; /*!< Offset: 0x000 (R/W) Interrupt Set Enable Register */
uint32_t RESERVED0[24U];
__IOM uint32_t ICER[8U]; /*!< Offset: 0x080 (R/W) Interrupt Clear Enable Register */
uint32_t RSERVED1[24U];
__IOM uint32_t ISPR[8U]; /*!< Offset: 0x100 (R/W) Interrupt Set Pending Register */
uint32_t RESERVED2[24U];
__IOM uint32_t ICPR[8U]; /*!< Offset: 0x180 (R/W) Interrupt Clear Pending Register */
uint32_t RESERVED3[24U];
__IOM uint32_t IABR[8U]; /*!< Offset: 0x200 (R/W) Interrupt Active bit Register */
uint32_t RESERVED4[56U];
__IOM uint8_t IP[240U]; /*!< Offset: 0x300 (R/W) Interrupt Priority Register (8Bit wide) */
uint32_t RESERVED5[644U];
__OM uint32_t STIR; /*!< Offset: 0xE00 ( /W) Software Trigger Interrupt Register */
} NVIC_Type;
typedef struct
{
__IM uint32_t CPUID; /*!< Offset: 0x000 (R/ ) CPUID Base Register */
__IOM uint32_t ICSR; /*!< Offset: 0x004 (R/W) Interrupt Control and State Register */
__IOM uint32_t VTOR; /*!< Offset: 0x008 (R/W) Vector Table Offset Register */
__IOM uint32_t AIRCR; /*!< Offset: 0x00C (R/W) Application Interrupt and Reset
Control Register */
__IOM uint32_t SCR; /*!< Offset: 0x010 (R/W) System Control Register */
__IOM uint32_t CCR; /*!< Offset: 0x014 (R/W) Configuration Control Register */
__IOM uint8_t SHP[12U]; /*!< Offset: 0x018 (R/W) System Handlers Priority
Registers (4-7, 8-11, 12-15) */
__IOM uint32_t SHCSR; /*!< Offset: 0x024 (R/W) System Handler Control and
State Register */
__IOM uint32_t CFSR; /*!< Offset: 0x028 (R/W) Configurable Fault Status Register */
__IOM uint32_t HFSR; /*!< Offset: 0x02C (R/W) HardFault Status Register */
__IOM uint32_t DFSR; /*!< Offset: 0x030 (R/W) Debug Fault Status Register */
__IOM uint32_t MMFAR; /*!< Offset: 0x034 (R/W) MemManage Fault Address Register */
__IOM uint32_t BFAR; /*!< Offset: 0x038 (R/W) BusFault Address Register */
__IOM uint32_t AFSR; /*!< Offset: 0x03C (R/W) Auxiliary Fault Status Register */
__IM uint32_t PFR[2U]; /*!< Offset: 0x040 (R/ ) Processor Feature Register */
__IM uint32_t DFR; /*!< Offset: 0x048 (R/ ) Debug Feature Register */
__IM uint32_t ADR; /*!< Offset: 0x04C (R/ ) Auxiliary Feature Register */
__IM uint32_t MMFR[4U]; /*!< Offset: 0x050 (R/ ) Memory Model Feature Register */
__IM uint32_t ISAR[5U]; /*!< Offset: 0x060 (R/ ) Instruction Set Attributes Register */
uint32_t RESERVED0[5U];
__IOM uint32_t CPACR; /*!< Offset: 0x088 (R/W) Coprocessor Access Control Register */
} SCB_Type;

NVIC 和 SCB 都位于系统控制空间(SCS)内,SCS 的地址从 0XE000E000 开始,SCB 和 NVIC
的地址也在 core_cm4.h 中有定义,如下:

#define SCS_BASE (0xE000E000UL) /*!< System Control Space Base Address */
#define NVIC_BASE (SCS_BASE + 0x0100UL) /*!< NVIC Base Address */
#define SCB_BASE (SCS_BASE + 0x0D00UL) /*!< System Control Block Base Address
#define SCnSCB ((SCnSCB_Type*) SCS_BASE) /*!< System control Register not in SCB */
#define SCB ((SCB_Type *) SCB_BASE) /*!< SCB configuration struct */
#define NVIC ((NVIC_Type *) NVIC_BASE ) /*!< NVIC configuration struct *//

以上的中断控制寄存器我们在移植 FreeRTOS 的时候是不需要关心的,这里只是提一下,
大家要是感兴趣的话可以参考 Cortex-M 的权威指南,我们重点关心的是是三个中断屏蔽寄存
器:PRIMASK、FAULTMASK 和 BASEPRI,这三个寄存器后面会详细的讲解。

3.0 优先级分组定义

当多个中断来临的时候处理器应该响应哪一个中断是由中断的优先级来决定的,高优先级
的中断(优先级编号小)肯定是首先得到响应,而且高优先级的中断可以抢占低优先级的中断,
这个就是中断嵌套。Cortex-M 处理器的有些中断是具有固定的优先级的,比如复位、NMI、
HardFault,这些中断的优先级都是负数,优先级也是最高的。
Cortex-M 处理器有三个固定优先级和 256 个可编程的优先级,最多有 128 个抢占等级,但
是实际的优先级数量是由芯片厂商来决定的。但是,绝大多数的芯片都会精简设计的,以致实
际上支持的优先级数会更少,如 8 级、16 级、32 级等,比如 STM32 就只有 16 级优先级。在设
计芯片的时候会裁掉表达优先级的几个低端有效位,以减少优先级数,所以不管用多少位来表
达优先级,都是 MSB 对齐的,如图  就是使用三位来表达优先级。

Bit0~Bit4 没有实现,所以读它们总是返回零,写如它们的话则会忽略写入的值。因此,对于 3 个位的情况,可是使用的优先级就是 8 个:0X00(最高优先级)、0X20、0X40、
0X60、0X80、0XA0、0XC0 和 0XE0。注意,这个是芯片厂商来决定的!不是我们能决定的,
比如 STM32 就选择了 4 位作为优先级!
有读者可能就会问,优先级配置寄存器是 8 位宽的,为什么却只有 128 个抢占等级?8 位
不应该是 256 个抢占等级吗?为了使抢占机能变得更可控,Cortex-M 处理器还把 256 个优先级
按位分为高低两段:抢占优先级(分组优先级)和亚优先级(子优先级),NVIC 中有一个寄存器是
“应用程序中断及复位控制寄存器(AIRCR)”,AIRCR 寄存器里面有个位段名为“优先级组”

PRIGROUP 就是优先级分组,它把优先级分为两个位段:MSB 所在的位段(左
边的)对应抢占优先级,LSB 所在的位段(右边的)对应亚优先级

在看一下 STM32 的优先级分组情况,我们前面说了 STM32 使用了 4 位,因此最多有 5 组
优先级分组设置,这 5 个分组在 stm32f4xx_hal_cortex.h 中有定义

#define NVIC_PRIORITYGROUP_0 ((uint32_t)0x00000007) /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PRIORITYGROUP_1 ((uint32_t)0x00000006) /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PRIORITYGROUP_2 ((uint32_t)0x00000005) /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PRIORITYGROUP_3 ((uint32_t)0x00000004) /*!< 3 bits for pre-emption priority1 bits for subpriority */
#define NVIC_PRIORITYGROUP_4 ((uint32_t)0x00000003) /*!< 4 bits for pre-emption priority

可以看出 STM32 有 5 个分组,但是一定要注意!STM32 中定义的分组 0 对应的值是 7!
如果我们选择分组 4,即 NVIC_PRIORITYGROUP_4 的话,那 4 位优先级就都全是抢占优先级
了,没有亚优先级,那么就有 0~15 共 16 个优先级。而移植 FreeRTOS 的时候我们配置的就是
组 4,ST 官方默认的也是组 4,优先级分组配置在 HAL_Init()中

如果使用 ALIENTEK 的基础例程的话默认配置的组 2,所以在将基础例程中的外设驱动移
植到 FreeRTOS 下面的时候需要修改优先级配置。主要是 FreeRTOS 的中断配置没有处理亚优
先级这种情况,所以只能配置为组 4,直接就 16 个优先级,使用起来也简单!

4.0 优先级设置

每个外部中断都有一个对应的优先级寄存器,每个寄存器占 8 位,因此最大宽度是 8 位,
但是最小为 3 位。4 个相临的优先级寄存器拼成一个 32 位寄存器。如前所述,根据优先级组的
设置,优先级又可以分为高、低两个位段,分别抢占优先级和亚优先级。STM32 我们已经设置
位组 4,所以就只有抢占优先级了。优先级寄存器都可以按字节访问,当然也可以按半字/字来
访问,有意义的优先级寄存器数目由芯片厂商来实现

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

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

我们在 STM32 上移植 FreeRTOS 的时候需要重点关注 PRIMASK、
FAULTMASK 和 BASEPRI 这三个寄存器,本节就来学习一下这三个寄存器。

1、PRIMASK 和 FAULTMASK 寄存器
在许多应用中,需要暂时屏蔽所有的中断一执行一些对时序要求严格的任务,这个时候就
可以使用 PRIMASK 寄存器,PRIMASK 用于禁止除 NMI 和 HardFalut 外的所有异常和中断,
汇编编程的时候可以使用 CPS(修改处理器状态)指令修改 PRIMASK 寄存器的数值:
CPSIE I; //清除 PRIMASK(使能中断)
CPSID I; //设置 PRIMASK(禁止中断)
PRIMASK 寄存器还可以通过 MRS 和 MSR 指令访问,如下:
MOVS R0, #1
MSR PRIMASK, R0 ;//将 1 写入 PRIMASK 禁止所有中断
以及:
MOVS R0, #0
MSR PRIMASK, R0 ;//将 0 写入 PRIMASK 以使能中断
uC/OS 中的临界区代码代码保护就是通过开关中断实现的(uC/OSIII 也可以使用禁止任务调
度的方法来实现临界区代码保护,这里不讨论这种情况),而开关中断就是直接操作 PRIMASK
寄存器的,所以在 uC/OS 中关闭中断的时候时关闭了除复位、NMI 和 HardFault 以外的所有中
断!
FAULTMASK 比 PRIMASK 更狠,它可以连 HardFault 都屏蔽掉,使用方法和 PRIMASK 类
似,FAULTMASK 会在退出时自动清零。
汇编编程的时候可以利用 CPS 指令修改 FAULTMASK 的当前状态:
CPSIE F ;清除 FAULTMASK
CPSID F ;设置 FAULTMASK
还可以利用 MRS 和 MSR 指令访问 FAULTMASK 寄存器:
MOVS R0, #1
MSR FAULTMASK, R0 ;将 1 写入 FAULTMASK 禁止所有中断
以及:
MOVS R0, #0
MSR FAULTMASK, R0 ;将 0 写入 FAULTMASK 使能中断

2、BASEPRI 寄存器
PRIMASK 和 FAULTMASK 寄存器太粗暴了,直接关闭除复位、NMI 和 HardFault 以外的
其他所有中断,但是在有些场合需要对中断屏蔽进行更细腻的控制,比如只屏蔽优先级低于某
一个阈值的中断。那么这个作为阈值的优先级值存储在哪里呢?在 BASEPRI 寄存器中,不过
如果向 BASEPRI 写 0 的话就会停止屏蔽中断。比如,我们要屏蔽优先级不高于 0X60 的中断,
则可以使用如下汇编编程:
MOV R0, #0X60
MSR BASEPRI, R0
如果需要取消 BASEPRI 对中断的屏蔽,可以使用如下代码:
MOV R0, #0
MSR BASEPRI, R0
注意!FreeRTOS 的开关中断就是操作 BASEPRI 寄存器来实现的!它可以关闭低于某个阈
值的中断,高于这个阈值的中断就不会被关闭!

6.0 中断配置宏

6.1 configPRIO_BITS
此宏用来设置 MCU 使用几位优先级,STM32 使用的是 4 位,因此此宏为 4!

6.2 configLIBRARY_LOWEST_INTERRUPT_PRIORITY
此宏是用来设置最低优先级,前面说了,STM32 优先级使用了 4 位,而且 STM32 配置的
使用组 4,也就是 4 位都是抢占优先级。因此优先级数就是 16 个,最低优先级那就是 15。所以
此宏就是 15,注意!不同的 MCU 此值不同,具体是多少要看所使用的 MCU 的架构,本教程
只针对 STM32 讲解!

6.3 configKERNEL_INTERRUPT_PRIORITY

此宏用来设置内核中断优先级,此宏定义如下:
#define configKERNEL_INTERRUPT_PRIORITY
( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
宏 configKERNEL_INTERRUPT_PRIORITY 为 , 宏
configLIBRARY_LOWEST_INTERRUPT_PRIORITY 左移 8-configPRIO_BITS 位,也就是左移 4
位。为什么要左移 4 位呢?前面我们说了,STM32 使用了 4 位作为优先级,而这 4 位是高 4 位,
因 此 要 左 移 4 位 才 是 真 正 的 优 先 级 。 当 然 了 也 可 以 不 用 移 位 , 直 接 将 宏
configLIBRARY_LOWEST_INTERRUPT_PRIORITY 定义为 0XF0!不过这样看起来不直观。
宏 configKERNEL_INTERRUPT_PRIORITY 用来设置 PendSV 和滴答定时器的中断优先级,
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 都 是 使 用 了 宏
configKERNEL_INTERRUPT_PRIORITY , 为 什 么 宏 portNVIC_PENDSV_PRI 是 宏
configKERNEL_INTERRUPT_PRIORITY 左移 16 位呢?宏 portNVIC_SYSTICK_PRI 也同样是
左移 24 位。4.1.4 小节讲过了,PendSV 和 SysTcik 的中断优先级设置是操作 0xE000_ED20 地址的,这样一次写入的是个 32 位的数据, SysTick 和 PendSV 的优先级寄存器分别对应这个 32
位数据的最高 8 位和次高 8 位,不就是一个左移 16 位,一个左移 24 位了。
PendSV 和 SysTick 优先级是在哪里设置的呢?在函数 xPortStartScheduler()中设置,此函数
在文件 port.c 中,函数如下:

BaseType_t xPortStartScheduler( void )
{
configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
#if( configASSERT_DEFINED == 1 )
{
volatile uint32_t ulOriginalPriority;
volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * )
( portNVIC_IP_REGISTERS_OFFSET_16 +
portFIRST_USER_INTERRUPT_NUMBER );
volatile uint8_t ucMaxPriorityValue;
ulOriginalPriority = *pucFirstUserPriorityRegister;
*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY &
ucMaxPriorityValue ) );
ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY &
ucMaxPriorityValue;
ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
{
ulMaxPRIGROUPValue--;
ucMaxPriorityValue <<= ( uint8_t ) 0x01;
}
ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
*pucFirstUserPriorityRegister = ulOriginalPriority;
}
#endif /* conifgASSERT_DEFINED */
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; //设置 PendSV 中断优先级
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; //设置 SysTick 中断优先级
vPortSetupTimerInterrupt();
uxCriticalNesting = 0;
prvEnableVFP();
*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;
prvStartFirstTask();
return 0;
}

上 述 代 码 中 红 色 部 分 就 是 设 置 PendSV 和 SysTick 优 先 级 的 , 它 们 是 直 接 向 地 址
portNVIC_SYSPRI2_REG 写入优先级数据,portNVIC_SYSPRI2_REG 是个宏,在文件 port.c 中
由定义,如下:
#define portNVIC_SYSPRI2_REG ( * ( ( volatile uint32_t * ) 0xe000ed20 ) )
可以看到宏 portNVIC_SYSPRI2_REG 就是地址 0XE000ED20!同时也可以看出在 FreeRTOS
中 PendSV 和 SysTick 的中断优先级都是最低的!

6.4 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
此宏用来设置 FreeRTOS 系统可管理的最大优先级,也就是我们在 4.1.5 小节中讲解
BASEPRI 寄存器说的那个阈值优先级,这个大家可以自由设置,这里我设置为了 5。也就是高
于 5 的优先级(优先级数小于 5)不归 FreeRTOS 管理!

6.5 configMAX_SYSCALL_INTERRUPT_PRIORITY
此宏是 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 左移 4 位而来的,原因
和宏 configKERNEL_INTERRUPT_PRIORITY 一样。此宏设置好以后,低于此优先级的中断可
以安全的调用 FreeRTOS 的 API 函数,高于此优先级的中断 FreeRTOS 是不能禁止的,中断服
务函数也不能调用 FreeRTOS 的 API 函数!
以 STM32 为例,有 16 个优先级,0 为最高优先级,15 为最低优先级,配置如下:
● configMAX_SYSCALL_INTERRUPT_PRIORITY==5
● configKERNEL_INTERRUPT_PRIORITY==15

由于高于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的优先级不会被 FreeRTOS 内核
屏蔽,因此那些对实时性要求严格的任务就可以使用这些优先级,比如四轴飞行器中的壁障检
测。
7.0 FreeRTOS 开关中断

FreeRTOS 开关中断函数为 portENABLE_INTERRUPTS ()和 portDISABLE_INTERRUPTS(),
这两个函数其实是宏定义,在 portmacro.h 中有定义,如下:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)
可以看出开关中断实际上是通过函数 vPortSetBASEPRI(0)和 vPortRaiseBASEPRI()来实现
的,这两个函数如下:

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
msr basepri, ulBASEPRI
}
}
/*-----------------------------------------------------------*/
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
msr basepri, ulNewBASEPRI
dsb
isb
}
}

函数 vPortSetBASEPRI()是向寄存器 BASEPRI 写入一个值,此值作为参数 ulBASEPRI 传
递进来,portENABLE_INTERRUPTS()是开中断,它传递了个 0 给 vPortSetBASEPRI(),根据我
们前面讲解 BASEPRI 寄存器可知,结果就是开中断。
函 数 vPortRaiseBASEPRI() 是 向 寄 存 器 BASEPRI 写 入 宏
configMAX_SYSCALL_INTERRUPT_PRIORITY , 那 么 优 先 级 低 于
configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断就会被屏蔽!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值