STM32F4+DP83848以太网通信指南系列(三):中断向量

前言:项目需求使用STM32F407进行以太网通信,并涉及到数据链路层的工业以太网通信,使用LWIP协议栈并不能满足需求,因此需要自己摸清STM32F407调用外部PHY进行网络收发包的过程,并在此基础上尝试自己构建适用于项目的网络协议栈。我基本上是从零开始着手这个项目的,之前只有一些STC51系列和STM32F1系列单片机开发的经验,项目开发过程中学习、参考、借鉴了很多网络上的教程和博客,在此尤其感谢正点原子团队发布的相关视频教程。公司买的开发板主芯片是一颗STM32F407,搭配了一颗DP83848的PHY,因此本系列教程将使用DP83848进行适配,同时原子哥的学习板和教程中是使用LAN8720这颗PHY进行适配的,本系列指南也会花一些篇幅介绍各种PHY与STM32芯片进行适配的方法。

为了您更好地阅读本系列,请点击原创连接进行浏览:

本章为系列指南的第三章,这一章将会在正式进入以太网的配置和使用之前,复习一下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 */