STM32F4+DP83848以太网通信指南系列(五):MAC+DMA配置

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

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

本章为系列指南的第五章,讲述STM32F407上MAC层以及其DMA的配置。读者需要提前准备的文档资料是STM32的中文数据手册:ST-RM0090 STM32F4参考手册中文版.pdf

我们在第一章知识储备章节说到,STM32F407会在168MHz主频之外分配一定的时间释放总线数据用来处理DMA,这其中就包含MAC层的DMA,复习一下STM32F4的总线架构图,(图片来自RM0090ST中文STM32F4手册P50):

我们看到,在上图红框标注的的S6阶段,就是MAC层的DMA总线,CPU会在核心逻辑之外,有专门的时间片轮转周期处理这一阶段的DMA,所有的数据读写都是DMA来控制,不需要我们在核心逻辑中编写。

本章的要解决的任务只有一个:能编写一个自己构建的DP83848Init()函数,就像任何类似的UARTInit()DelayInit()等函数一样,在main()函数初始化阶段调用,完成一系列启动网卡的操作。

这个任务看似简单,其实比较复杂,因此本章篇幅也会比较多。这个函数包含多个子任务:

  1. GPIO的初始化
  2. MAC层及DMA配置
  3. 中断配置
  4. 网络服务启动

一、GPIO初始化

这个我们在上一章已经完成了,参见:《STM32F4+DP83848以太网通信指南第四章:PHY配置》

二、MAC层初始化

2.1 编码

首先编写以下函数:

static void ETH_MACDMA_Config(void) {
    ETH_InitTypeDef ETH_InitStructure;

    /* Enable ETHERNET clock  */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_ETH_MAC_Tx | RCC_AHB1Periph_ETH_MAC_Rx, ENABLE);

    ETH_DeInit(); /* Reset ETHERNET on AHB Bus */

    ETH_SoftwareReset();   /* Software reset */

    while (ETH_GetSoftwareResetStatus() == SET);  /* Wait for software reset */

    /* ETHERNET Configuration
     * Call ETH_StructInit to get a default structure
     * if you don't like to configure all ETH_InitStructure parameter
     */
    ETH_StructInit(Ð_InitStructure);

    /* Fill ETH_InitStructure parametrs */
    /*------------------------   MAC   -----------------------------------*/
    ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Enable; /* 10M/100M自适应 */

    ETH_InitStructure.ETH_LoopbackMode = ETH_LoopbackMode_Disable;
    ETH_InitStructure.ETH_RetryTransmission = ETH_RetryTransmission_Disable;
    ETH_InitStructure.ETH_AutomaticPadCRCStrip = ETH_AutomaticPadCRCStrip_Disable;
    ETH_InitStructure.ETH_ReceiveAll = ETH_ReceiveAll_Enable; /* 混杂模式 */
    ETH_InitStructure.ETH_BroadcastFramesReception = ETH_BroadcastFramesReception_Enable;
    ETH_InitStructure.ETH_PromiscuousMode = ETH_PromiscuousMode_Disable;
    ETH_InitStructure.ETH_MulticastFramesFilter = ETH_MulticastFramesFilter_Perfect;
    ETH_InitStructure.ETH_UnicastFramesFilter = ETH_UnicastFramesFilter_Perfect;
    ETH_InitStructure.ETH_ChecksumOffload = ETH_ChecksumOffload_Enable; /* 在IP包收发时使用硬件计算校验和 */

    /*------------------------   DMA   -----------------------------------*/

    /* When we use the Checksum offload feature, we need to enable the Store and Forward mode:
    the store and forward guarantee that a whole frame is stored in the FIFO, so the MAC can insert/verify the checksum,
    if the checksum is OK the DMA can handle the frame otherwise the frame is dropped */
    ETH_InitStructure.ETH_DropTCPIPChecksumErrorFrame = ETH_DropTCPIPChecksumErrorFrame_Enable;
    ETH_InitStructure.ETH_ReceiveStoreForward = ETH_ReceiveStoreForward_Enable;
    ETH_InitStructure.ETH_TransmitStoreForward = ETH_TransmitStoreForward_Enable;

    ETH_InitStructure.ETH_ForwardErrorFrames = ETH_ForwardErrorFrames_Disable;
    ETH_InitStructure.ETH_ForwardUndersizedGoodFrames = ETH_ForwardUndersizedGoodFrames_Disable;
    ETH_InitStructure.ETH_SecondFrameOperate = ETH_SecondFrameOperate_Enable;
    ETH_InitStructure.ETH_AddressAlignedBeats = ETH_AddressAlignedBeats_Enable;
    ETH_InitStructure.ETH_FixedBurst = ETH_FixedBurst_Enable;
    ETH_InitStructure.ETH_RxDMABurstLength = ETH_RxDMABurstLength_32Beat;
    ETH_InitStructure.ETH_TxDMABurstLength = ETH_TxDMABurstLength_32Beat;
    ETH_InitStructure.ETH_DMAArbitration = ETH_DMAArbitration_RoundRobin_RxTx_2_1;

    /* 
     * 初始化时,RJ45变压器如果没有上电,ETH_Init会返回非0值,
     * 为确保系统上电后正确初始化网络,需要每500ms尝试初始化一次PHY,直到成功
     * DP83848_PHY_ADDRESS为上一章分析并定义的0x01
     */
    while( !ETH_Init(Ð_InitStructure, DP83848_PHY_ADDRESS) ) {
        vu32 sdelay = 84000000;
        while(sdelay--);
    }

    /* Enable the Ethernet Rx Interrupt */
    ETH_DMAITConfig(ETH_DMA_IT_NIS | ETH_DMA_IT_R, ENABLE);
}

