本文主要介绍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Ð_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();//以太网底层初始化
}
底层初始化文章来源:https://www.toymoban.com/news/detail-689976.html
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发送一个包。文章来源地址https://www.toymoban.com/news/detail-689976.html
到了这里,关于STM32F407以太网DMA描述符和数据链路层收发数据的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!