本文是《ALIENTEK STM32F429 FreeRTOS 开发教程》第四章学习笔记
第一章笔记–FreeRTOS简介与源码下载
第二章笔记–FreeRTOS在STM32F4上移植
第三章笔记-FreeRTOS系统配置
中断由硬件产生,当中断产生后CPU就会中断当前流程转去处理中断服务,Cortex-M内核的MCU提供一个用于管理中断的嵌套向量中断控制器(NVIC)
NVIC 与 CM3 内核共同完成对中断的响应。NVIC的寄存器以存储器映射的方式来访问,除了包含控制寄存器和中断处理的控制逻辑之外,NVIC 还包含了 MPU
的控制寄存器、 SysTick 定时器以及调试控制。
Cortex-M3和M4的NVIC最多支持249个外部中断输入(IRQs)、1个不可屏蔽中断(NMI)、1个Systick(滴答定时器)定时器中断和多个系统异常
在 NVIC 的中断控制及状态寄存器中,有一个 VECTACTIVE 位段;另外,还有一个特殊功能寄存器IPSR。在它们二者的里面,都记录了当前正服务异常的编号,编号为 1-15 的对应系统异常,
编号大于等于 16 的则全是外部中断
Cortex-M处理器有多个用于管理中断和异常的可编程寄存器,这些寄存器都在NVIC和系统控制块(SCB)中,CMSIS将这些寄存器定义为结构体。
NVIC访问地址是0xE000_E000,打开core_cm4.h文件:
可以看到NVIC基址是从 0xE000_E000 + 0x0100UL = 0xE000_E100开始的
宏定义的NVIC中有一结构体 NVIC_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;
CM3和CM4 中可以有240对使能位/除能位,每个中断拥有一对。这 240 个对子分布在8对32位寄存器中(最后一对没有用完)。欲使能一个中断,需要写 1 到对应 SETENA 的位中;欲除能一个中断,需要写 1 到对应的 CLRENA 位中;如果往它们中写 0,不会有任何效果
类型为32位,长度为8的数组ISER对应着8个32位的中断使能寄存器(开始地址为0xE000_E100);
类型为32位,长度为8的数组ICER对应着8个32位的中断除能寄存器(开始地址为0xE000_E100+(8+24)*4=0xE000_E180);
如果中断发生时,正在处理同级或高优先级异常,或者被掩蔽,则中断不能立即得到响应。此时中断被悬起。中断的悬起状态可以通过“中断设置悬起寄存器(SETPEND)”和“中断悬起清除寄存器(CLRPEND)”来读取,还可以写它们来手工悬起中断
类型为32位,长度为8的数组ISPR对应着8个32位的中断悬起寄存器(开始地址为:0xE000_E200);
类型为32位,长度为8的数组ICPR对应着8个32位的中断悬起寄存器(开始地址为:0xE000_E280)
每个外部中断都有一个活动状态位。在处理器执行了其 ISR 的第一条指令后,它的活动位就被置1,并且直到ISR返回时才硬件清零
由于支持嵌套,允许高优先级异常抢占某个ISR。然而,哪怕一个中断被抢占,其活动状态也依然为 1
类型为32位,长度为8的数组IABR对应着8个32位的活动状态寄存器(开始地址为:0xE000_E300)
每个外部中断都有一个对应的优先级寄存器,每个寄存器占用 8 位,但是允许最少只使用最高3位。4个相临的优先级寄存器拼成一个 32 位寄存器
根据优先级组设置,优先级可以被分为高低两个位段,分别是抢占优先级和亚优先级
类型为32位,长度为240的数组IP对应着240个32位的优先级寄存器(开始地址为:0xE000_E400)
用一个 32位类型的数据 STIR 表示 软件触发中断寄存器STIR(地址为:0xE000_EF00)
以STM32F429为例子,打开core_cm4.h文件 可以看到结构体 SCB_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_Type作用相似,都是将相关寄存器用结构体封装表示出来
优先级影响一个中断是否被响应以及何时被响应
优先级数值越小,则优先级越高
CM3和CM4支持中断嵌套,使高优先级抢占低优先级中断
原则上, Cortex-M处理器支持3个固定的高优先级和多达256级的可编程优先级,并且支持 128级抢占,但为了精简设计,STM32最后只有16级优先级(选择4位作为优先级),在设计时裁掉表达优先级的几个低端有效位来减少优先级数
使用3个位来表达优先级情况:
[4:0]没有被实现,所以读它们总是返回零,写它们则忽略写入的值,对于3个位的情况,我们能够使用的8个优先级为: 0x00(最高), 0x20, 0x40, 0x60, 0x80,0xA0, 0xC0 以及 0xE0
为了使抢占机能变得更可控,Cortex_M还把256级优先级按位分成高低两段,分别是抢占优先级和子优先级
STM32F429将中断分为 5 个组,组0~4。该分组的设置是由 SCB->AIRCR 寄存器的 bit10~8 来定义的
打开stm32HAL库里NVIC相关代码可以看到:
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);//设置为中断优先级分组4
通过三个中断优先级设置函数
void HAL_NVIC_SetPriority(IRQn_Type IRQn,uint32_t PreemptPriority, uint32_t SubPriority);
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn);
void HAL_NVIC_SetPriority(IRQn_Type IRQn,uint32_t PreemptPriority, uint32_t SubPriority);
IRQn_Type IRQn:中断优先级类型,每个类型对应NVIC_Type.IP[x](x为0-239)
uint32_t PreemptPriority: 抢占优先级
uint32_t SubPriority:子优先级
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn);
portNVIC_SYSPRI2_REG:
#define portNVIC_SYSPRI2_REG (*((volatile uint32_t *)0xe000ed20))
前面说过每个外部中断都有一个对应的优先级寄存器,每个寄存器占用 8位,4个相临的优先级寄存器拼成一个32位寄存器
要设置PendSV与SYSTick的优先级,则对最后4个相邻的寄存器组成的32位寄存器进行操作,地址为0xE000_ED20
#define portNVIC_PENDSV_PRI(((uint32_t)configKERNEL_INTERRUPT_PRIORITY)<<16UL)
#define pportNVIC_SYSTICK_PRI(((uint32_t)configKERNEL_INTERRUPT_PRIORITY)<<24UL)
#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf
#define configPRIO_BITS 4 /*STM32使用了几位作为优先级 */
通过代码一系列的宏定义可以分析: 因为一开始设置优先级分组是组4,所以最低优先级是0xf(15), 八位的优先级寄存器只使用了4位作为优先级,所以要左移(8-4)位进行赋值,分别给基地址为0xE000_ED20的32位寄存器的高八位和次高八位进行赋值,因此要左移24位和16位,这样就可以把PendSV与SYSTick的优先级设置成最低了
PRIMASK 用于除能在NMI和硬fault之外的所有异常,它有效地把当前优先级改为0(可编程优先级中的最高优先级)
该寄存器可以通过 MRS 和 MSR 以下例方式访问:
关中断:
MOV R0, #1
MSR PRIMASK, R0
开中断:
MOV R0, #0
MSR PRIMASK, R0
还可以通过CPS指令快速完成上述功能:
CPSID i ;关中断
CPSIE i ;开中断
FAULTMASK会把当前优先级改为‐1。硬fault都会被掩蔽。使用方案与PRIMASK的相似。但要注意的是,FAULTMASK会在异常退出时自动清零。
FreeRTOS中使用这个寄存器
在更精巧的设计中,需要对中断掩蔽进行更细腻的控制——只掩蔽优先级低于某一阈值的中断——它们的优先级在数字上大于等于某个数。那么这个数存储在哪里?就存储在
BASEPRI中。不过,如果往BASEPRI中写0,则另当别论——BASEPRI将停止掩蔽任何中断
屏蔽所有优先级不高于0x60的中断:
MOV R0, #0x60
MSR BASEPRI, R0
取消对所有中断的屏蔽
MOV R0, #0
MSR BASEPRI, R0
打开代码可以看到屏蔽中断的操作:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
section. */
msr basepri, ulNewBASEPRI
dsb
isb
}
}
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 /*系统可管理的最高中断优先级*/
取消所有屏蔽写入0就可以:
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
用来设置MCU使用几位优先级,STM32使用是4位,所以此宏设置为4
用来设置最低优先级,优先级使用了4位,而且是组4,所以最低优先级是15
用来设置内核中断优先级
#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))
用来设置PendSV和SysTick的中断优先级,在1.3.4中有详细配置
用来设置FreeRTOS系统可管理的最大优先级,在1.4.2当中提到对BASEPRI寄存器说的阈值优先级,这里设置了5,即高于5的优先级不归FreeRTOS管理
宏configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY左移4位得到,与configKERNEL_INTERRUPT_PRIORITY同理,设置好后,高于此优先级中断FreeRTOS是不能禁止的
开关中断函数为
portENABLE_INTERRUPTS()
portDISABLE_INTERRUPTS()
这两个函数只是一层封装,真正执行的是:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
在1.4.2已经分析过