上面的代码,配合注释虽然简单明了,但如果你直接copy到项目中编译,肯定会出现大把的错误,显然,从第一个结构体的定义就找不到,下面还有很多ETH的函数也找不到,那么,这个ETH_InitTypeDef结构体,以及下面ETH_DeInit()ETH_SoftwareReset()ETH_StructInit()等函数在哪里呢?

2.2 stm32f4x7_eth.c文件和Ethernet库函数

在第一章知识储备中,已经说过了,STM32F4标准库中并未带有ETH方面的库函数,在STM32官网搜索LWIP能够搜索到官方使用LWIP的DEMO,官方文档编号是STSW-STM32070,在这份文档中有LWIP协议栈,并且有官方的调用样例,我们可以从中挖掘到ETH部分的库函数,资料我下载回来,并且上传到CDN了,可以直接下载:en.stsw-stm32070.zip

这份文档解压后,在/STM32F4x7_ETH_LwIP_V1.1.1/Libraries/STM32F4x7_ETH_Driver路径下面的stm32f4x7_eth.c以及配套的.h和一个stm32f4x7_eth_conf_template.h文件是比较关键的,类似于标准库提供的那些I2C,UART,SPI等库函数文件。

我们将这三个文件全部引入工程,并且重命名stm32f4x7_eth_conf_template.hstm32f4x7_eth_conf.h,所有配置均保持跟官方一致的默认配置,这样在本文2.1章节提到的结构体和那些ETH函数,就有定义了,编译起来也不会出错了。

有了官方DEMO的样例文件,我们回过头来看一下2.1章节中出现的一大段初始化MAC层的代码,并不是我原创自己想当然瞎写出来的,我们可以考证一下其出处。

打开STM32F4x7_ETH_LwIP_V1.1.1\Project\Standalone\udp_echo_client\src\stm32f4x7_eth_bsp.c文件后,你会发现有相似的代码描述,我们看懂注释后,可以做适当的配置调整。

三、中断配置

这个环节相对比较简单,直接编码:

