STM32 中断系统
1. 中断和异常的区别
1.1 中断是指系统停止当前正在运行的程序转而其他服务,可能是程序接收了比自身高优先级的请求,或者是人为设置中断,中断是属于正常现象。
1.2 异常是指由于cpu本身故障、程序故障或者请求服务等引起的错误,异常属于不正常现象。
Cortex-M3内核总共支持256个中断,其中包含16个内核异常和240个外部中断,但是各个芯片产商在设计芯片的时候会对CM3内核的芯片进行精简设计,如STM32F103系列,所搭载的异常响应系统,包含10个系统异常和60个外部中断,用一张表将它们管理起来,编号0~15位系统异常,16以上称为外部中断
系统异常清单:
外部中断清单:
.................
外部中断信号从核外发出,信号最终要传递到NVIC(嵌套向量中断控制器)。NVIC跟内核紧密耦合,它控制着整个芯片中断的相关功能。
2. 中断优先级
STM32支持两种优先级:抢占优先级和子优先级。所有优先级可编程的中断源都需要指定这两种优先级。
抢占优先级决定是否可以产生中断嵌套,子优先级决定中断响应顺序,若两种优先级一样则看中断源在中断向量表中的偏移量,偏移量小的先响应。
每个中断源都需要被指定抢占优先级和子优先级,自然需要相应的寄存器来记录。在NVIC中有一个专门处理中断优先级的寄存器NICV_IPRx,用于配置中断源的优先级。IPR的宽度为8Bit,对于CM3内核来说,因为它支持的中断源为256个,那么原则上每个外部中断源可配置的优先级位0~255,数值越小优先级越高。但是因为绝大多数CM3芯片都会精简设计,所以不会使用到全部位,在STM32F103中只使用4Bit。
注意,个别系统系统的优先级是固定的,所以它们的中断优先级是不可编程的。
2.1 CM3核的优先级分组方式
CM3中定义了8个Bit用于设置中断源的优先级,这8个Bit可以分配为:
(1)8bit用于响应优先级
(2)最高1位用于指定抢占优先级,最低7位用于执行子优先级
(3)最高2位用于指定抢占优先级,最低6位用于执行子优先级
(4)最高3位用于指定抢占优先级,最低5位用于执行子优先级
(5)最高4位用于指定抢占优先级,最低4位用于执行子优先级
(6)最高5位用于指定抢占优先级,最低3位用于执行子优先级
(7)最高6位用于指定抢占优先级,最低2位用于执行子优先级
(8)最高7位用于指定抢占优先级,最低1位用于执行子优先级
static __INLINE void NVIC_SetPriorityGrouping(uint32_t PriorityGroup) { uint32_t reg_value; uint32_t PriorityGroupTmp = (PriorityGroup & 0x07); /* only values 0..7 are used */ reg_value = SCB->AIRCR; /* read old register configuration */ reg_value &= ~(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk); /* clear bits to change */ reg_value = (reg_value | (0x5FA << SCB_AIRCR_VECTKEY_Pos) | (PriorityGroupTmp << 8)); /* Insert write key and priorty group */ SCB->AIRCR = reg_value; }
该函数写在.h文件中,且声明为内联函数(__INLINE),内联函数跟宏替换差不多,可以避免函数调用的压栈出栈等开销。PriorityGroup的取值为0~7。
2.2 STM32的优先级分组方式
CM3核的优先级分组方式是针对256个中断全部用上的场合,但是Cortex-M3也允许在具有较少中断源时用较少的寄存器位指定中断源的优先级。STM32并没有使用Cortex-M3内核嵌套向量中断全套东西,而是使用了它的一部分:
3. NVIC操作相关函数
NVIC的描述结构体在core_cm3.h中:
typedef struct { __IO uint32_t ISER[8]; /* 中断使能寄存器(Interrupt Set Enable Register),Offset: 0x000 */ uint32_t RESERVED0[24]; __IO uint32_t ICER[8]; /* 中断清除寄存器(Interrupt Clear Enable Register),Offset: 0x080 */ uint32_t RSERVED1[24]; __IO uint32_t ISPR[8]; /* 中断使能挂起寄存器(Interrupt Set Pending Register),Offset: 0x100 */ uint32_t RESERVED2[24]; __IO uint32_t ICPR[8]; /* 中断清除挂起寄存器(Interrupt Clear Pending Register),Offset: 0x180 */ uint32_t RESERVED3[24]; __IO uint32_t IABR[8]; /* 中断有效位寄存器(Interrupt Active bit Register ),Offset: 0x200 */ uint32_t RESERVED4[56]; __IO uint8_t IP[240]; /* 中断优先级寄存器(Interrupt Priority Register),Offset: 0x300 (8Bit wide) */ uint32_t RESERVED5[644]; __O uint32_t STIR; /* 软中断触发寄存器(Software Trigger Interrupt Register),Offset: 0xE00 */ } NVIC_Type;
编程中常用的是ISER、ICER和IP这三个寄存器。ISER和ICER分别用于enable、disable中断,IP用于控制中断优先级。
同在core_cm3.h中,定义了对结构体成员的操作函数,这是针对Cortex-M3内核芯片都适用的函数:
(1)设置优先级分组寄存器: NVIC_SetPriorityGrouping(uint32_t PriorityGroup) (2)从NVIC中断控制器得到优先级分组设置值: NVIC_GetPriorityGrouping(void) (3)使能中断: NVIC_EnableIRQ(IRQn_Type IRQn) (4)失能中断: NVIC_DisableIRQ(IRQn_Type IRQn) (5)获取挂起中断编号: NVIC_GetPendingIRQ(IRQn_Type IRQn) (6)设置中断挂其位: NVIC_SetPendingIRQ(IRQn_Type IRQn) (7)清除中断挂起位: NVIC_ClearPendingIRQ(IRQn_Type IRQn) (8)NVIC_GetActive(IRQn_Type IRQn) (9)设置中断源的中断优先级: NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) (10)获取中断源的中断优先级: NVIC_GetPriority(IRQn_Type IRQn) (11)编码一个中断的优先级,不知道干嘛: NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t SubPriority) (12)解码一个中断的优先级,不知道干嘛: NVIC_DecodePriority (uint32_t Priority, uint32_t PriorityGroup, uint32_t* pPreemptPriority, uint32_t* pSubPriority) (13)系统复位: NVIC_SystemReset(void)
在外设库misc.h定义了针对STM32的NVIC的初始化描述结构体:
typedef struct { uint8_t NVIC_IRQChannel; /* 中断源 */ uint8_t NVIC_IRQChannelPreemptionPriority; /* 抢占优先级 */ uint8_t NVIC_IRQChannelSubPriority; /* 子优先级 */ FunctionalState NVIC_IRQChannelCmd; /* 中断使能或者失能 */ } NVIC_InitTypeDef;
misc.c也定义了针对STM32的NVIC的操作函数:
(1)设置优先级分组寄存器: NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup) (2)初始化NVIC_InitTypeDef类的结构体: NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct) (3)设置中断向量表位置和偏移: NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset) 系统可以选择从SRAM启动,也可以选择从flash启动,对应的启动地址会映射到0地址处,而中断向量表是要被放在0地址处的,所以要将中
断向量表放在SRAM/flash的起始位置。函数参数一的取值为NVIC_VectTab_RAM/NVIC_VectTab_FLASH,参数二的取值必须是0x200的整数倍(STM32就是这么规定的)。 (4)选择进入低功耗模式的条件: NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState) 参数一取值NVIC_LP_SEVONPEND/NVIC_LP_SLEEPDEEP/NVIC_LP_SLEEPONEXIT,参数二取值ENABLE/DISABLE
4. EXTI–外部中断和事件控制器
EXTI有20个中断/事件线,每个GPIO都可以被设置为中断/事件的输入线,占用EXTI0~EXTI15,还有另外4根用于特定的外设事件的EXTI16~EXTI19:
注意,EXTIx与GPIOx的对应关系,EXTI0只能和P[x]0绑定(x = A、B、C、D…),
实现绑定操作的函数声明位于标准库Libraries\STM32F10x_StdPeriph_Driver\inc\stm32f10x_gpio.h中:
GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
参数一GPIO_PortSource的取值为GPIO_PortSourceGPIOx (x = A..G),
参数二GPIO_PinSource的取值为GPIO_PinSourcex(x = 0..15)
这个函数在一般初始化EXTI寄存器时候调用。因为外部中断是GPIO引脚的复用功能,所以同时要开启GPIO复用功能的时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE)
5. EXTI描述结构体的初始化
EXTI描述结构体声明在标准外设库Libraries\STM32F10x_StdPeriph_Driver\inc\stm32f10x_exti.h中:
typedef struct { uint32_t EXTI_Line; /* 中断事件线 */ EXTIMode_TypeDef EXTI_Mode; /* EXTI模式,事件/中断 */ EXTITrigger_TypeDef EXTI_Trigger; /* 触发类型 */ FunctionalState EXTI_LineCmd; /* EXTI使能 */ }EXTI_InitTypeDef;
与EXTI操作相关的函数有:
(1)去除EXTI_InitTypeDef结构体的初始化:EXTI_DeInit(void) (2)初始化EXTI_InitTypeDef结构体: EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct) (3)默认初始化:EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct) (4)EXTI_GenerateSWInterrupt(uint32_t EXTI_Line) 产生一个软件中断 (5)获取产生中断的标志:EXTI_GetFlagStatus(uint32_t EXTI_Line) EXTI_GetITStatus(uint32_t EXTI_Line) (6)清除中断产生标志:EXTI_ClearFlag(uint32_t EXTI_Line) EXTI_ClearITPendingBit(uint32_t EXTI_Line)
获取/清除产生中断的标志的实现是一样的,但是为什么要分成两组函数?
也许这是STM32标准外设库设计者出自于为兼容性考虑吧。有的ARM芯片的中断体系分为两层,也就是说中断信号要抵达NVIC需要两层筛选,同理清除中断标志也需要清除两层。但是在CM3核的ARM只设计了一层,STM32为了兼容其他芯片,依旧还是将函数设计成两层,只不过这两层的实现体是一致的。