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

STM32F407 以太网控制器框图

stm32cubemx 配置stm32f107网络时钟 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描述符有两种结构,一种是环形结构,一种是链式结构,如下图:

stm32cubemx 配置stm32f107网络时钟 stm32f407 网络_描述符_02


在环形结构中,每个以太网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。

stm32cubemx 配置stm32f107网络时钟 stm32f407 网络_stm32_03


RDES0定义如下:

stm32cubemx 配置stm32f107网络时钟 stm32f407 网络_缓存_04


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定义如下:

stm32cubemx 配置stm32f107网络时钟 stm32f407 网络_缓存_05


RBS2:接收缓存区2的大小。这些位以字节为单位指示第二个缓存区的大小,因为我们打算使用链式结构(RCH 位置1),所以此位没有意义。

RCH:表明链式的第二个地址到底指示的是什么地址,该为置1时,第二个地址是下一个描述符的地址,为0时表示的是第二个缓存区的地址。

RBS1:接收缓存区1的大小。

RDES2定义如下:

stm32cubemx 配置stm32f107网络时钟 stm32f407 网络_stm32_06


表示此描述符第一个缓存区的地址。

RDES3定义如下:

stm32cubemx 配置stm32f107网络时钟 stm32f407 网络_嵌入式硬件_07


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

stm32cubemx 配置stm32f107网络时钟 stm32f407 网络_缓存_08

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

TDES0定义如下:

stm32cubemx 配置stm32f107网络时钟 stm32f407 网络_缓存_09


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);//
    }
    else
    {
    	//进入else 表示当前描述符已经是最后一个描述符,将最后一个描述符的第二个地址赋值为第一个描述符的地址。构成链式结构(类似环形链表)
      /* For last descriptor, set next descriptor address register equal to the first descriptor base address */ 
      DMATxDesc->Buffer2NextDescAddr = (uint32_t) DMATxDescTab;  
    }
  }

  /* Set Transmit Desciptor List Address Register */
  ETH->DMATDLAR = (uint32_t) DMATxDescTab;//将描述符基地址赋值给以太网控制器的寄存器,就是这一步让描述符和硬件关联起来。
}

数据链路层接收数据流程

在中断服务函数中,判断接收到的数据长度如果不为0,则调用接收函数,接收函数如下:

typedef struct{
  u32 length;	//接收数据包的长度
  u32 buffer;	//接收数据包的缓存区
  __IO ETH_DMADESCTypeDef *descriptor;//指向接收描述符
}FrameTypeDef;

FrameTypeDef eth_rx_pack(void)
{
	uint32_t framelength = 0;
	FrameTypeDef frame={0,0,NULL};
	if((DMARxDescToGet->StatusÐ_DMARxDesc_OWN)!=(u32)RESET)//条件成立是DMA持有 错误状态
	{
		frame.length = 0;
		if ((ETH->DMASRÐ_DMASR_RBUS)!=(u32)RESET) //没理解
		{ 
			ETH->DMASR = ETH_DMASR_RBUS;//清除ETH DMA的RBUS位 
			ETH->DMARPDR=0;//恢复DMA接收
		}		
		return frame;
	}
	
	if(((DMARxDescToGet->StatusÐ_DMARxDesc_ES)==(u32)RESET)&& //没有错误
	((DMARxDescToGet->Status & ETH_DMARxDesc_LS)!=(u32)RESET)&&  //LS为1 表示此描述符指向的缓存区为帧的最后一个缓存区
	((DMARxDescToGet->Status & ETH_DMARxDesc_FS)!=(u32)RESET))   //FS为1 表示此描述符包含帧的第一个缓存区
	{       
		framelength=((DMARxDescToGet->StatusÐ_DMARxDesc_FL)>>ETH_DMARxDesc_FrameLengthShift)-4;//得到接收包帧长度(不包含4字节CRC)
 		frame.buffer = DMARxDescToGet->Buffer1Addr;//得到包数据所在的位置
	}
	else 
	{
		framelength=ETH_ERROR;//错误 
	}
	frame.length = framelength;//得到数据包的长度
	frame.descriptor = DMARxDescToGet;//得到接收描述符
	
	DMARxDescToGet=(ETH_DMADESCTypeDef*)(DMARxDescToGet->Buffer2NextDescAddr);//追踪描述符指向下一个缓存区
	return frame;
}

