STM32F103ZET6的中断管理
1、STM32的中断
STM32的中断管理是属于内核部分的,所以中断管理的寄存器也是属于内核组,不属于芯片外设,在查看相关资料的时候,需要查看相对应的内核手册。
STM32F103ZET6是Cortex-M3内核的IC。Cortex-M3内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。但是STM32并没有完全使用Cortex-M3内核的全部中断,只是用了其中的一部分。
在STM32中,有时候中断也称为异常、系统异常,把它们统一理解为中断就可以了。
2、中断编号
STM32不同类型的芯片IC所具有的中断个数是不一样的,在HAL库中,可以通过查找IRQn_Type这个结构体来查看该IC所具有的中断。IRQn_Tyepe对该芯片的中断进行了编号。STM32F103ZET6的IRQn_Type结构体定义在stm32f103xe.h头文件中,如下:
typedef enum
{
/****** Cortex-M3 Processor Exceptions Numbers ***************************************************/
NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt */
HardFault_IRQn = -13, /*!< 3 Cortex-M3 Hard Fault Interrupt */
MemoryManagement_IRQn = -12, /*!< 4 Cortex-M3 Memory Management Interrupt */
BusFault_IRQn = -11, /*!< 5 Cortex-M3 Bus Fault Interrupt */
UsageFault_IRQn = -10, /*!< 6 Cortex-M3 Usage Fault Interrupt */
SVCall_IRQn = -5, /*!< 11 Cortex-M3 SV Call Interrupt */
DebugMonitor_IRQn = -4, /*!< 12 Cortex-M3 Debug Monitor Interrupt */
PendSV_IRQn = -2, /*!< 14 Cortex-M3 Pend SV Interrupt */
SysTick_IRQn = -1, /*!< 15 Cortex-M3 System Tick Interrupt */
/****** STM32 specific Interrupt Numbers *********************************************************/
WWDG_IRQn = 0, /*!< Window WatchDog Interrupt */
PVD_IRQn = 1, /*!< PVD through EXTI Line detection Interrupt */
TAMPER_IRQn = 2, /*!< Tamper Interrupt */
RTC_IRQn = 3, /*!< RTC global Interrupt */
FLASH_IRQn = 4, /*!< FLASH global Interrupt */
RCC_IRQn = 5, /*!< RCC global Interrupt */
EXTI0_IRQn = 6, /*!< EXTI Line0 Interrupt */
EXTI1_IRQn = 7, /*!< EXTI Line1 Interrupt */
EXTI2_IRQn = 8, /*!< EXTI Line2 Interrupt */
EXTI3_IRQn = 9, /*!< EXTI Line3 Interrupt */
EXTI4_IRQn = 10, /*!< EXTI Line4 Interrupt */
DMA1_Channel1_IRQn = 11, /*!< DMA1 Channel 1 global Interrupt */
DMA1_Channel2_IRQn = 12, /*!< DMA1 Channel 2 global Interrupt */
DMA1_Channel3_IRQn = 13, /*!< DMA1 Channel 3 global Interrupt */
DMA1_Channel4_IRQn = 14, /*!< DMA1 Channel 4 global Interrupt */
DMA1_Channel5_IRQn = 15, /*!< DMA1 Channel 5 global Interrupt */
DMA1_Channel6_IRQn = 16, /*!< DMA1 Channel 6 global Interrupt */
DMA1_Channel7_IRQn = 17, /*!< DMA1 Channel 7 global Interrupt */
ADC1_2_IRQn = 18, /*!< ADC1 and ADC2 global Interrupt */
USB_HP_CAN1_TX_IRQn = 19, /*!< USB Device High Priority or CAN1 TX Interrupts */
USB_LP_CAN1_RX0_IRQn = 20, /*!< USB Device Low Priority or CAN1 RX0 Interrupts */
CAN1_RX1_IRQn = 21, /*!< CAN1 RX1 Interrupt */
CAN1_SCE_IRQn = 22, /*!< CAN1 SCE Interrupt */
EXTI9_5_IRQn = 23, /*!< External Line[9:5] Interrupts */
TIM1_BRK_IRQn = 24, /*!< TIM1 Break Interrupt */
TIM1_UP_IRQn = 25, /*!< TIM1 Update Interrupt */
TIM1_TRG_COM_IRQn = 26, /*!< TIM1 Trigger and Commutation Interrupt */
TIM1_CC_IRQn = 27, /*!< TIM1 Capture Compare Interrupt */
TIM2_IRQn = 28, /*!< TIM2 global Interrupt */
TIM3_IRQn = 29, /*!< TIM3 global Interrupt */
TIM4_IRQn = 30, /*!< TIM4 global Interrupt */
I2C1_EV_IRQn = 31, /*!< I2C1 Event Interrupt */
I2C1_ER_IRQn = 32, /*!< I2C1 Error Interrupt */
I2C2_EV_IRQn = 33, /*!< I2C2 Event Interrupt */
I2C2_ER_IRQn = 34, /*!< I2C2 Error Interrupt */
SPI1_IRQn = 35, /*!< SPI1 global Interrupt */
SPI2_IRQn = 36, /*!< SPI2 global Interrupt */
USART1_IRQn = 37, /*!< USART1 global Interrupt */
USART2_IRQn = 38, /*!< USART2 global Interrupt */
USART3_IRQn = 39, /*!< USART3 global Interrupt */
EXTI15_10_IRQn = 40, /*!< External Line[15:10] Interrupts */
RTC_Alarm_IRQn = 41, /*!< RTC Alarm through EXTI Line Interrupt */
USBWakeUp_IRQn = 42, /*!< USB Device WakeUp from suspend through EXTI Line Interrupt */
TIM8_BRK_IRQn = 43, /*!< TIM8 Break Interrupt */
TIM8_UP_IRQn = 44, /*!< TIM8 Update Interrupt */
TIM8_TRG_COM_IRQn = 45, /*!< TIM8 Trigger and Commutation Interrupt */
TIM8_CC_IRQn = 46, /*!< TIM8 Capture Compare Interrupt */
ADC3_IRQn = 47, /*!< ADC3 global Interrupt */
FSMC_IRQn = 48, /*!< FSMC global Interrupt */
SDIO_IRQn = 49, /*!< SDIO global Interrupt */
TIM5_IRQn = 50, /*!< TIM5 global Interrupt */
SPI3_IRQn = 51, /*!< SPI3 global Interrupt */
UART4_IRQn = 52, /*!< UART4 global Interrupt */
UART5_IRQn = 53, /*!< UART5 global Interrupt */
TIM6_IRQn = 54, /*!< TIM6 global Interrupt */
TIM7_IRQn = 55, /*!< TIM7 global Interrupt */
DMA2_Channel1_IRQn = 56, /*!< DMA2 Channel 1 global Interrupt */
DMA2_Channel2_IRQn = 57, /*!< DMA2 Channel 2 global Interrupt */
DMA2_Channel3_IRQn = 58, /*!< DMA2 Channel 3 global Interrupt */
DMA2_Channel4_5_IRQn = 59, /*!< DMA2 Channel 4 and Channel 5 global Interrupt */
} IRQn_Type;
3、NVIC
NVIC的简称是嵌套中断向量控制器,它控制着整个芯片中断相关的功能。NVIC是Cortex-M3内核里面的一个外设,它们共同完成对中断的响应。但是ST在设计芯片的时候对Cortex-M3内核里面的NVIC进行了裁剪,把不需要的部分去掉,所以说STM32的NVIC是Cortex-M3的NVIC的一个子集。
在HAL库中,NVIC的结构体代码如下:
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;
NVIC_Type结构体在core_cm3.h文件中定义 。
NVIC_Type结构体中的RSERVED都是保留位,即给每个寄存器都预留了很多位,方便以后可以扩展功能。
ISER[8]中断使能寄存器组:
Cortxe-M3内核支持256个中断,ISER寄存器的每一个bit位控制一个中断使能,ISER是32位寄存器,8个32位的寄存器组成了256个中断控制位。
但需要注意的是STM32F103的可屏蔽中断只有60个,所以用不到8个ISER寄存器,只用到了ISER[0]和ISER[1]这两个寄存器,ISER[0]和ISER[1]可以组成成64个中断使能位。
ISER[0]的bit0~bit31分别对应中断0~31;ISER[1]的bit0~bit31分别对应中断32~59。
当需要使能某个外设中断时,就必须找到ISER寄存器的对应位,并将其置1,具体是置位哪一个位,可以根据结构体IRQn_Type来查询。比如说WWDG_IRQn在IRQn_Type中的值为0,那么在ISER寄存器中的中断使能位就是ISER[0]中的bit0位。
假设某外设中的在IRQn_Type的中断编号为IRQn,则可以根据如下操作置位该外设的中断使能位:
ISER[ IRQn >> 5 ] = (1<<(IRQn & 31))
IRQn & 31相当于只取低5位的值,因为只有对ISER寄存器写1才能使能中断,但是写0是不会有任何效果的,所以这里直接用等于号而不需要用”|”号。
ICER[8]中断失能寄存器组:
ICER寄存器的作用刚好与ISER寄存器的作用相反,ICER寄存器是用来清除某个中断的使能的。ICER寄存器的使用跟ISER是一样的,只是功能相反。
因为NIVC的寄存器都是写1有效,而写0是无效的,不能通过给ISER寄存器写0来清除中断使能位,所以增加了ICER寄存器与ISER寄存器对应。
ICER寄存器的使用方式可以参考ISER。
ISPR[8]中断挂起寄存器组:
每个位的中断与ISER是一样的,只是功能不同。ISPR寄存器如果置1是将一个正在进行的中断挂起,转而执行同级别或更高级别的中断。同ISER一样,写0无效。
ICPR[8]中断解除挂起寄存器组:
ICPR寄存器与ISPR寄存器功能刚好相反,通过置位ICPR寄存器来解除ISPR寄存器挂起的中断。同ISER一样,写0无效。
一般很少用到ISPR寄存器和ICPR寄存器。
IABR[8]中断激活标志位寄存器组:
IABR寄存器是一个状态寄存器,它是一个只读寄存器。通过读取IABR寄存器的值,可以知道当前执行的中断是哪一个。IABR寄存器的对应位在中断执行完之后又硬件自动清零。IABR寄存器的中断位与ISER是一样的。
IP[240]中断优先级控制寄存器组:
IP寄存器与STM32的中断分组密切相关。IP寄存器组由240个8bit的寄存器组成,每个可屏蔽中断占用8bit,总共可以表示240个可屏蔽中断,但是STM32F103只用到了IP[59]~IP[0]这60个。
IP寄存器的8个bit并没有全部使用,只用了高4位,也就是bit4~bit7位。Bit4~bit7位又分为抢占优先级和响应先级,抢占优先级在前,响应先级在后。而对于抢占优先级和响应优先级各占几位,则需要根据SCB的AIRCR寄存器中的中断分组设置有决定。
4、中断分组
STM32将中断分为5个组,组0~组4。STM32中断的分组设置是由SCB的AIRCR寄存器的bit8~bit10为来设置的。而抢占优先级和响应优先级则需要根据分组来决定,如下:
-
- 中断分组0:AIRCR[10:8] = 111,那么对于IP寄存器的bit7~bit4的占位情况为:0位抢占优先级和4位响应优先级。
- 中断分组1:AIRCR[10:8] = 110,那么对于IP寄存器的bit7~bit4的占位情况为:1位抢占优先级和3位响应优先级。
- 中断分组2:AIRCR[10:8] = 101,那么对于IP寄存器的bit7~bit4的占位情况为:2位抢占优先级和2位响应优先级。
- 中断分组3:AIRCR[10:8] = 100,那么对于IP寄存器的bit7~bit4的占位情况为:3位抢占优先级和1位响应优先级。
- 中断分组4:AIRCR[10:8] = 011,那么对于IP寄存器的bit7~bit4的占位情况为:4位抢占优先级和0位响应优先级。
举个例子来说,如果中断分组设置为3,即AIRCR[10:8] = 100,那么STM32的所有可屏蔽中断,每个中断的IP寄存器的bit7~bit5位是抢占优先级,bit4位是响应优先级,每个可屏蔽中断,可以设置为0~7的抢占优先级,0~1的响应优先级。
需要注意的是SCB的AIRCR寄存具有写保护,需要对SCB的AIRCR寄存的高16位写入0x05FA这个密钥才能修改AIIRCR寄存器。
抢占优先级和响应优先级的区别:
抢占优先级别高于响应优先级,而数值越小所代表的优先级就越高。
如果两个中断的抢占优先级和响应优先级都是一样的,则哪个中断先发生就先执行。
如果抢占优先级高不一样,那么抢占优先级高的中断可以打断正在进行的抢占优先级比较低的中断。
如果抢占优先级和响应优先级都相同的话,就根据中断编号来决定谁先执行,中断编号越小,优先级越高。
如果中断的抢占优先级相同,响应优先级不同的中断是不可以相互打断的。
举例说明如下:设定中断分组为2,将RTC的中断的抢占优先级设为2,响应优先级设为1。EXTI0的中断的抢占优先级设为3,响应优先级设为0。EXTI1的抢占优先级设为2,响应优先级设为0。因为数值越小所代表的优先级就越高,那么这3个中断的优先级顺序为:EXTI1中断 > RTC中断 >EXTI0中断。EXTI1和RTC中断都可以打断EXTI0中断;而EXTI1和RTC中断却不能相互打断。
5、中断编程
首先使能某个外设中断,这个具体由每个外设的相关中断使能控制位控制。比如串口由发送完成中断,接收完成中断,这两个中断都是由串口控制寄存器的相关中断使能位控制。
然后设置中断的分组和中断优先级,关于中断分组,一般只设置一次就好了,设置过后就不再更改,重复设置为同一个组号也可以。
使能中断,这里使能的是ISER寄存器的中断使能位。
在HAL库函数中,对NVIC的操作封装在stm32f1xx_hal_cortex.c文件中,其函数的声明如下:
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
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_SystemReset(void);
HAL_NVIC_SetPriorityGrouping函数是设置中断分组:
HAL_NVIC_SetPriority函数是设置中断的优先级。IRQn是对应的中断编号;PreemptPriority是设置抢占优先级;SubPriority是设置响应优先级。
HAL_NVIC_EnableIRQ函数是使能中断,操作的是ISER寄存器。
HAL_NVIC_DisableIRQ函数是失能中断,操作的是ICER寄存器。
void HAL_NVIC_SystemReset函数是软件复位操作。
6、中断服务函数
在STM32的启动文件当中,为每个中断都写了一个中断服务函数,只是启动文件中的终端服务函数都是空的,为的只是初始化中断向量表。实际的中断服务函数都需要重新编写。
在HAL库中系统异常和中断服务函数一般写在stm32f10x_it.c文件中,当然也可以写在其它文件,但是必须保证不能出现两个文件具有同一个中断服务函数,否者会报错。
还需要注意的就是中断服务函数的函数名必须跟启动文件里面预先设置的函数名一样,如果不一样,当触发中断时,系统在中断向量表中找不到中断服务函数的入口,就会直接跳转到启动文件里预先写好的空函数,并且在里面循环,造成死机。
7、中断屏蔽功能
PRIMASK与FAULTMASK特殊功能寄存器
PRIMASK用于禁止除了NMI和硬fault之外的所有异常和中断。也就是说PRIMASK能禁止中断,可以当做关总中断来使用,但是不能关闭NMI和硬fault的中断。PRIMASK的使用方式如下:
关闭中断:
MOV R0, #1
MSR PRIMASK, R0
将1的值写入PRIMASK中,这样就关闭除了NMI和硬fault之外的中断。
也可以通过CPS指令快速完成关闭中断:
CPSID i
开中断:
MOV R0, #1
MSR PRIMASK, R0
将PRIMASK的值清0,开启中断。
也可以通过CPS指令快速完成关闭中断:
CPSIE i
而FAULTMSK寄存器能直接把当前优先级改为-1,这样一来,连硬fault中断都被屏蔽了,但是FAULTMSK寄存器不能屏蔽NMI中断,而且FAULTMASK会在异常退出时自动清0,FAULTMASK的使用方式如下:
关闭中断:
MOV R0, #1
MSR FAULTMASK, R0
将1的值写入FAULTMASK中,这样就关闭除了NMI之外的中断。
开中断
MOV R0, #1
MSR FAULTMASK, R0
将FAULTMASK的值清0,开启中断。
BASEPRI寄存器
使用BASEPRI寄存器能根据优先级屏蔽部分中断。中断的优先级设置的数值越小,那么优先级就越高,如果将BASEPRI的值设为0x40,优先级设置由于是高4位有效,也就相当于BASEPRI的值设为2,那么低于或等于优先级2的中断教会被屏蔽,即优先级0和优先级1的中断可响应;优先级2、优先级3以及以上的优先级中断将被屏蔽。
编程如下:
MOV R0, #0x40
MSR BASEPRI,R0
如果要取消BASEPRI对中断的屏蔽,则如下:
MOV R0, #0
MSR BASEPRI,R0
系统中表达优先级的位数,也同样影响BASEPRI中有意义的位数。如果系统中只使用3个位来表达优先级,则BASEPRI有意义的值仅为0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0以及0xE0。