void ETH_NVIC_Config(void) {
    NVIC_InitTypeDef   NVIC_InitStructure;

    /* Enable the Ethernet global Interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = ETH_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

ETH_IRQn宏定义是STM32库中现成的ETH中断描述,中断优先级配置的1-0,相对比较高,但仍留了一位优先级给系统定时器中断排在前面。

现在来回顾一下,目前我们已经拥有三个准备好的函数,分别是ETH_GPIO_Config()ETH_MACDMA_Config()ETH_NVIC_Config,第一个函数在上一章编写好的,后面两个是刚刚编写的。下面封装一个总体函数:

void ETH_BSP_Config(void) {
    ETH_GPIO_Config();

    ETH_NVIC_Config();      // Config NVIC for Ethernet

    ETH_MACDMA_Config();    // Configure the Ethernet MAC/DMA
}

以上,所有代码皆在stm32f4x7_eth_bsp.c文件中。

四、网络服务启动

我们现在已经完成了大部分的初始化和配置任务,下面,我们需要着手编写以太网服务的启动工作代码,也就是我们这一章节的核心任务,编写DP83848Init()函数:

void DP83848Init(uint8_t* HWADDR){
    int i;
    /* Configure ethernet (GPIOs, clocks, MAC, DMA) */
    ETH_BSP_Config();

    /* initialize MAC address in ethernet MAC */
    ETH_MACAddressConfig(ETH_MAC_Address0, HWADDR);
    /* Initialize Tx Descriptors list: Chain Mode */
    ETH_DMATxDescChainInit(DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);
    /* Initialize Rx Descriptors list: Chain Mode  */
    ETH_DMARxDescChainInit(DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);

    /* Enable the TCP, UDP and ICMP checksum insertion for the Tx frames */
    for(i = 0; i < ETH_TXBUFNB; i++) {
        ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i], ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
    }
    ETH_Start();
}

其中,ETH_BSP_Config()来自于我们上一阶段封装好的函数,其余的调用依然来自stm32f4x7_eth.c文件。虽然这里的注释描写得也十分清晰的了,这里有一点需要提一下,在上述代码的第9行和第11行就是配置了两个DMA的链状描述符,关于链状DMA描述符和环装DMA描述符,如果需要理解得更多一点,可以观看原子哥的视频教程,那里面花了一些篇幅介绍,不过我个人感觉视频中讲的也不是特别清楚,视频下载地址:https://pan.baidu.com/s/1jIvvTcy,暂时我们先这么用吧。

按照惯例,上面那一段函数也不是我突发奇想,心血来潮,闭着眼睛毫无根据写下的,我们来看看这段函数的出处,依然在之前那份LWIP文档里面,路径为:STM32F4x7_ETH_LwIP_V1.1.1\Utilities\Third_Party\lwip-1.4.1\port\STM32F4x7\Standalone\ethernetif.c,看第76行low_level_init()函数,顾名思义,low_level_init()就是底层初始化的意思,我们重点观察这个函数的后半部分,前面操作netif结构体的部分我们暂时用不到,后面部分调用ETH库函数的函数就是我们需要的。代码截取如下:

static void low_level_init(struct netif *netif)
{
#ifdef CHECKSUM_BY_HARDWARE
  int i; 
#endif
  /* set MAC hardware address length */
  netif->hwaddr_len = ETHARP_HWADDR_LEN;

  /* set MAC hardware address */
  netif->hwaddr[0] =  MAC_ADDR0;
  netif->hwaddr[1] =  MAC_ADDR1;
  netif->hwaddr[2] =  MAC_ADDR2;
  netif->hwaddr[3] =  MAC_ADDR3;
  netif->hwaddr[4] =  MAC_ADDR4;
  netif->hwaddr[5] =  MAC_ADDR5;

  /* initialize MAC address in ethernet MAC */ 
  ETH_MACAddressConfig(ETH_MAC_Address0, netif->hwaddr); 

  /* maximum transfer unit */
  netif->mtu = 1500;

  /* device capabilities */
  /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;

  /* Initialize Tx Descriptors list: Chain Mode */
  ETH_DMATxDescChainInit(DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);
  /* Initialize Rx Descriptors list: Chain Mode  */
  ETH_DMARxDescChainInit(DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);

#ifdef CHECKSUM_BY_HARDWARE
  /* Enable the TCP, UDP and ICMP checksum insertion for the Tx frames */
  for(i=0; i<ETH_TXBUFNB; i++)
    {
      ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i], ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
    }
#endif

   /* Note: TCP, UDP, ICMP checksum checking for received frame are enabled in DMA config */

  /* Enable MAC and DMA transmission and reception */
  ETH_Start();

}

顺便提一下,ethernetif.c这个源文件本身我们是不需要的,我们并不需要LWIP库中的任何代码,所有的一切都是借鉴其网络收发包模块是怎么编写的。

