打印
[应用相关]

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

[复制链接]
1517|9
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

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

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


使用特权

评论回复
沙发
keaibukelian|  楼主 | 2019-7-4 09:27 | 只看该作者

本章的要解决的任务只有一个:能编写一个自己构建的DP83848Init()函数,就像任何类似的UARTInit(),DelayInit()等函数一样,在main()函数初始化阶段调用,完成一系列启动网卡的操作。这个任务看似简单,其实比较复杂,因此本章篇幅也会比较多。这个函数包含多个子任务:

GPIO的初始化

MAC层及DMA配置

中断配置

网络服务启动


使用特权

评论回复
板凳
keaibukelian|  楼主 | 2019-7-4 09:27 | 只看该作者
一、GPIO初始化

这个我们在上一章已经完成了。


使用特权

评论回复
地板
keaibukelian|  楼主 | 2019-7-4 09:28 | 只看该作者
二、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()等函数在哪里呢?


使用特权

评论回复
5
keaibukelian|  楼主 | 2019-7-4 09:28 | 只看该作者

2.2 stm32f4x7_eth.c文件和Ethernet库函数

在第一章知识储备中,已经说过了,STM32F4标准库中并未带有ETH方面的库函数,在STM32官网搜索LWIP能够搜索到官方使用LWIP的DEMO,官方文档编号是STSW-STM32070,在这份文档中有LWIP协议栈,并且有官方的调用样例,我们可以从中挖掘到ETH部分的库函数。这份文档解压后,在/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.h为stm32f4x7_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文件后,你会发现有相似的代码描述,我们看懂注释后,可以做适当的配置调整。


使用特权

评论回复
6
keaibukelian|  楼主 | 2019-7-4 09:29 | 只看该作者
三、中断配置

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



  • 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);



  • }




使用特权

评论回复
7
keaibukelian|  楼主 | 2019-7-4 09:29 | 只看该作者

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文件中。


使用特权

评论回复
8
keaibukelian|  楼主 | 2019-7-4 09:30 | 只看该作者
四、网络服务启动

我们现在已经完成了大部分的初始化和配置任务,下面,我们需要着手编写以太网服务的启动工作代码,也就是我们这一章节的核心任务,编写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, ETH_DMATxDesc_ChecksumTCPUDPICMPFull);



  •     }



  •     ETH_Start();



  • }


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


使用特权

评论回复
9
keaibukelian|  楼主 | 2019-7-4 09:30 | 只看该作者

按照惯例,上面那一段函数也不是我突发奇想,心血来潮,闭着眼睛毫无根据写下的,我们来看看这段函数的出处,依然在之前那份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, 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库中的任何代码,所有的一切都是借鉴其网络收发包模块是怎么编写的。


使用特权

评论回复
10
keaibukelian|  楼主 | 2019-7-4 09:31 | 只看该作者

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

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


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

63

主题

3875

帖子

5

粉丝