本章为系列指南的第五章,讲述STM32F407上MAC层以及其DMA的配置。我们在第一章知识储备章节说到,STM32F407会在168MHz主频之外分配一定的时间释放总线数据用来处理DMA,这其中就包含MAC层的DMA,复习一下STM32F4的总线架构图,(图片来自RM0090ST中文STM32F4手册P50):
我们看到,在上图红框标注的的S6阶段,就是MAC层的DMA总线,CPU会在核心逻辑之外,有专门的时间片轮转周期处理这一阶段的DMA,所有的数据读写都是DMA来控制,不需要我们在核心逻辑中编写。
本章的要解决的任务只有一个:能编写一个自己构建的DP83848Init()函数,就像任何类似的UARTInit(),DelayInit()等函数一样,在main()函数初始化阶段调用,完成一系列启动网卡的操作。这个任务看似简单,其实比较复杂,因此本章篇幅也会比较多。这个函数包含多个子任务:
GPIO的初始化
MAC层及DMA配置
中断配置
网络服务启动
一、GPIO初始化
这个我们在上一章已经完成了。
二、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部分的库函数。这份文档解压后,在/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文件后,你会发现有相似的代码描述,我们看懂注释后,可以做适当的配置调整。
三、中断配置
这个环节相对比较简单,直接编码:
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的配置和初始化。
至此,我们这一章的任务就圆满完成了,我尽量做到每一行代码都有其出处,而不是只贴出代码,让读者只知其然不知其所以然,我想我们弄清楚这些出处后,就可以清晰地根据自己的不同设备和场景来移植。