最后,我们再次小结一下,经过不断的对照和参考,我们现在已经有一份完整的DP83848Init()函数,这个函数将放在main()函数开头部分进行整个PHY、MAC、DMA的配置和初始化。

至此,我们这一章的任务就圆满完成了,我尽量做到每一行代码都有其出处,而不是只贴出代码,让读者只知其然不知其所以然,我想我们弄清楚这些出处后,就可以清晰地根据自己的不同设备和场景来移植。

STM32F4+DP83848以太网通信指南系列(四):PHY配置

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

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

本章为系列指南的第四章,这一章将正式进入以太网的配置和使用。首先我们关注一下PHY的配置,前面讲到,我们的工程使用了开发板上的一颗DP83848芯片,关于这颗芯片,可以下载它的datasheet:DP83848C.pdf

RMII和ADDR的确定

接下来我们来看开发板的原理图:

通过电路原理图可以看到接线方式是使用RMII接口模式接线的,因此接下来我们在配置PHY的时候注意要是用RMII。

我们知道DP83848,以及任何一个PHY芯片,都是有5个bit的地址信号的,RMII通信时会携带ADDR数据,只有ADDR吻合的PHY才会有响应。先来看看原理图上DP83848的管脚定义,原理图上DP83848的42号脚标注的PHYAD0,并且引脚悬空了,没有特别的处理。我们再来看看DP83848的数据手册,在P18页,有如下描述:

红框标记部分阐述了PHYAD0内部上拉了,其余4个AD位内部下拉了,因此悬空状态下这颗DP83848的默认ADDR就是0x01

PHY配置

PHY的配置主要解决GPIO的配置和RMII的接口,这部分比较简单,因为GPIO很多都是专用的,基本都是定义死的,查看原理图或者STM32F4xx的数据手册,都能很容易确定用到哪些GPIO,每个GPIO用来干什么的,代码如下:

#define     DP83848_PHY_ADDRESS     0x01    // DP83848 PHY芯片地址.

void ETH_GPIO_Config(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    /* Enable GPIOs clocks */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOG | RCC_AHB1Periph_GPIOC , ENABLE);

    /* Enable SYSCFG clock */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

    SYSCFG_ETH_MediaInterfaceConfig(SYSCFG_ETH_MediaInterface_RMII); //MAC和PHY之间使用RMII接口

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;

    /* Configure PA1, PA2 and PA7 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_7;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_ETH);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_ETH);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_ETH);

    /* Configure PC1, PC4 and PC5 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource1, GPIO_AF_ETH);
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource4, GPIO_AF_ETH);
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource5, GPIO_AF_ETH);

    /* Configure PG11, PG14 and PG13 */
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_11 | GPIO_Pin_13 | GPIO_Pin_14;
    GPIO_Init(GPIOG, &GPIO_InitStructure);
    GPIO_PinAFConfig(GPIOG, GPIO_PinSource11, GPIO_AF_ETH);
    GPIO_PinAFConfig(GPIOG, GPIO_PinSource13, GPIO_AF_ETH);
    GPIO_PinAFConfig(GPIOG, GPIO_PinSource14, GPIO_AF_ETH);
}

综上,就是PHY部分配置的全过程,还是比较简单的,第一行定义的DP83848_PHY_ADDRESS我们会在下一章配置MAC层的章节使用到。这个函数完成的任务也比较单一,就是初始化系统配置SYSCFG、确定RMII协议接口、初始化各个需要用到的GPIO。

我们将这个文件保存为stm32f4x7_eth_bsp.c,如果你在其他工程项目中能看到这么一个文件,十有八九都会有这么一个函数,我也是参考了一些其他项目这么写的,文件保存后,我们会在以后用到,这一章暂时结束。

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

丁丁生于 1987.07.01 ,30岁,英文ID:newflydd

  • 现居住地 江苏 ● 泰州 ● 姜堰
  • 创建了 Jblog 开源博客系统
  • 坚持十余年的 独立博客 作者
  • 大学本科毕业后就职于 中国电信江苏泰州分公司,前两年从事Oracle数据库DBA工作,两年后公司精简技术人员,被安排到农村担任支局长(其本质是搞销售),于2016年因志向不合从国企辞职,在小城镇找了一份程序员的工作。
  • Git OSChina 上积极参与开源社区