本文主要介绍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;
uint32_t ControlBufferSize;
uint32_t Buffer1Addr;
uint32_t Buffer2NextDescAddr;
#ifdef USE_ENHANCED_DMA_DESCRIPTORS
uint32_t ExtendedStatus;
uint32_t Reserved1;
uint32_t TimeStampLow;
uint32_t TimeStampHigh;
#endif
} 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;
ETH_DMADESCTypeDef *dma_rx_desc_tab = NULL;
uint8_t *tx_buff = NULL;
uint8_t *rx_buff = NULL;
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);
}
发送描述符关联和接收描述符关联类似,这里就以发送描述符为例介绍。
void ETH_DMATxDescChainInit(ETH_DMADESCTypeDef *DMATxDescTab, uint8_t* TxBuff, uint32_t TxBuffCount)
{
uint32_t i = 0;
ETH_DMADESCTypeDef *DMATxDesc;
DMATxDescToSet = DMATxDescTab;
for(i=0; i < TxBuffCount; i++)
{
DMATxDesc = DMATxDescTab + i;
DMATxDesc->Status = ETH_DMATxDesc_TCH;
DMATxDesc->Buffer1Addr = (uint32_t)(&TxBuff[i*ETH_TX_BUF_SIZE]);
if(i < (TxBuffCount-1))
{
DMATxDesc->Buffer2NextDescAddr = (uint32_t)(DMATxDescTab+i+1);
}
else
{
DMATxDesc->Buffer2NextDescAddr = (uint32_t) DMATxDescTab;
}
}
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)
{
frame.length = 0;
if ((ETH->DMASRÐ_DMASR_RBUS)!=(u32)RESET)
{
ETH->DMASR = ETH_DMASR_RBUS;
ETH->DMARPDR=0;
}
return frame;
}
if(((DMARxDescToGet->StatusÐ_DMARxDesc_ES)==(u32)RESET)&&
((DMARxDescToGet->Status & ETH_DMARxDesc_LS)!=(u32)RESET)&&
((DMARxDescToGet->Status & ETH_DMARxDesc_FS)!=(u32)RESET))
{
framelength=((DMARxDescToGet->StatusÐ_DMARxDesc_FL)>>ETH_DMARxDesc_FrameLengthShift)-4;
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;
__HAL_LOCK(heth);
heth->State = HAL_ETH_STATE_BUSY;
if(((heth->RxDesc->Status & ETH_DMARXDESC_OWN) == (uint32_t)RESET))
{
if(((heth->RxDesc->Status & ETH_DMARXDESC_LS) != (uint32_t)RESET))
{
(heth->RxFrameInfos).SegCount++;
if ((heth->RxFrameInfos).SegCount == 1)
{
(heth->RxFrameInfos).FSRxDesc =heth->RxDesc;
}
heth->RxFrameInfos.LSRxDesc = heth->RxDesc;
framelength = (((heth->RxDesc)->Status & ETH_DMARXDESC_FL) >> ETH_DMARXDESC_FRAMELENGTHSHIFT) - 4;
heth->RxFrameInfos.length = framelength;
heth->RxFrameInfos.buffer = ((heth->RxFrameInfos).FSRxDesc)->Buffer1Addr;
heth->RxDesc = (ETH_DMADescTypeDef*) ((heth->RxDesc)->Buffer2NextDescAddr);
heth->State = HAL_ETH_STATE_READY;
__HAL_UNLOCK(heth);
return HAL_OK;
}
else if((heth->RxDesc->Status & ETH_DMARXDESC_FS) != (uint32_t)RESET)
{
(heth->RxFrameInfos).FSRxDesc = heth->RxDesc;
(heth->RxFrameInfos).LSRxDesc = NULL;
(heth->RxFrameInfos).SegCount = 1;
heth->RxDesc = (ETH_DMADescTypeDef*) (heth->RxDesc->Buffer2NextDescAddr);
}
else
{
(heth->RxFrameInfos).SegCount++;
heth->RxDesc = (ETH_DMADescTypeDef*) (heth->RxDesc->Buffer2NextDescAddr);
}
}
heth->State = HAL_ETH_STATE_READY;
__HAL_UNLOCK(heth);
return HAL_ERROR;
}
再看接收中断函数:
void ETH_IRQHandler(void)
{
FrameTypeDef frame={0,0,NULL};
uint32_t length = 0;
length = ETH_GetRxPktSize(DMARxDescToGet);
if(length != 0)
{
printf("recv_length = %d\r\n",length);
frame = eth_rx_pack();
printf("frame.length = %d\r\n",frame.length);
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;
if((ETH->DMASRÐ_DMASR_RBUS)!=(u32)RESET)
{
ETH->DMASR=ETH_DMASR_RBUS;
ETH->DMARPDR=0;
}
printf("eth_interrupt\r\n\r\n");
ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
}
发送函数如下:
uint8_t test_buffer[2048]={0};
uint8_t eth_tx_pack(uint8_t length)
{
if((DMATxDescToSet->StatusÐ_DMATxDesc_OWN) != RESET)
return ETH_ERROR;
DMATxDescToSet->ControlBufferSize = lengthÐ_DMATxDesc_TBS1;
DMATxDescToSet->Status|=ETH_DMATxDesc_LS|ETH_DMATxDesc_FS;
DMATxDescToSet->Status|=ETH_DMATxDesc_OWN;
if((ETH->DMASRÐ_DMASR_TBUS)!=(u32)RESET)
{
ETH->DMASR=ETH_DMASR_TBUS;
ETH->DMATPDR=0;
}
DMATxDescToSet=(ETH_DMADESCTypeDef*)(DMATxDescToSet->Buffer2NextDescAddr);
return ETH_SUCCESS;
}
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);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_11;
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);
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);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_ETH);
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;
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;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_ETH_MAC_Tx |RCC_AHB1Periph_ETH_MAC_Rx, ENABLE);
ETH_DeInit();
ETH_SoftwareReset();
while(ETH_GetSoftwareResetStatus() == SET);
ETH_StructInit(Ð_InitStructure);
ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Enable;
ETH_InitStructure.ETH_LoopbackMode = ETH_LoopbackMode_Disable;
ETH_InitStructure.ETH_RetryTransmission = ETH_RetryTransmission_Disable;
ETH_InitStructure.ETH_AutomaticPadCRCStrip = ETH_AutomaticPadCRCStrip_Disable;
ETH_InitStructure.ETH_ReceiveAll = ETH_ReceiveAll_Disable;
ETH_InitStructure.ETH_BroadcastFramesReception = ETH_BroadcastFramesReception_Enable;
ETH_InitStructure.ETH_PromiscuousMode = ETH_PromiscuousMode_Disable;
ETH_InitStructure.ETH_MulticastFramesFilter = ETH_MulticastFramesFilter_Perfect;
ETH_InitStructure.ETH_UnicastFramesFilter = ETH_UnicastFramesFilter_Perfect;
ETH_InitStructure.ETH_DropTCPIPChecksumErrorFrame = ETH_DropTCPIPChecksumErrorFrame_Enable;
ETH_InitStructure.ETH_ReceiveStoreForward = ETH_ReceiveStoreForward_Enable;
ETH_InitStructure.ETH_TransmitStoreForward = ETH_TransmitStoreForward_Enable;
ETH_InitStructure.ETH_ForwardErrorFrames = ETH_ForwardErrorFrames_Disable;
ETH_InitStructure.ETH_ForwardUndersizedGoodFrames = ETH_ForwardUndersizedGoodFrames_Disable;
ETH_InitStructure.ETH_SecondFrameOperate = ETH_SecondFrameOperate_Enable;
ETH_InitStructure.ETH_AddressAlignedBeats = ETH_AddressAlignedBeats_Enable;
ETH_InitStructure.ETH_FixedBurst = ETH_FixedBurst_Enable;
ETH_InitStructure.ETH_RxDMABurstLength = ETH_RxDMABurstLength_32Beat;
ETH_InitStructure.ETH_TxDMABurstLength = ETH_TxDMABurstLength_32Beat;
ETH_InitStructure.ETH_DMAArbitration = ETH_DMAArbitration_RoundRobin_RxTx_2_1;
rval=ETH_Init(Ð_InitStructure,0);
if(rval == SUCCESS)
{
ETH_DMAITConfig(ETH_DMA_IT_NIS|ETH_DMA_IT_R,ENABLE);
}
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);
ETH_DMATxDescChainInit(dma_rx_desc_tab, rx_buff, ETH_TXBUFNB);
ETH_DMARxDescChainInit(dma_tx_desc_tab, tx_buff, ETH_RXBUFNB);
ETH_Start();
}
wireshark抓包如下:
中间浅黄色的ARP包一共11个,其中第一个ARP包是路由的广播包,剩下的10个包是我开发板收到数据包后,将其收到的内容原封不动的发了10次,细心的同学肯定看到了wireshark抓包从第二个ARP包开始后边都是间隔5ms。因为我们程序中每个5ms发送一个包。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)