STM32F407以太网DMA描述符和数据链路层收发数据

2023-05-16

本文主要介绍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);//
    }
    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&ETH_DMARxDesc_OWN)!=(u32)RESET)//条件成立是DMA持有 错误状态
	{
		frame.length = 0;
		if ((ETH->DMASR&ETH_DMASR_RBUS)!=(u32)RESET) //没理解
		{ 
			ETH->DMASR = ETH_DMASR_RBUS;//清除ETH DMA的RBUS位 
			ETH->DMARPDR=0;//恢复DMA接收
		}		
		return frame;
	}
	
	if(((DMARxDescToGet->Status&ETH_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&ETH_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&ETH_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&ETH_DMATxDesc_OWN) != RESET)//1不等于0就是描述符被DMA持有
		return ETH_ERROR;//返回0
	DMATxDescToSet->ControlBufferSize = length&ETH_DMATxDesc_TBS1;//设置帧长度
	DMATxDescToSet->Status|=ETH_DMATxDesc_LS|ETH_DMATxDesc_FS;//DMA描述符中包含第一个帧的和最后一个帧
  	DMATxDescToSet->Status|=ETH_DMATxDesc_OWN;//发送描述符的WMN位置1 描述符归DMA持有
	
	if((ETH->DMASR&ETH_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(&ETH_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(&ETH_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抓包如下:
在这里插入图片描述中间浅黄色的ARP包一共11个,其中第一个ARP包是路由的广播包,剩下的10个包是我开发板收到数据包后,将其收到的内容原封不动的发了10次,细心的同学肯定看到了wireshark抓包从第二个ARP包开始后边都是间隔5ms。因为我们程序中每个5ms发送一个包。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32F407以太网DMA描述符和数据链路层收发数据 的相关文章

  • STM32F4 SPI RX/TX DMA 读写FLASH数据

    STM32 使用DMA读写FLASH数据需要注意以下几点 xff1a 1 SPI全双工模式下 xff0c 无论读写FLASH数据均需要同时使能RX TX DMA 2 写数据时回读数据应当丢弃 xff0c 读数据时应当发送0xff来启动SPI
  • Xilinx的Zynq系列,ARM和PL通过DMA通信时如何保证DDR数据的正确性。

    使用ZYNQ或者MPSoC的好处是可以通过PL逻辑设计硬件加速器 xff0c 对功能进行硬件加速 加速器和ARM之间的交互信息一般包含自定义加速指令传递 待计算数据以及计算结果 这三种交互信息为了实现高性能往往需要使用DMA进行通信 考虑两
  • stm32---DMA

    DMA 全称Direct Memory Access xff08 直接存储器访问 xff09 xff0c 把一个地址空间的值 复制 到另一个地址空间 xff0c 使用DMA传输方式无需CPU直接控制传输 xff0c 通过硬件为RAM和IO设
  • stm32串口DMA方式发送数据

    该文档介绍stm32 uart1通过DMA方式发送和接收数据 xff0c 代码示例基于ucos ii操作系统 该文档参考https wenku baidu com view d44ef1380975f46526d3e1b5 html 中内容
  • HAL_DMA_IRQHandler()代码详细分析

    这里使用ADC1联动DMA1 xff0c 开启半传输中断 传输中断 debug时 xff0c dma也一直在工作 xff0c 所以半传输中断 传输中断会同时生效 1 adc1使用了DMA1 Stream0 xff0c Instance表示如
  • STM32使用串口空闲中断接收不定长数据帧-USART_IT_IDLE使用(不使用DMA方式)

    STM32使用串口空闲中断接收不定长数据帧 USART IT IDLE使用 xff08 不使用DMA方式 xff09 前言串口空闲中断介绍清中断方法串口中断处理函数串口中断用到的全局变量定义串口初始化 xff08 使能接收中断 空闲中断 x
  • 2.14 STM32 串口传输最佳处理方式 FreeRTOS+队列+DMA+IDLE (二)

    紧接着上一篇文章 如何合理处理多个串口接收大量数据 此种方法 很厉害 很NB 首先 利用DMA 可节省大量CUP资源 其次 利用IDLE空闲中断来接收位置个数的数据 最后利用串口DMA环形数据的偏移量 长度 入队 出队处理数据 保证了任务的
  • 在EBAZ4205 zynq7010上运行AXI_DMA中断回环测试

    在EBAZ4205 zynq7010上运行AXI DMA loop interrupt 整体的布局图 这是上面的一张接口图 下面对每个模块附上截图 AXI DMA的输出mm2s introut s2mm introut接到PS系统的中断触发
  • 成功解决keil识别不到单片机芯片,下载不了程序

    成功解决keil识别不到单片机芯片 下载不了程序 我的芯片是STM32F429 正点原子的阿波罗 今天使用开发板做实验 突然找不到芯片了 以前下载的PWM波也运行不了 查找了好久 原来是芯片锁了 终于解决了 我是第二种办法实现给芯片解锁的
  • Micropython应用篇一--下载文件到F407VE Black开发板及开发板启动Flash选取

    Micropython应用篇一 下载文件到F407VE Black开发板及开发板启动选取 各位客官老爷们 咱又见面了 本来准备直接上例程来着 但由于怕各位老爷们对STM32的熟悉程度不一致 所以来一篇关于程序烧录及开发板启动 25Q128
  • stm32之ADC应用实例(单通道、多通道、基于DMA)

    硬件 STM32F103VCT6 开发工具 Keil uVision4 下载调试工具 ARM仿真器 网上资料很多 这里做一个详细的整合 也不是很详细 但很通俗 所用的芯片内嵌3个12位的模拟 数字转换器 ADC 每个ADC共用多达16个外部
  • Micropython应用篇四---F407VE Black开发板IIC OLED1306

    Micropython应用篇四 F407VE Black开发板IIC OLED1306显示 最近一段时间做Keil例程比Micropython多很多 无论如何 Micropython Arduino作为嵌入式入门篇也发过一些文章 包括公司的
  • STM32 之八 在线升级(IAP)超详细图解 及 需要注意的问题解决

    IAP 是啥 IAP In Application Programming 即在应用编程 也就是用户可以使用自己的程序对MCU的中的运行程序进行更新 而无需借助于外部烧写器 其实ST官网也给出了IAP的示例程序 感兴趣的可以直接去官网搜索
  • STM32F407移植FATFS文件系统(版本 R0.09b)支持长文件名和中文名称

    FatFs文件系统 默认是不支持长文件名和中文名称的 要想支持长文件名和中文名称 需要打开ffconf h文件进行配置 一 支持长文件名 FatFs文件系统 默认是不支持长文件名的 要想支持长文件名 需要打开ffconf h文件进行配置 找
  • STM32F407 USART3串口使用DMA接收不定长数据和DMA中断发送

    一 前言 使用DMA通信的好处是 不占用单片机资源 不像普通串口中断 发送一个字节触发一次中断 发送100个字节触发100次中断 接收一个字节触发一次中断 接收200个字节触发200次中断 数据接收完毕触发一次DMA中断 发送数据完毕触发一
  • 数据缓存如何路由本例中的对象?

    考虑图示的数据缓存架构 ASCII 艺术如下 CPU core A CPU core B Devices Cache A1 Cache B1 with DMA Cache 2 RAM
  • 多核架构中的 CPU 和内存访问

    我想知道如果 例如 CPU 的 2 个核心尝试同时访问内存 通过内存控制器 一般 如何处理内存访问 实际上 当内核和启用 DMA 的 IO 设备尝试以相同方式访问时 同样适用 I think 内存控制器足够智能 可以利用地址总线并同时处理这
  • 如何从cdev获取设备

    我正在编写一个内核模块 它将分配一些一致的内存并返回相应的虚拟和物理地址 我正在将模块注册为cdev 分配空间dma alloc coherent 我想使用 mmap 它dma common mmap dma common mmap 需要一
  • Linux内核设备驱动程序以DMA方式进入内核空间

    LDD3 p 453 演示dma map single使用作为参数传入的缓冲区 bus addr dma map single dev gt pci dev gt dev buffer count dev gt dma dir Q1 这个缓
  • 捕获数据包后会发生什么?

    我一直在阅读关于网卡捕获数据包后会发生什么的内容 我读得越多 我就越困惑 首先 我读过传统上 在网卡捕获数据包后 它会被复制到内核空间中的一个内存块 然后复制到用户空间 供随后处理数据包数据的任何应用程序使用 然后我读到了 DMA 其中 N

随机推荐

  • 无题

    今天在思考一个问题 xff0c 关于互联网行业的 xff0c 其他行业还不太清楚不敢妄加评论 互联网行业在当前社会中其实面临着很多社会问题需要解决和探索 xff0c 比如职业发展的不确定性 35岁的职业危机 职业社交关系的难处理 xff0c
  • Markdown学习笔记:如何画流程图

    如何使用markdown画流程图 话说网上关于使用markdown画流程图的相关的教程真是一堆堆的坑 xff0c 我丝毫不怀疑这些博文作者确实是知道如何使用markdown画流程图了 xff0c 但是写出来的文章却丝毫没有明白具体的使用方法
  • Gradle打jar包,包含所有依赖

    前言 最近被gradle折腾的欲仙欲死 gradle想把所有依赖打进jar包主要有两种方式 xff1a 一种是重写jar动作 xff0c 一种是用第三方插件 为了装x xff0c 我一直都是用的第一种方式 xff0c 结果出了问题解决不了
  • Spring Data Rest如何暴露ID字段

    前言 为了懒省事 xff0c 使用Spring Data Rest来直接提供rest接口 xff0c 重点遇到点小坑 xff0c 记录一下 记录 问题 entity xff1a span class hljs annotation 64 E
  • hadoop清空回收站

    直接删除目录 xff08 不放入回收站 xff09 hdfs dfs span class hljs attribute rm span span class hljs attribute skipTrash span path span
  • 漫谈数据仓库之拉链表(原理、设计以及在Hive中的实现)

    0x00 前言 本文将会谈一谈在数据仓库中拉链表相关的内容 xff0c 包括它的原理 设计 以及在我们大数据场景下的实现方式 最新文章已经迁入公众 xff1a 木东居士 全文由下面几个部分组成 xff1a 先分享一下拉链表的用途 什么是拉链
  • 《数据仓库实践》

    序言 2017 年初 xff0c 我开始在简书上写关于数据仓库的系列博客 xff0c 博客主题围绕大数据场景下数据仓库的理论和实践来展开 xff0c 截止现在已有十篇左右 最初写作的时候主要是抱着学习和总结的态度 xff0c 导致很多地方略
  • 一种计算用户留存的方法

    0x00 概述 用户留存分析是互联网时代常用的一种数据分析方法 而很多快速发展的公司并没有相应的方法论沉淀 xff0c 这就导致了在计算用户留存的时候会出现下面的一些问题 xff1a 1 xff09 用户留存的定义不明确 xff0c 不同的
  • MAVLink--结构

    MAVLink源文件结构 MAVLink是为微型飞行器MAV xff08 Micro Air Vehicle xff09 设计的 xff08 LGPL xff09 开源的通讯协议 是无人飞行器和地面站 xff08 Ground Contro
  • 关于函数strtok和strtok_r的使用要点和实现原理(二)

    xff08 一 xff09 中已经介绍了使用strtok函数的一些注意事项 xff0c 本篇将介绍strtok的一个应用并引出strtok r函数 1 一个应用实例 网络上一个比较经典的例子是将字符串切分 xff0c 存入结构体中 如 xf
  • TX2 ubuntu CPU占用率、占用物理内存、占用虚拟内存、进程ID、系统温度

    文件解释 在实际工作中有时需要程序打印出某个进程的内存占用情况以作参考 下面介绍一种通过Linux下的伪文件系统 proc计算某进程内存占用的程序实现方法 首先 为什么会有所谓的 伪文件 呢 Linux系统的文件类型大致可分为三类 普通文件
  • 【Fast RTPS】入门--------Ubuntu系统下

    RTPS协议的简单介绍 在RTPS的顶层 xff0c Domain域定义了不同的通信层 几个域可以同时独立地共存 域包含任意数量的参与者Participants xff0c 即发送和接收数据的元素 参与者使用端Endpoints xff1a
  • Linux 内核配置选项(转)

    Linux 内核配置选项 from http www mitbbs com mitbbs article t php board 61 Linux amp gid 61 10715608 amp ftype 61 0 第一部分 01 Cod
  • Cortex-M3双堆栈MSP和PSP

    什么是栈 xff1f 在谈M3堆栈之前我们先回忆一下数据结构中的栈 栈是一种先进后出的数据结构 类似于枪支的弹夹 xff0c 先放入的子弹最后打出 xff0c 后放入的子弹先打出 M3内核的堆栈也不例外 xff0c 也是先进后出的 栈的作用
  • 烧毁DC/DC电路问题

    使用芯龙半导体的XL7005A DC DC芯片 已经很多年了 xff0c 用的也很稳定 这次在做一个设备的时候 xff0c 系统上电就会烧DC DC芯片以及系统电路中的LDO和MCU等 试了很多次终于发现规律了 xff0c DC DC电路就
  • FreeRTOS内核全局变量

    想要分析FreeRTOS源码 xff0c 想要理解FreeRTOS源码的整个宏观架构 xff0c 有一个前提就是必须知道FreeRTOS内核中那些全局变量的意义 xff0c 每个全局变量都是用来干什么的 只有了解了这些全局变量我们才能从宏观
  • 基于LWIP协议栈RAW API的 UDP传输实验

    什么是UDP xff1f UDP是用户数据报协议 xff0c 是OSI参考模型中的传输层协议 UDP的特点 缺点 xff1a 无连接的 xff0c 不可靠的 xff0c 不能保证数据安全到达目的地 优点 xff1a 消耗资源小 xff0c
  • 初识CANOpen

    什么是CANOpen CANOpen是位于CAN总线之上的一个应用层协议 CAN总线只规定了物理层和数据链路层 xff0c 有了这两层 xff0c 数据就可以在CAN总线上传输了 我们和哪个设备通信就和哪个设备约定好 xff0c 哪个ID代
  • STM32单片机被锁无法烧写程序解决办法

    以前遇到无法烧写程序的问题 在开发中 xff0c 单片机突然无法烧写程序 xff0c 这种情况相信大家应该都遇到过 比如烧写程序引脚被设置为别的功能 这种情况也是最常见的 我们可以把复位电容短路 xff0c 让单片机复位 xff0c 然后点
  • STM32F407以太网DMA描述符和数据链路层收发数据

    本文主要介绍STM32F407单片机MAC内核的DMA描述符 xff0c 以及如何实现以太网二层的数据收发 这一篇先实现数据链路层的正常收发 xff0c 下一篇再去介绍如何把LWIP移植到单片机上 大部分资料都是把LWIP移植和以太网卡驱动