STM32F10xxx_异常与中断
STM32F10xxx_异常与中断
更新记录
version | status | description | date | author |
---|---|---|---|---|
V1.0 | C | Create Document | 2018.10.27 | John Wan |
status:
C―― Create,
A—— Add,
M—— Modify,
D—— Delete。
1、异常与中断的概念
对于几乎所有的微控制器,中断都是一种常见的特性。中断一般是由硬件(如外设和外部输入引脚)产生的时间,它会引起程序偏离正常的流程(如给外设提供服务),所有的Cortex-M处理器都会提供一个专门用于中断处理的嵌套向量中断控制器(NVIC)
。那么除了中断请求以外,还有其他需要服务的事件,将其称为"异常"。在ARM的说法中,中断也是一种异常。除了NVIC
,系统控制块(SCB)
寄存器中也包含了一些常用与系统异常的寄存器。
注:后续的用词,中断:即表示外设中断;系统异常:编号从1~15的各种异常;异常:指系统异常与中断。
2、异常的流程
2.1 中断请求的来源
- 系统异常
- 外设中断
Cortex-M处理器的异常架构具有多种特性,支持多个系统异常和外部中断。编号1~15的为系统异常,16及以上的则为中断输入。包括所有中断在内的多数异常,都具有可编程的优先级,一些系统异常则具有固定的优先级。不同的微控制器的中断编号、优先级都可能不同,具体的中断编号由芯片商根据需求进行配置,查阅对应的指导手册。
而CMSIS-Core定义的中断标识是用枚举实现,从数值0开始(代表中断#0)表示中断编号,系统异常的编号为负数。之所以CMSIS-Core使用另外一条编号系统,是因为这样可稍微提高部分API函数的效率(例如设置优先级)。
2.2 处理器接受中断请求的条件
- 处理器正在运行(未被暂停或处于复位状态)
- 异常处于使能状态(NMI和HardFault为特殊情况,他们总是使能的)
- 异常的优先级高于当前等级
- 异常未被异常屏蔽寄存器屏蔽
2.2.1 异常的使能状态
- 中断的使能
由NVIC
寄存器中的中断设置使能寄存器(ISER)
与中断清除使能寄存器(ICER)
控制,可进行字、半字或字节进行访问。需要说明的是ISER/ICER
寄存器都是32位宽,每个位代表一个中断输入,如果存在32个以上的外部中断,则ISER/ICER
寄存器可能不止一个。需要强调的是该寄存器只进行中断的设置,即异常编号为16开始的外部中断#0,不包括前16个异常类型的系统异常。
- 系统异常的使能
由SCB
寄存器中的系统处理控制和状态寄存器(SHCSR)
控制,其中只能设置异常编号为4、5、6的存储管理错误、总线错误、使用错误三种系统异常的使能。
2.2.2 异常的优先级
每个异常都有一个优先级,其中绝大部分的优先等级可编辑,编程范围为0~255,极少部分的系统异常的优先等级是固定的且优先级为负数,这样可方便处理器进行判断是否应该接受异常以及何时接受并执行异常处理,大致分为两种决定的场景:
- 正常的流程被中断
- 中断的流程被中断(中断的嵌套)
- 优先级的定义
只有更高优先级的异常(优先级编号更小)可以抢占低优先级的异常(优先级编号更大),Cortex-M3
处理器在设计上具有3个固定的最高优先级以及256个可编程优先级(最多具有128个抢占等级),可编程优先级的实际数量由芯片设计商决定,多数使用Cortex-M3
的芯片,支持的优先级较少,这样可降低NVIC
的复杂度,降低功耗且增加速度。
在Cortex-M3
处理器中每个异常都存在一个8bit的优先级寄存器
来设置其中断优先级等级(例如后面讲的NVIC->IP[n]
),而该8bit的优先级寄存器
进一步分为两个部分:抢占优先级与子优先级,可利用系统控制块SCB
中的应用中断和复位控制寄存器AIRCR
的BIT[10:8](见图4)来配置优先级的分组共23=8个分组(见图5),而这里的分组就决定了8bit的优先级寄存器
中抢占优先级占几位,子优先级占几位:
上面是Cortex-M3
的最大允许设置范围,前面提到,可编程优先级的实际数量根据芯片商的各种需求可进行裁剪设计,通过芯片商的手册可以查询,例如在STM32F10xxx
中就限制了只能使用8bit的优先级寄存器
的高四位BIT[7:4],称其有效宽度,那么优先级分组的方式就只有5个分组,在这5个分组中,抢占优先级最多只能占4bit,即抢占优先级等级最多只能设置24 = 16个,见下图。[1]
在处理器已经在运行一个中断处理时能否产生另外一个中断,是由该中断的抢占优先级决定的。子优先级只会用在具有两个相同分组优先级的异常同时产生的情形,此时,具有更高子优先级(数值更小)的异常会被首先处理。
问题:
(1)为什么前面提到Cortex-M3
有256个可编程优先级,而最多只具备128个抢占优先级?
如图5,优先级分组时总会配置一种情况,即达到分组的最大宽度时,抢占优先级最多只能分配7位,保留一位的子优先级。
(2)为什么8bit的优先级寄存器
的宽度设置是通过移除LSB
用高位表示,而不是通过移除MSB
用低位表示?
便于不同处理器之间进行移植,按照这种方式,在具有4位优先级配置寄存器的设备上写的程序,就可能会在具有3位优先级配置寄存器的设备上运行。例如某应用程序,IRQ#0
的优先级为0x05,IRQ#1
的优先级为0x03,那么移除了最高位bit2之后,则IRQ#0
的优先级会变为0x01,高于IRQ#1
的优先级。如果IRQ#0
的优先级为0x50,IRQ#1
的优先级为0x30,那么移除最低位bit0之后,还是IRQ#0
的优先级较低。
- 中断的优先级
每个中断都有对应的优先级寄存器,可根据在系统控制块SCB
中应用中断和复位控制寄存器AIRCR
配置的优先级分组所规定的抢占优先级范围和子优先级范围来设定抢占优先级等级以及子优先级等级。其优先级分组有几种情况由芯片商决定。该寄存器可通过字节、半字和字访问,优先级寄存器的数量取决于芯片中实际存在的外部中断数。可通过中断优先级寄存器NVIC->IP[n]
来进行设置.
- 系统异常的优先级
关于可编辑的系统异常优先级,原理同中断的优先级设定,其可通过系统处理优先级寄存器 SCB->SHP[0~11]
来进行设置。
- CMSIS-Core设置异常优先级
core_cm3.h
优先级分组
static __INLINE void NVIC_SetPriorityGrouping(uint32_t PriorityGroup) //设置优先级分组
static __INLINE uint32_t NVIC_GetPriorityGrouping(void) //读取优先级分组
优先级等级
static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)//设置异常的优先级
static __INLINE uint32_t NVIC_GetPriority(IRQn_Type IRQn) //读取异常的优先级
- STM32F10xxx库文件中设置异常优先级
misc.h
/** @defgroup Preemption_Priority_Group
* @{
*/
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
0 bits for subpriority */
#define IS_NVIC_PRIORITY_GROUP(GROUP) (((GROUP) == NVIC_PriorityGroup_0) || \
((GROUP) == NVIC_PriorityGroup_1) || \
((GROUP) == NVIC_PriorityGroup_2) || \
((GROUP) == NVIC_PriorityGroup_3) || \
((GROUP) == NVIC_PriorityGroup_4))
#define IS_NVIC_PREEMPTION_PRIORITY(PRIORITY) ((PRIORITY) < 0x10)
#define IS_NVIC_SUB_PRIORITY(PRIORITY) ((PRIORITY) < 0x10)
#define IS_NVIC_OFFSET(OFFSET) ((OFFSET) < 0x000FFFFF)
misc.c
/** @defgroup MISC_Private_Defines
* @{
*/
#define AIRCR_VECTKEY_MASK ((uint32_t)0x05FA0000)
/**
* @brief Configures the priority grouping: pre-emption priority and subpriority.
* @param NVIC_PriorityGroup: specifies the priority grouping bits length.
* This parameter can be one of the following values:
* @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority
* 4 bits for subpriority
* @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority
* 3 bits for subpriority
* @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority
* 2 bits for subpriority
* @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority
* 1 bits for subpriority
* @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority
* 0 bits for subpriority
* @retval None
*/
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
/* Check the parameters */
assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
/* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
- 中断的设置步骤
(1)设置优先级分组,STM32F10XXX默认的优先级分组是2位抢占优先级,2位子优先级。(SCB->AIRCR的复位为0x05FA0000,如果不进行设置bit[10:8]为0x05)
(2)设置中断的抢占优先级与子优先级。
(3)在NVIC
或外设中使能中断
2.2.3 异常的屏蔽
用于异常屏蔽的特殊寄存器,包括PRIMASK、FAULTMASK、BASEPRI
:
- PRIMASK
在许多应用中,可能都需要暂时禁止所有中断以执行一些时序关键的任务,此时可以使用PRIMASK寄存器
。该寄存器只能在特权状态下访问。
PRIMASK寄存器
用于禁止除NMI
和HardFault
外的所有异常。实际上它是将当前优先级改为0(最高可编程等级),以不被其它异常所中断。
CMSIS-Core的core_cm3.c中提供以下函数进行查询与设置:
/**
* @brief Return the Priority Mask value
*
* @return PriMask
*
* Return state of the priority mask bit from the priority mask register
*/
__ASM uint32_t __get_PRIMASK(void)
{
mrs r0, primask
bx lr
}
/**
* @brief Set the Priority Mask value
*
* @param priMask PriMask
*
* Set the priority mask bit in the priority mask register
*/
__ASM void __set_PRIMASK(uint32_t priMask)
{
msr primask, r0
bx lr
}
core_cm3.h中:
#define __enable_irq __enable_interrupt /*!< global Interrupt enable */
#define __disable_irq __disable_interrupt /*!< global Interrupt disable */
关于下面的core_cm3.h
中可参考开关中断
- FAULTMASK
行为上与PRIMASK寄存器
类似,只是它实际上会将当前优先级修改为-1,这样甚至是HardFlaut
处理也会被屏蔽,则当FAULTMASK
置位时,只有NMI
异常处理才能执行。
CMSIS-Core的core_cm3.c中提供以下函数:
/**
* @brief Return the Fault Mask value
*
* @return FaultMask
*
* Return the content of the fault mask register
*/
__ASM uint32_t __get_FAULTMASK(void)
{
mrs r0, faultmask
bx lr
}
/**
* @brief Set the Fault Mask value
*
* @param faultMask faultMask value
*
* Set the fault mask register
*/
__ASM void __set_FAULTMASK(uint32_t faultMask)
{
msr faultmask, r0
bx lr
}
core_cm3.h中:
static __INLINE void __enable_fault_irq() { __ASM ("cpsie f"); }
static __INLINE void __disable_fault_irq() { __ASM ("cpsid f"); }
FAULTMASK
也只能在特权状态下访问,不过不能在NMI
和HardFault
处理中设置。
FAULTMASK
会在退出异常处理被自动清除,从NMI
中退出时除外。因此可以利用此特性,如果在低优先级的异常处理中触发了一个高优先的异常(NMI
除外),但是想要先处理完低优先级的异常处理再进行高优先的处理,那么可以:
- 设置
FAULTMASK
禁止所有异常(NMI
除外) - 设置高优先级异常的挂起状态
- 退出处理
由于在FAULTMASK
置位时,挂起的高优先级异常处理无法执行,高优先级的异常就会在FAULTMASK
被清除前继续保持挂起状态,低优先级处理完成后才会将其清除。因此,可以强制让高优先级处理在低优先级处理结束后开始执行。
- BASEPRI
有些情况下,可能只想禁止优先级低于某特定等级的中断,只需将所需的屏蔽优先级写入BASEPRI寄存器
。只能在特权状态下访问。
CMSIS-Core的core_cm3.c中提供以下函数:
/**
* @brief Return the Base Priority value
*
* @return BasePriority
*
* Return the content of the base priority register
*/
__ASM uint32_t __get_BASEPRI(void)
{
mrs r0, basepri
bx lr
}
/**
* @brief Set the Base Priority value
*
* @param basePri BasePriority
*
* Set the base priority register
*/
__ASM void __set_BASEPRI(uint32_t basePri)
{
msr basepri, r0
bx lr
}
2.2.4 异常的请求设置与查询
- 中断输入与挂起行为
每个中断都有多个属性:
- 每个中断都可被禁止(默认)或使能。
- 每个中断都可被挂起(等待服务的请求)或解除挂起。
- 每个中断可处于活跃(正在处理)或非活跃状态。
为了支持这些属性,NVIC
中包含了多个可编程寄存器,用于中断使能控制、挂起状态和只读的活跃状态位。
当NVIC
的中断输入被确认后,它就会引发该中断的挂起状态,被存储在NVIC
的可编程寄存器中,即使该中断请求在处理器还没来的急处理,就已经被取消,只要在NVIC
中已经被挂起过,那么就算有效,这就是NVIC
支持脉冲中断请求特性。
当中断正被处理时,它就会处于活跃状态,处理完成,活跃状态自动清除。但对于许多微控制器设计,外设会产生电平触发的中断,因此ISR必须要手动清除中断请求,如写入外设中的某个寄存器。
(1)、当中断处于活跃状态是,处理器无法在中断完成和异常返回前再次接受同一个中断请求。
(2)、若中断请求产生时处理器正在处理另一个具有更高优先级的中断,而在处理器队该中断请求做出响应之前,通过软件代码将挂起状态清除掉,那么该请求就会被取消且不会再得到处理。
(3)、若外设持续保持某个中断请求,那么即使软件尝试着清除该挂起状态,挂起状态还是会再次置位的。
(4)、若在得到处理后,中断源仍在继续保持中断请求,那么这个中断就会再次进入挂起状态且再次得到处理器的服务。
(5)、对于脉冲中断请求,若在处理器开始处理前,中断请求信号产生了多次,它们会被当作一次中断请求。
(6)、中断的挂起状态可以在其正被处理时再次置位。
(7)、即使中断被禁止了,它的挂起状态仍可置位。在这种情况下,若中断稍后被使能了,可以被触发并得到服务。有些时候,这种情况并不是所希望的,因此需要在使能NVIC
中的中断前手动清除挂起状态。
- 中断挂起的设置、清除、查询
中断的挂起状态可通过中断设置挂起寄存器 NVIC->ISPR[n]
和中断清除挂起寄存器 NVIC->ICPR[n]
访问,若存在32个以上的外部中断输入,则寄存器可能不止一个。
CMSIS-Core中core_m3.h提供以下函数:
static __INLINE uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn)
static __INLINE void NVIC_SetPendingIRQ(IRQn_Type IRQn)
static __INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn)
- 中断的活跃状态查询
每个外部中断都有一个活跃状态位,当处理器开始执行中断处理时,该位会被置1,而在执行中断返回时会被清零,通过中断活跃状态寄存器 NVIC->IABR[n]
进行查询。如果被抢占,还是会处于活跃状态。当外部中断的数量超过32个,寄存器不止一个。
CMSIS-Core中core_m3.h提供以下函数:
static __INLINE uint32_t NVIC_GetActive(IRQn_Type IRQn)
- 系统异常的挂起设置、清除、查询
通过中断控制和状态寄存器 SCB->ICSR
进行访问。
- 系统异常的活跃状态查询
通过系统处理控制和状态寄存器 SCB->SHCSR
进行访问。
2.3 如何暂停当前任务进入异常流程
2.4 如何执行异常处理,并触发异常返回
2.5 如何进行异常返回,执行暂停的任务
参考《STM32中文参考手册_V10》P130,《STM32F10xxx_20xxx_21xxx_L1xxx_Programming_Mannual_PM0056_ENV6》P135 Binary Point ↩︎