本文主要介绍STM32F407单片机MAC内核的DMA描述符,以及如何实现以太网二层的数据收发。这一篇先实现数据链路层的正常收发,下一篇再去介绍如何把LWIP移植到单片机上。大部分资料都是把LWIP移植和以太网卡驱动放在一起介绍,对新手不友好。所以我在这篇文章先把网卡驱动梳理清楚。本文使用STM32F407的标准库介绍。
STM32F407 以太网控制器框图

以太网控制器的工作流程
发送数据流程:以太网DMA描述符从发送缓存区把数据搬运到TX FIFO中,然后由MAC控制器把TX FIFO中的数据通过MII或RMII接口发送到PHY芯片,PHY芯片把数据转换成光信号或电信号发送到网络中。我们只要把待发送的数据存储到DMA描述符指向的缓存区中即可,剩下的事交给以太网控制器。
接收流程:PHY把光信号或电信号转换成数字信号发送到MII或RMII接口,以太网控制器把MII、RMII接口的数据存储到RX FIFO中,以太网DMA会把RX FIFO中的数据搬运到接收DMA描述符指向的缓存区,然后由CPU处理。
注意:RX FIFO 和 TX FIFO是不能通过CPU直接访问的,必须借助以太网DMA传输。
STM32F407以太网DMA描述符
以太网DMA描述符分为接收描述符和发送描述符。
因为是DMA传输,所以提出了一个DMA描述符的概念,DMA描述符是软件上的一个概念,在代码中体现出来就是一个结构体。DMA描述符虽然是体现在软件上的,但是必须根据硬件以太网DMA的设计去编写DMA描述符。这样以太网DMA描述符才能正常工作。以太网DMA描述符有两种结构,一种是环形结构,一种是链式结构,如下图:

在环形结构中,每个以太网DMA描述符有两个缓存区;在链接结构中,每个以太网DMA有一个缓存区。DMA描述符在程序里就是用结构体表示的。如下代码所示:
typedef struct {
__IO uint32_t Status; /*!< Status */
uint32_t ControlBufferSize; /*!< Control and Buffer1, Buffer2 lengths */
uint32_t Buffer1Addr; /*!< Buffer1 address pointer */
uint32_t Buffer2NextDescAddr; /*!< Buffer2 or next descriptor address pointer */
/* Enhanced ETHERNET DMA PTP Descriptors */
#ifdef USE_ENHANCED_DMA_DESCRIPTORS
uint32_t ExtendedStatus; /* Extended status for PTP receive descriptor */
uint32_t Reserved1; /* Reserved */
uint32_t TimeStampLow; /* Time Stamp Low value for transmit and receive */
uint32_t TimeStampHigh; /* Time Stamp High value for transmit and receive */
#endif /* USE_ENHANCED_DMA_DESCRIPTORS */
} ETH_DMADESCTypeDef;
常规描述符只使用前4个成员,增强描述符会用到后4个成员。这里只讨论常规DMA描述符,前4个成员变量含义如下所示:
DMA描述符是保存在RAM中的结构体变量。RAM中结构体成员的每一位的含义要和手册中以太网控制器DMA描述符结构一一对应。DMA描述符虽然在RAM中,但是每一位的含义由硬件DMA描述符定义,这样硬件DMA才能和软件DMA描述符协同工作。
先看接收描述符:
结构体成员status对应手册中的RDES0,ControlBufferSize对应RDES1,Buffer1Addr对应RDES2,Buffer2NextDescAddr对应RDES3。

RDES0定义如下:

OWN:表示此描述符归谁持有,0表示归CPU持有,1表示归DMA持有。当此位为1时,CPU不能操作该描述符。DMA在帧接收完成或此描述符的关联缓存区已满时将该位清0,此时也就是把描述符归还给CPU了。
FL:帧长度,注意是帧长度。因为一个帧可能用若干个DMA描述符承载。所以不是此描述符指向缓存区内的有效长度。只有在LS位置1而且DE清0时FL的长度才有效。 如果FL未置1,ES未也未置1,FL则表示的是此帧已经传输的字节。
FS:表示此描述符包含帧的第一个缓存区。
LS:表示此描述符包含帧的最后一个缓存区。
RDES1定义如下:

RBS2:接收缓存区2的大小。这些位以字节为单位指示第二个缓存区的大小,因为我们打算使用链式结构(RCH 位置1),所以此位没有意义。
RCH:表明链式的第二个地址到底指示的是什么地址,该为置1时,第二个地址是下一个描述符的地址,为0时表示的是第二个缓存区的地址。
RBS1:接收缓存区1的大小。
RDES2定义如下:

表示此描述符第一个缓存区的地址。
RDES3定义如下:

表示此描述符第二个缓存区的地址或下一个描述符的地址,到底表示什么由RDES1中的RCH位决定。
再看发送描述符:

如果理解了接收描述符,那么发送描述符就很好理解了。两者差距就是Status字段不一样,这要看具体手册了。
TDES0定义如下:

OWN位:当前发送描述符归谁持有,0表示归CPU持有,1表示归DMA持有。
TCH位:该位决定了TDES3的意义,当TCH为0时,TDES3表示第二个缓存区的地址;TCH为1时,TDES3表示下一个描述符的地址。
上边这两位非常重要。
TDES1~TDES3和接收描述符类似。
到此,发送描述符和接收描述符介绍完了,但是有一个问题,DMA描述符时定义在RAM中的结构体变量,缓存区也是RAM中的一块区域,这两者还没有关联起来,而且DMA描述符、缓存区也没有和硬件DMA关联起来。下面的代码就是介绍这一系列是如何关联起来的。
ETH_DMADESCTypeDef *dma_tx_desc_tab = NULL;//定义DMA发送描述符指针
ETH_DMADESCTypeDef *dma_rx_desc_tab = NULL;//定义DMA接收描述符指针
uint8_t *tx_buff = NULL;//DMA描述符发送缓存区的指针
uint8_t *rx_buff = NULL;//DMA描述符接收缓存区的指针
extern ETH_DMADESCTypeDef *DMATxDescToSet;//引用追踪发送描述符的指针
extern ETH_DMADESCTypeDef *DMARxDescToGet;//引用追踪接收描述符的指针
//以太网内存申请
void eth_memory_malloc(void)
{
dma_rx_desc_tab=mymalloc(SRAMIN,ETH_RXBUFNB*sizeof(ETH_DMADESCTypeDef));//申请接收描述符的内存
dma_tx_desc_tab=mymalloc(SRAMIN,ETH_TXBUFNB*sizeof(ETH_DMADESCTypeDef));//申请发送描述符的内存
rx_buff=mymalloc(SRAMIN,ETH_RX_BUF_SIZE*ETH_RXBUFNB); //申请接收缓存区
tx_buff=mymalloc(SRAMIN,ETH_TX_BUF_SIZE*ETH_TXBUFNB); //申请发送缓存区
}
发送描述符关联和接收描述符关联类似,这里就以发送描述符为例介绍。
//DMATxDescTab DMA发送描述符的指针
//TxBuff 发送缓存区的指针
//TxBuffCount 发送描述符的个数
void ETH_DMATxDescChainInit(ETH_DMADESCTypeDef *DMATxDescTab, uint8_t* TxBuff, uint32_t TxBuffCount)
{
uint32_t i = 0;
ETH_DMADESCTypeDef *DMATxDesc;
/* Set the DMATxDescToSet pointer with the first one of the DMATxDescTab list */
DMATxDescToSet = DMATxDescTab;//把发送描述符的基地址赋值给追踪描述符
/* Fill each DMATxDesc descriptor with the right values */
for(i=0; i < TxBuffCount; i++)//循环处理TxBuffCount 个描述符。
{
/* Get the pointer on the ith member of the Tx Desc list */
DMATxDesc = DMATxDescTab + i;//获取当前DMA描述符的地址。
/* Set Second Address Chained bit */
DMATxDesc->Status = ETH_DMATxDesc_TCH; //当前描述符的TCH置位,也就是Buffer2NextDescAddr 表示的是下一个描述符的地址,而非第二个缓存区的地址
/* Set Buffer1 address pointer */
DMATxDesc->Buffer1Addr = (uint32_t)(&TxBuff[i*ETH_TX_BUF_SIZE]);//将缓存区的地址赋值给Buffer1Addr
/* Initialize the next descriptor with the Next Descriptor Polling Enable */
if(i < (TxBuffCount-1))
{
//进入if表示当前描述符还不是最后一个描述符,将第二个描述符的地址赋值为下一个描述符的地址
/* Set next descriptor address register with next descriptor base address */
DMATxDesc->Buffer2NextDescAddr = (uint32_t)(DMATxDescTab+i+1);</

本文详细介绍了STM32F407单片机中以太网控制器的工作流程,特别是DMA描述符在数据发送和接收过程中的作用。通过分析发送和接收描述符的结构及状态位,阐述了如何实现以太网二层数据的正常收发。此外,文章还提到了如何将LWIP移植到单片机上的后续步骤,以及以太网控制器的初始化配置和中断处理方法。
最低0.47元/天 解锁文章
8305