上边这个函数貌似是有问题的,因为一帧以太网数据可能会占用多个描述符,这种写法只处理了一帧数据只占用一个描述符的情况,HAL库并不是这种写法,HAL库的写法才是正确的,后续对此函数进行重新。

下面是HAL库的写法:

以太网句柄结构体如下:
下面这个函数就是处理每一个描述符,当接收完最后一个描述符就返回HAL_OK。调用函数区拷贝。

typedef struct
{
  ETH_TypeDef                *Instance;    //指向以太网控制器的基地址
  ETH_InitTypeDef            Init;          //保存一些初始化的配置
  uint32_t                   LinkStatus;    //以太网的链接状态
  ETH_DMADescTypeDef         *RxDesc;       //当前接收描述符的指针
  ETH_DMADescTypeDef         *TxDesc;       //当前发送描述符的指针
  ETH_DMARxFrameInfos        RxFrameInfos;  //保存接收帧信息的变量
  __IO HAL_ETH_StateTypeDef  State;         //以太网控制器的状态
  HAL_LockTypeDef            Lock;          //以太网初始化的时候上锁
} ETH_HandleTypeDef;

HAL_StatusTypeDef HAL_ETH_GetReceivedFrame(ETH_HandleTypeDef *heth)
{
  uint32_t framelength = 0;
  
  /* Process Locked */
  __HAL_LOCK(heth);
  
  /* Check the ETH state to BUSY */
  heth->State = HAL_ETH_STATE_BUSY;
  
  /* Check if segment is not owned by DMA */
  /* (((heth->RxDesc->Status & ETH_DMARXDESC_OWN) == (uint32_t)RESET) && ((heth->RxDesc->Status & ETH_DMARXDESC_LS) != (uint32_t)RESET)) */
  if(((heth->RxDesc->Status & ETH_DMARXDESC_OWN) == (uint32_t)RESET))//一个描述符接收完成
  {	  
    /* Check if last segment */
    if(((heth->RxDesc->Status & ETH_DMARXDESC_LS) != (uint32_t)RESET)) //此描述符指向的缓存区为此数据包的最后一个缓存区
    {
      /* increment segment count */
      (heth->RxFrameInfos).SegCount++;
      
      /* Check if last segment is first segment: one segment contains the frame */
      if ((heth->RxFrameInfos).SegCount == 1)//如果最后一个描述符也是第一个描述符 那么这帧数据只存放在一个DMA描述符的缓存区中
      {
        (heth->RxFrameInfos).FSRxDesc =heth->RxDesc;//当前描述符同时也是第一个描述符 获取第一个描述符的地址
      }
      
      heth->RxFrameInfos.LSRxDesc = heth->RxDesc;//获取最后一个描述符的地址
      
      /* Get the Frame Length of the received packet: substruct 4 bytes of the CRC */
      framelength = (((heth->RxDesc)->Status & ETH_DMARXDESC_FL) >> ETH_DMARXDESC_FRAMELENGTHSHIFT) - 4;//获取帧长度
      heth->RxFrameInfos.length = framelength;//一个以太网包可以跨越多个描述符 这里获取的是这个数据包的长度 而不是描述符的内的有效长度
      
      /* Get the address of the buffer start address */
      heth->RxFrameInfos.buffer = ((heth->RxFrameInfos).FSRxDesc)->Buffer1Addr;//获取第一个描述符缓存区的地址
      /* point to next descriptor */
      heth->RxDesc = (ETH_DMADescTypeDef*) ((heth->RxDesc)->Buffer2NextDescAddr);//当前描述符指向下一个描述符
      
      /* Set HAL State to Ready */
      heth->State = HAL_ETH_STATE_READY;
      
      /* Process Unlocked */
      __HAL_UNLOCK(heth);
      
      /* Return function status */
      return HAL_OK;
    }
    /* Check if first segment */
    else if((heth->RxDesc->Status & ETH_DMARXDESC_FS) != (uint32_t)RESET)//此描述符指向的缓存区为此帧数据的第一个缓存区
    {
      (heth->RxFrameInfos).FSRxDesc = heth->RxDesc;//一帧数据可以能存在于多个DMA描述符,保存第一个DMA描述符
      (heth->RxFrameInfos).LSRxDesc = NULL;//最后一个DMA描述符指针清空
      (heth->RxFrameInfos).SegCount = 1;//缓存区计数
      /* Point to next descriptor */
      heth->RxDesc = (ETH_DMADescTypeDef*) (heth->RxDesc->Buffer2NextDescAddr);//指向下一个描述符
    }
    /* Check if intermediate segment */ 
    else	//此描述符指向缓存区为此帧数据的中间段
    {
      (heth->RxFrameInfos).SegCount++;//缓存区计数
      /* Point to next descriptor */
      heth->RxDesc = (ETH_DMADescTypeDef*) (heth->RxDesc->Buffer2NextDescAddr);//指向下一个描述符 
    } 
  }
  
  /* Set ETH HAL State to Ready */
  heth->State = HAL_ETH_STATE_READY;
  
  /* Process Unlocked */
  __HAL_UNLOCK(heth);
  
  /* Return function status */
  return HAL_ERROR;
}

