本章为系列指南的第三章,这一章将会在正式进入以太网的配置和使用之前,复习一下STM32的中断以及中断向量,因为我们以后要在中断中响应以太网收包。
中断—嵌入式中的多线程
从51单片机到ARM架构的32位微芯片,到树莓派、Ardunio等单板机,中断的概念对于这些芯片都非常重要。本人是纯软件工程师出身,科班学习时根本没有接触过嵌入式开发,学的都是C++,C#,JAVA,Go这些语言。在我看来嵌入式中的中断就相当于这些高级语言中的多线程,main()函数定义了一条主线程,然后各种配置出来的中断Handle就是游离在主线程之外的各种事件的回调函数,他们会在不同的事件下响应并触发,一旦触发中断,CPU的运算逻辑将会在主线程中打个断点,并立即离开主线程,进入中断函数中去以支线程的方式处理逻辑,分支线程逻辑执行完毕后再回到主线程继续执行逻辑,这时候有一些全局变量可能已经被中断中的逻辑更新了,因此也会出现高级语言多线程编程中常出现的并发冲突问题,因此,对于中断,我总结了以下注意点:
一定要让中断函数能顺利return,而且,尽量迅速地return。
为了能尽快让中断return,我们一般在全局做消息通知,让主线程判断消息,主线程中根据消息状态处理所有业务逻辑,中断只负责发出通知,更新通知。
中断可以嵌套发生,比如A中断执行一半,B中断来了,此时CPU有两个选择:1.立刻离开A,进入B,B执行完了再回到A;2.将A执行完成后,再进入B。
上述两种选择可通过配置中断优先级来确定,如果B配置的优先级比A低,则选择前者,如果B配置的优先级比A高,则选择后者
STM32的中断优先级通过中断向量表来配置,相比51单片机上的线性优先级结构,向量表显得更为灵活,呃。。。复杂。
如果考虑到中断嵌套的情况发生,我们不能将消息通知覆盖,比如回到中断A后将B的消息覆盖掉,并且主线程中的消息通知逻辑也应该有优先响应的逻辑。这一点是我个人的编程经验,以后本系列实际开发时可以观察到。
要想保证嵌入式项目能稳定、流畅地运行,必须尽可能保证逻辑的清晰,主函数的while(1)循环快进快出,每次只处理一件任务;中断快进快出,每次只做必要的运算和消息通知,这样的架构最稳定。
中断向量
在51单片机中,响应优先级一般固定了:外部中断0 > 定时器中断0 > 外部中断1 > 定时器中断1 > 串口中断;优先级如果需要修改是通过IP寄存器来设置的,这里就不展开讲了。
对于STM32来说,配置中断的响应优先级和抢占优先级更加灵活也更加复杂。下面是关于中断向量的知识点:
任何一个需要使用中断的STM32工程,我们都需要一个全局的,配置且仅配置一次的中断分组,函数为:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_X);,X范围从0-4。我看到有很多工程项目中,这个函数调用了一次以上,几乎是每次配置一个小的中断类型时都会调用一次这个函数,每次设置的分组还不一样,可能作者是从各式各样的代码片段中强行拷贝的,这是一个很严重的误区。再次重申,这个分组函数,只需要在初始化阶段调用一次!
在设计整个嵌入式工程之前,我们需要大致地梳理一下项目中需要用到的中断类型,以及它们的优先级关系,以此来确定X的数值。
中断优先级关系有两种,一种是抢占优先级,另一种是响应优先级,前者的优先级强度要高于后者。
抢占优先级:主线程运行时,A中断产生了,CPU离开主线程,进入A的中断函数,A中断函数执行了一半,B中断产生了,如果B的抢占优先级高于A,则CPU会立刻离开A,进入B的中断函数,执行完B再回到A,执行完A再回到主线程;如果B的抢占优先级等于或者低于A,此时CPU并不会离开A,而是将A全部执行结束,再进入B,执行完B后,再回到主线程。
响应优先级:主线程运行时,A中断,B中断同时产生,并且它们的抢占优先级相同,此时CPU根据AB的响应优先级等级判断需要首先执行谁的中断函数。
从上述的两种场景可以看出,抢占优先级的强度要明显高于响应优先级,只有在很特殊的场景下,两个不同类型的中断才会在同一时间点发生,因此响应优先级的力度相对较弱,而且抢占优先级关系到中断能否嵌套执行,这关系到整个系统架构的流程走向,比较重要。
STM32分配了4个bit让开发者对每一类型的中断进行优先级的配置,包括抢占优先级和响应优先级。为了充分利用这4个bit,并且能够灵活配置让其满足不同开发者的需求,STM32做了一个分组配置,在不同的分组情况下,这4个bit分给嵌套和响应优先级的位数不同,在Keil中查看NVIC_PriorityGroup_0的宏定义,在misc.h文件中有以下代码:
#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 */
注释中pre-emption priority就是我上文所说的抢占优先级,subpriority则是响应优先级,可以看到,针对不同的分组,STM32允许使用4个bit中不同的位数来分别表示其抢占优先级和响应优先级。关于响应优先级的英文subpriority,应该是子优先级的意思,而pre-emption priority英文我查了一下,确实没找到很贴切的翻译。
无论是抢占优先级还是响应优先级,都是数值小的优先级高。
根据以上知识点,假设我们规划的项目需要六个中断,分别是UART中断,SPI中断,以太网中断,SysTick中断,外部中断1和外部中断2。SysTick中断优先级最高,并且能够嵌入任何其他的中断,以太网中断次高,下面是UART中断,SPI中断,最低的是外部中断1+外部中断2,但当外部中断1和外部中断2同时发生时,我们希望2优先响应。根据以上的需求,我们先规划一下抢占中断的层次,依次是:
SysTick中断 > 以太网中断 > UART中断 > SPI中断 > 外部中断1和外部中断2
然后规划一下外部中断1和外部中断2的响应优先级:外部中断2 > 外部中断1
OK,根据以上分析,我们需要1个bit来代表响应中断优先级,另外的3个bit可以用来代表抢占优先级,因此,配置中断向量的分组为NVIC_PriorityGroup_3是一个合理的配置。
Ethernet中断
搞懂中断向量分组的概念后,我们需要对每一个不同的中断配置其抢占优先级和响应优先级,以Ethernet中断为例,我们使用以下代码配置:
void ETH_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable the Ethernet global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = ETH_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
以上代码比较容易理解,先定义了一个结构体对象,然后配置好要设置的中断类型,抢占优先级,响应优先级,最后传递给NVIC_Init()函数,使用中断。
对于任意中断类型,都可以使用上述代码配置,不同的中断类型宏定义可以到stm32f4xx.h文件中去查看,在该文件的172行-268行为STM32F407共定义了约81个中断类型。
附:节选中断类型片段代码如下:
typedef enum IRQn
{
/****** Cortex-M4 Processor Exceptions Numbers ****************************************************************/
NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt */
MemoryManagement_IRQn = -12, /*!< 4 Cortex-M4 Memory Management Interrupt */
BusFault_IRQn = -11, /*!< 5 Cortex-M4 Bus Fault Interrupt */
UsageFault_IRQn = -10, /*!< 6 Cortex-M4 Usage Fault Interrupt */
SVCall_IRQn = -5, /*!< 11 Cortex-M4 SV Call Interrupt */
DebugMonitor_IRQn = -4, /*!< 12 Cortex-M4 Debug Monitor Interrupt */
PendSV_IRQn = -2, /*!< 14 Cortex-M4 Pend SV Interrupt */
SysTick_IRQn = -1, /*!< 15 Cortex-M4 System Tick Interrupt */
/****** STM32 specific Interrupt Numbers **********************************************************************/
WWDG_IRQn = 0, /*!< Window WatchDog Interrupt */
PVD_IRQn = 1, /*!< PVD through EXTI Line detection Interrupt */
TAMP_STAMP_IRQn = 2, /*!< Tamper and TimeStamp interrupts through the EXTI line */
RTC_WKUP_IRQn = 3, /*!< RTC Wakeup interrupt through the EXTI line */
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_Stream0_IRQn = 11, /*!< DMA1 Stream 0 global Interrupt */
DMA1_Stream1_IRQn = 12, /*!< DMA1 Stream 1 global Interrupt */
DMA1_Stream2_IRQn = 13, /*!< DMA1 Stream 2 global Interrupt */
DMA1_Stream3_IRQn = 14, /*!< DMA1 Stream 3 global Interrupt */
DMA1_Stream4_IRQn = 15, /*!< DMA1 Stream 4 global Interrupt */
DMA1_Stream5_IRQn = 16, /*!< DMA1 Stream 5 global Interrupt */
DMA1_Stream6_IRQn = 17, /*!< DMA1 Stream 6 global Interrupt */
ADC_IRQn = 18, /*!< ADC1, ADC2 and ADC3 global Interrupts */
#if defined (STM32F40_41xxx)
CAN1_TX_IRQn = 19, /*!< CAN1 TX Interrupt */
CAN1_RX0_IRQn = 20, /*!< CAN1 RX0 Interrupt */
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_TIM9_IRQn = 24, /*!< TIM1 Break interrupt and TIM9 global interrupt */
TIM1_UP_TIM10_IRQn = 25, /*!< TIM1 Update Interrupt and TIM10 global interrupt */
TIM1_TRG_COM_TIM11_IRQn = 26, /*!< TIM1 Trigger and Commutation Interrupt and TIM11 global 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 (A and B) through EXTI Line Interrupt */
OTG_FS_WKUP_IRQn = 42, /*!< USB OTG FS Wakeup through EXTI line interrupt */
TIM8_BRK_TIM12_IRQn = 43, /*!< TIM8 Break Interrupt and TIM12 global interrupt */
TIM8_UP_TIM13_IRQn = 44, /*!< TIM8 Update Interrupt and TIM13 global interrupt */
TIM8_TRG_COM_TIM14_IRQn = 45, /*!< TIM8 Trigger and Commutation Interrupt and TIM14 global interrupt */
TIM8_CC_IRQn = 46, /*!< TIM8 Capture Compare Interrupt */
DMA1_Stream7_IRQn = 47, /*!< DMA1 Stream7 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_DAC_IRQn = 54, /*!< TIM6 global and DAC1&2 underrun error interrupts */
TIM7_IRQn = 55, /*!< TIM7 global interrupt */
DMA2_Stream0_IRQn = 56, /*!< DMA2 Stream 0 global Interrupt */
DMA2_Stream1_IRQn = 57, /*!< DMA2 Stream 1 global Interrupt */
DMA2_Stream2_IRQn = 58, /*!< DMA2 Stream 2 global Interrupt */
DMA2_Stream3_IRQn = 59, /*!< DMA2 Stream 3 global Interrupt */
DMA2_Stream4_IRQn = 60, /*!< DMA2 Stream 4 global Interrupt */
ETH_IRQn = 61, /*!< Ethernet global Interrupt */
ETH_WKUP_IRQn = 62, /*!< Ethernet Wakeup through EXTI line Interrupt */
CAN2_TX_IRQn = 63, /*!< CAN2 TX Interrupt */
CAN2_RX0_IRQn = 64, /*!< CAN2 RX0 Interrupt */
CAN2_RX1_IRQn = 65, /*!< CAN2 RX1 Interrupt */
CAN2_SCE_IRQn = 66, /*!< CAN2 SCE Interrupt */
OTG_FS_IRQn = 67, /*!< USB OTG FS global Interrupt */
DMA2_Stream5_IRQn = 68, /*!< DMA2 Stream 5 global interrupt */
DMA2_Stream6_IRQn = 69, /*!< DMA2 Stream 6 global interrupt */
DMA2_Stream7_IRQn = 70, /*!< DMA2 Stream 7 global interrupt */
USART6_IRQn = 71, /*!< USART6 global interrupt */
I2C3_EV_IRQn = 72, /*!< I2C3 event interrupt */
I2C3_ER_IRQn = 73, /*!< I2C3 error interrupt */
OTG_HS_EP1_OUT_IRQn = 74, /*!< USB OTG HS End Point 1 Out global interrupt */
OTG_HS_EP1_IN_IRQn = 75, /*!< USB OTG HS End Point 1 In global interrupt */
OTG_HS_WKUP_IRQn = 76, /*!< USB OTG HS Wakeup through EXTI interrupt */
OTG_HS_IRQn = 77, /*!< USB OTG HS global interrupt */
DCMI_IRQn = 78, /*!< DCMI global interrupt */
CRYP_IRQn = 79, /*!< CRYP crypto global interrupt */
HASH_RNG_IRQn = 80, /*!< Hash and Rng global interrupt */
FPU_IRQn = 81 /*!< FPU global interrupt */
#endif /* STM32F40_41xxx */