1. 程式人生 > >STM32開發筆記49:STM32F4+DP83848乙太網通訊指南系列(三):中斷向量

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 */