再看接收中断函数:

//以太网中断服务函数
void ETH_IRQHandler(void)
{
	FrameTypeDef frame={0,0,NULL};
	uint32_t length = 0;
	length = ETH_GetRxPktSize(DMARxDescToGet);//获取接收描述符接收到数据的长度
	if(length != 0)//不等于0则表示接收到了数据
	{
		printf("recv_length = %d\r\n",length);//打印接收数据的长度这个长度包含CRC
		frame = eth_rx_pack();
		printf("frame.length = %d\r\n",frame.length);//这个长度不包含CRC
		mymemcpy(test_buffer,(uint8_t *)(frame.buffer),frame.length);//拷贝接收到的数据
		for(int i =0;i<frame.length;i++)//打印接收到的数据
		{
			if(i%16 == 0)
				printf("\r\n");
			
			printf("%02x ",test_buffer[i]);
		}
		
		tx_flag = 10;//发送次数	这里就是自己接收到什么数据包再原样发送出去
		tx_length = frame.length;//发送的长度
	}
	
	frame.descriptor->Status=ETH_DMARxDesc_OWN;//设置Rx描述符OWN位,buffer重归ETH DMA 
	if((ETH->DMASRÐ_DMASR_RBUS)!=(u32)RESET)//当Rx Buffer不可用位(RBUS)被设置的时候,重置它.恢复传输
	{ 
		ETH->DMASR=ETH_DMASR_RBUS;//重置ETH DMA RBUS位 
		ETH->DMARPDR=0;//恢复DMA接收
	}
		

	printf("eth_interrupt\r\n\r\n");
	ETH_DMAClearITPendingBit(ETH_DMA_IT_R); 	//清除DMA中断标志位
	ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);	//清除DMA接收中断标志位
}

发送函数如下:

uint8_t test_buffer[2048]={0};

uint8_t eth_tx_pack(uint8_t length)
{
	if((DMATxDescToSet->StatusÐ_DMATxDesc_OWN) != RESET)//1不等于0就是描述符被DMA持有
		return ETH_ERROR;//返回0
	DMATxDescToSet->ControlBufferSize = lengthÐ_DMATxDesc_TBS1;//设置帧长度
	DMATxDescToSet->Status|=ETH_DMATxDesc_LS|ETH_DMATxDesc_FS;//DMA描述符中包含第一个帧的和最后一个帧
  	DMATxDescToSet->Status|=ETH_DMATxDesc_OWN;//发送描述符的WMN位置1 描述符归DMA持有
	
	if((ETH->DMASRÐ_DMASR_TBUS)!=(u32)RESET)//当Tx Buffer不可用位(TBUS)被设置的时候,重置它.恢复传输
	{ 
		ETH->DMASR=ETH_DMASR_TBUS;//重置ETH DMA TBUS位 
		ETH->DMATPDR=0;//恢复DMA发送
	} 
	DMATxDescToSet=(ETH_DMADESCTypeDef*)(DMATxDescToSet->Buffer2NextDescAddr);//更新发送追踪描述符    
	return ETH_SUCCESS;  //返回1发送成功
}


