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

这篇具有很好参考价值的文章主要介绍了STM32F407以太网DMA描述符和数据链路层收发数据。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

STM32F407 以太网控制器框图

dma描述符,stm32,单片机,嵌入式硬件

以太网控制器的工作流程

发送数据流程:以太网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描述符,stm32,单片机,嵌入式硬件
在环形结构中,每个以太网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。
dma描述符,stm32,单片机,嵌入式硬件
RDES0定义如下:
dma描述符,stm32,单片机,嵌入式硬件
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定义如下:
dma描述符,stm32,单片机,嵌入式硬件
RBS2:接收缓存区2的大小。这些位以字节为单位指示第二个缓存区的大小,因为我们打算使用链式结构(RCH 位置1),所以此位没有意义。
RCH:表明链式的第二个地址到底指示的是什么地址,该为置1时,第二个地址是下一个描述符的地址,为0时表示的是第二个缓存区的地址。
RBS1:接收缓存区1的大小。
RDES2定义如下:
dma描述符,stm32,单片机,嵌入式硬件
表示此描述符第一个缓存区的地址。
RDES3定义如下:

dma描述符,stm32,单片机,嵌入式硬件
表示此描述符第二个缓存区的地址或下一个描述符的地址,到底表示什么由RDES1中的RCH位决定。

再看发送描述符
dma描述符,stm32,单片机,嵌入式硬件

如果理解了接收描述符,那么发送描述符就很好理解了。两者差距就是Status字段不一样,这要看具体手册了。
TDES0定义如下:
dma描述符,stm32,单片机,嵌入式硬件
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抓包如下:
dma描述符,stm32,单片机,嵌入式硬件中间浅黄色的ARP包一共11个,其中第一个ARP包是路由的广播包,剩下的10个包是我开发板收到数据包后,将其收到的内容原封不动的发了10次,细心的同学肯定看到了wireshark抓包从第二个ARP包开始后边都是间隔5ms。因为我们程序中每个5ms发送一个包。文章来源地址https://www.toymoban.com/news/detail-689976.html

到了这里,关于STM32F407以太网DMA描述符和数据链路层收发数据的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32F407 ADC+DMA+定时器 定时采样模拟量

    项目中需要对多个通道的电压进行一定频率的AD采样,由于采样过程贯穿整个任务,为了使采样过程尽可能不占用CPU资源,采用定时器触发的多通道ADC扫描采样,且采样数据由DMA传到RAM中的缓存。 这样做有以下几个好处:1、由定时器触发ADC采样,这样采样的频率可控,且定时

    2024年02月14日
    浏览(43)
  • 【STM32F407 ADC+DMA采集压力变送器数据(HAL库)】

    之前项目中需要对麦克传感器的mpm480隔爆压力变送器(4-20ma输出)的数据进行实时采集,使用STM32F407作为控制器,使用信号转换模块将压力变送器4-20ma的输出转换为0-3.3v的信号量,输入到STM32F407板子的ADC1的通道10,并使用DMA2通道0数据流0将采集的多个值从外设直接存入存储器

    2024年02月16日
    浏览(56)
  • 【STM32F1】以太网通信之UDP/TCP实验

    在本实验中,开发板主控芯片通过 SPI 接口与 CH395Q 以太网芯片进行通讯,从而完成对 CH395Q 以太网芯片的功能配置、数据接收等功能,同时将 CH395Q 以太网芯片的 Socket0 配 置为 UDP 模式,并可通过按键发送 UDP 广播数据至其他的 UDP 客户端,也能够接收其他 UDP 客户端广播的数

    2024年02月07日
    浏览(42)
  • STM32的以太网外设+PHY(LAN8720)使用详解(5):MAC及DMA配置

    stm32的ETH外设挂载在AHB1总线上,位于RCC_AHB1ENR的bit25-bit27: 相关语句如下: 直接调用ETH_DeInit函数来复位ETH外设 上述语句操作的寄存器如下: 首先设置位25为1复位以太网MAC(复位MAC寄存器到默认值),然后设置为0取消复位。 首先调用ETH_SoftwareReset函数复位MAC的DMA 上述语句操

    2024年02月03日
    浏览(38)
  • 关于STM32F4和GD32F4以太网,LAN8720+lwip+freemodbus,实现modbus tcp

    关于STM32F4和GD32F4以太网,LAN8720+lwip+freemodbus 这里使用了大佬 小灰灰搞电子 的代码,文章看 STM32F407+LAN8720移植Lwip和freeModbus实现MODBUS TCP 代码看 STM32F407+LAN8720+LWIP移植freemodbus TCP.zip 他的代码是基于正点原子F407的板子开发的,如果是别的板子,需要修改引脚 小灰灰的代码里,没

    2024年02月14日
    浏览(30)
  • stm32F407学习DAY.14 在DMA模式下进行USART串口数据收发(正点原子例程为例)

    目录 一、DMA配置 1、DMA1和DMA2的请求映射 2、DMA挂载总线 3、DMA相关库函数 ​4、DMA配置过程(以串口1为例) 1)进行时钟使能 2)等待DMA可配置 3)初始化DMA(串口1的TX为DMA2 数据流7 通道4,RX为DMA2 数据流5 通道4) a.DMA外设地址par: b.DMA存储器0地址mar: c.数据传输量ndtr: 4)

    2024年02月04日
    浏览(39)
  • 从STM32F407到AT32F407(一)

    雅特力公司的MCU有着性能超群,价格优越的巨大优势,缺点是相关资料少一些,我们可以充分利用ST的现有资源来开发它。 我用雅特力的STM32F437开发板,使用原子 stm32f407的开发板自带程序,测试串口程序,原设定串口波特率为115200,但是输出乱码,波特率改成230400,串口输

    2024年02月02日
    浏览(50)
  • STM32F407的介绍

    内核 32位 高性能ARM Cortex-M4处理器 时钟: 高达168MHz,实际还可以超频一点点 stm32f407的主频通过PLL倍频后能够达到168MHz,而且芯片内置一个16MHz的晶振和一个32KHz的晶振,可以满足不同功耗的需求。 支持FPU(浮点运算)和DSP指令 144引脚 114个IO口 存储器容量: 1024K FLASH, 192K

    2024年02月10日
    浏览(42)
  • STM32F407的时钟

    时钟源用来为环形脉冲发生器提供频率稳定且电平匹配的方波时钟脉冲信号。它通常由石英 晶体振荡器和与非门组成的正反馈振荡电路组成,其输出送至环形脉冲发生器。 F4开发指南P107 F4开发指南P108 HSI高速内部时钟源 High Speed Internal。RC 振荡器,频率为 16MHz。可以直接作为

    2024年02月10日
    浏览(43)
  • STM32F407——串口通信

    本文将对串口通信的分类和基于 stm32 的串口配置进行介绍,以及如何使用串口调试助手进行串口收发功能的调试,旨在帮助还不会使用 stm32 单片机串口资源进行通信的家人们快速学会如何使用串口来进行通信。 (纯干货、快速上手、零基础也能会!!!) (1)串口,即串

    2023年04月08日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包