STM32開發筆記49:STM32F4+DP83848乙太網通訊指南系列(三):中斷向量
本章為系列指南的第三章,這一章將會在正式進入乙太網的配置和使用之前,複習一下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 */