//获取当前发送DMA描述符指向的缓存
uint32_t get_currentTXbuffer()
{
	return DMATxDescToSet->Buffer1Addr;
}

void Low_Level_Output(uint32_t length)
{
	uint8_t *add = 0;
	add = (uint8_t *)get_currentTXbuffer();
	mymemcpy(add,test_buffer,length);
	eth_tx_pack(length);
}

主循环:

while(1)
	{
		if(tx_flag)
		{
			tx_flag--;
			printf("tx...\r\n");
			Low_Level_Output(tx_length);
			delay_ms(5);
		}
	}

下面介绍以太网控制器的初始化,因为对以太网控制器的寄存器还不算了解,这里只能大概介绍。
MAC管脚初始化

void mac_pin_init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG|RCC_AHB1Periph_GPIOC,ENABLE);
	//TX0~1 	TXEN
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_11;//分别是TXD0 TXD1 TXEN
	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 ;  
	GPIO_Init(GPIOG, &GPIO_InitStructure);
	GPIO_PinAFConfig(GPIOG, GPIO_PinSource13, GPIO_AF_ETH);
	GPIO_PinAFConfig(GPIOG, GPIO_PinSource14, GPIO_AF_ETH);
	GPIO_PinAFConfig(GPIOG, GPIO_PinSource11, GPIO_AF_ETH);
	//RX0~1
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	GPIO_PinAFConfig(GPIOC, GPIO_PinSource4, GPIO_AF_ETH);
	GPIO_PinAFConfig(GPIOC, GPIO_PinSource5, GPIO_AF_ETH);
	//CSR_DV	载波侦听信号
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_ETH);
	//REF_CLK	给MCU的50MHZ信号
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_ETH);
}

MAC的NVIC初始化

void eth_nvic_init(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	
	NVIC_InitStructure.NVIC_IRQChannel = ETH_IRQn;  //以太网中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0X00;  //中断寄存器组2最高优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0X00;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}

MAC配置:

void eth_init(void)
{
	uint32_t rval = 0;
	
	ETH_InitTypeDef ETH_InitStructure; 
	//以太网还有一个PTP时钟 是控制什么的?
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_ETH_MAC_Tx |RCC_AHB1Periph_ETH_MAC_Rx, ENABLE);
	

	
	ETH_DeInit();//复位以太网MAC	控制的是AHB1的REST寄存器
	ETH_SoftwareReset();//MAC的DMA控制器会复位所有MAC子系统的内部寄存器和逻辑
	while(ETH_GetSoftwareResetStatus() == SET);
	ETH_StructInit(Ð_InitStructure); //将结构体设置为缺省值
	
	ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Enable;//开启自协商 PHY的10/100M 全/半双工	写到PHY寄存器
	ETH_InitStructure.ETH_LoopbackMode = ETH_LoopbackMode_Disable;//回送模式关闭	写到 MACCR寄存器
	ETH_InitStructure.ETH_RetryTransmission = ETH_RetryTransmission_Disable;//禁止重试 仅仅在半双工模式下有效 写到 MACCR寄存器
	ETH_InitStructure.ETH_AutomaticPadCRCStrip = ETH_AutomaticPadCRCStrip_Disable;//关闭自动去除padCRC	写到MACCR寄存器
	ETH_InitStructure.ETH_ReceiveAll = ETH_ReceiveAll_Disable;//关闭接收所有的帧 传送给应用程序的只是通过了地址过滤的帧 写到MACFFR寄存器
	ETH_InitStructure.ETH_BroadcastFramesReception = ETH_BroadcastFramesReception_Enable;//允许接收广播帧 写到MACFFR寄存器
	ETH_InitStructure.ETH_PromiscuousMode = ETH_PromiscuousMode_Disable;//关闭混合模式 类似wireshark的混杂模式	写到MACFFR寄存器
	ETH_InitStructure.ETH_MulticastFramesFilter = ETH_MulticastFramesFilter_Perfect;//对于组播地址使用完美地址过滤   
	ETH_InitStructure.ETH_UnicastFramesFilter = ETH_UnicastFramesFilter_Perfect;	//对单播地址使用完美地址过滤
	ETH_InitStructure.ETH_DropTCPIPChecksumErrorFrame = ETH_DropTCPIPChecksumErrorFrame_Enable;//TCP/IP错误时丢弃 写到DMAOMR寄存器
	ETH_InitStructure.ETH_ReceiveStoreForward = ETH_ReceiveStoreForward_Enable;//开启接收存储并转发 写到DMAOMR寄存器
	ETH_InitStructure.ETH_TransmitStoreForward = ETH_TransmitStoreForward_Enable;//打开发送存储并转发 只要TXFIFO中有一个帧 则发送会启动	写到DMAOMR寄存器
	ETH_InitStructure.ETH_ForwardErrorFrames = ETH_ForwardErrorFrames_Disable;//禁止转发错误帧 RXFIFO会丢弃带有错误状态的帧	写到DMAOMR寄存器
	ETH_InitStructure.ETH_ForwardUndersizedGoodFrames = ETH_ForwardUndersizedGoodFrames_Disable;//不转发过小(长度小于64字节)的好帧	写到DMAOMR寄存器
	ETH_InitStructure.ETH_SecondFrameOperate = ETH_SecondFrameOperate_Enable;//打开处理第二帧的功能	写到DMAOMR寄存器
	ETH_InitStructure.ETH_AddressAlignedBeats = ETH_AddressAlignedBeats_Enable;//开启DMS地址对齐功能	写到DMABMR寄存器
	ETH_InitStructure.ETH_FixedBurst = ETH_FixedBurst_Enable;//开启固定突发传输功能	写到DMABMR寄存器
	ETH_InitStructure.ETH_RxDMABurstLength = ETH_RxDMABurstLength_32Beat;     		//DMA发送的最大突发长度为32个节拍   
	ETH_InitStructure.ETH_TxDMABurstLength = ETH_TxDMABurstLength_32Beat;			//DMA接收的最大突发长度为32个节拍
	ETH_InitStructure.ETH_DMAArbitration = ETH_DMAArbitration_RoundRobin_RxTx_2_1;	//RXDMA请求和TXDMA请求优先级比为2:1
	rval=ETH_Init(Ð_InitStructure,0);		//配置ETH
	if(rval == SUCCESS)
	{
		//打开DAM正常中断 和 接收中断使能
		//正常中断包括发送中断   
		//发送缓存区不可用
		//接收中断
		//提前接收中断
		ETH_DMAITConfig(ETH_DMA_IT_NIS|ETH_DMA_IT_R,ENABLE);//DMAIER寄存器 
	}

	eth_memory_malloc();//以太网内存申请
	Low_Level_init();//以太网底层初始化

}

底层初始化

void Low_Level_init(void)
{
	uint8_t mac[6] = {0x00,0x01,0x02,0x03,0x04}; 
	ETH_MACAddressConfig(ETH_MAC_Address0, mac); //向STM32F4的MAC地址寄存器中写入MAC地址
	ETH_DMATxDescChainInit(dma_rx_desc_tab, rx_buff, ETH_TXBUFNB);//将接收描述符和接收缓存区关联起来 串成链式结构 初始化了发送追踪描述符
	ETH_DMARxDescChainInit(dma_tx_desc_tab, tx_buff, ETH_RXBUFNB);//将发送描述符和发送缓存区关联起来 串成链表	  初始化了接收追踪描述符
	ETH_Start(); //开启MAC和DMA			
}

wireshark抓包如下:

stm32cubemx 配置stm32f107网络时钟 stm32f407 网络_描述符_10

中间浅黄色的ARP包一共11个,其中第一个ARP包是路由的广播包,剩下的10个包是我开发板收到数据包后,将其收到的内容原封不动的发了10次,细心的同学肯定看到了wireshark抓包从第二个ARP包开始后边都是间隔5ms。因为我们程序中每个5ms发送一个包。