以太网外设ETH

1. 概述

近几年,项目需要,在多款单片机上使用了以太网外设。
本文为阶段知识整理,查缺补漏,方便以后再次遇到相关任务时,可以游刃有余的完成工作。

1.1 修改时间

  • 2023年5月6日创建本文。包含STM32的ETH外设。
  • 2023年5月9日修改本文。包含HHD32,GD32的ETH外设。

2. STM32F107的以太网外设

  • 没有使用中断引脚,通过DMA的方式进行收发。
  • RT-THREAD
  • LwIP

2.0 Chain Mode

以太网外设ETH_第1张图片

#define ETH_MAX_PACKET_SIZE       1524U    /*!< ETH_HEADER + ETH_EXTRA + ETH_VLAN_TAG + ETH_MAX_ETH_PAYLOAD + ETH_CRC */
/* Definition of the Ethernet driver buffers size and count */
#define ETH_RX_BUF_SIZE                ETH_MAX_PACKET_SIZE /* buffer size for receive               */
#define ETH_TX_BUF_SIZE                ETH_MAX_PACKET_SIZE /* buffer size for transmit              */
#define ETH_RXBUFNB                    8U       /* 4 Rx buffers of size ETH_RX_BUF_SIZE  */
#define ETH_TXBUFNB                    4U       /* 4 Tx buffers of size ETH_TX_BUF_SIZE  */
__ALIGN_BEGIN ETH_DMADescTypeDef  DMARxDscrTab[ETH_RXBUFNB] __ALIGN_END;/* Ethernet Rx MA Descriptor */
__ALIGN_BEGIN ETH_DMADescTypeDef  DMATxDscrTab[ETH_TXBUFNB] __ALIGN_END;/* Ethernet Tx DMA Descriptor */
__ALIGN_BEGIN uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] __ALIGN_END; /* Ethernet Receive Buffer */
__ALIGN_BEGIN uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE] __ALIGN_END; /* Ethernet Transmit Buffer */
  /* Initialize Tx Descriptors list: Chain Mode */
  HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);

  /* Initialize Rx Descriptors list: Chain Mode  */
  HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);

2.1 low_level_init

  • 根据STM32的型号,可以知道F107支持ETH外设。
  • 支持MII模式(25MHz时钟)和RMII模式(50MHz时钟)。
    以太网外设ETH_第2张图片

2.1.1 初始化函数 HAL_ETH_Init

函数声明:HAL_StatusTypeDef HAL_ETH_Init(ETH_HandleTypeDef *heth); 用于初始化MAC和DMA。

(1)初始化引脚
  • 包括时钟,中断优先级
 /* Init the low level hardware : GPIO, CLOCK, NVIC. */
  HAL_ETH_MspInit(heth);
(2)选择MII或者RMII模式
 /* Select MII or RMII Mode*/
  AFIO->MAPR &= ~(AFIO_MAPR_MII_RMII_SEL);
  AFIO->MAPR |= (uint32_t) heth->Init.MediaInterface; //ETH_MEDIA_INTERFACE_MII      0x00000000
                                                      //ETH_MEDIA_INTERFACE_RMII     0x00800000
(3)软件复位
  • 等待复位完成,有超时机制
  /* Ethernet Software reset */
  /* Set the SWR bit: resets all MAC subsystem internal registers and logic */
  /* After reset all the registers holds their respective reset values */
  (heth->Instance)->DMABMR |= ETH_DMABMR_SR;
 /* Get tick */
  tickstart = HAL_GetTick();

  /* Wait for software reset */
  while (((heth->Instance)->DMABMR & ETH_DMABMR_SR) != (uint32_t)RESET)
  {
    /* Check for the Timeout */
    if ((HAL_GetTick() - tickstart) > ETH_TIMEOUT_SWRESET)
    {
      heth->State = HAL_ETH_STATE_TIMEOUT;

      /* Process Unlocked */
      __HAL_UNLOCK(heth);

      /* Note: The SWR is not performed if the ETH_RX_CLK or the ETH_TX_CLK are
         not available, please check your external PHY or the IO configuration */
         //注意:如果ETH_RX_CLK或ETH_TX_CLK不可用,则不执行SWR,请检查您的外部PHY或IO配置
      return HAL_TIMEOUT;
    }
  }
(4)初始化MAC
  /*-------------------------------- MAC Initialization ----------------------*/
  /* Get the ETHERNET MACMIIAR value */
  tmpreg1 = (heth->Instance)->MACMIIAR;
  /* Clear CSR Clock Range CR[2:0] bits */
  tmpreg1 &= ETH_MACMIIAR_CR_MASK;

  /* Get hclk frequency value */
  hclk = HAL_RCC_GetHCLKFreq();

  /* Set CR bits depending on hclk value */
  if ((hclk >= 20000000U) && (hclk < 35000000U))
  {
    /* CSR Clock Range between 20-35 MHz */
    tmpreg1 |= (uint32_t)ETH_MACMIIAR_CR_DIV16;
  }
  else if ((hclk >= 35000000U) && (hclk < 60000000U))
  {
    /* CSR Clock Range between 35-60 MHz */
    tmpreg1 |= (uint32_t)ETH_MACMIIAR_CR_DIV26;
  }
  else
  {
    /* CSR Clock Range between 60-72 MHz */
    tmpreg1 |= (uint32_t)ETH_MACMIIAR_CR_DIV42;
  }

  /* Write to ETHERNET MAC MIIAR: Configure the ETHERNET CSR Clock Range */
  (heth->Instance)->MACMIIAR = (uint32_t)tmpreg1;
(5)PHY初始化和配置
  • 让PHY芯片进行寄存器复位。
 /* Put the PHY in reset mode */
HAL_ETH_WritePHYRegister(heth, PHY_BCR, PHY_RESET));
/* Delay to assure PHY reset */
HAL_Delay(PHY_RESET_DELAY);
  • 如果启用了“自协商”
if ((heth->Init).AutoNegotiation != ETH_AUTONEGOTIATION_DISABLE)
{
    ......
}
  • 如果禁用了“自协商” , 需要写入“双工模式”,“速度”。
  else /* AutoNegotiation Disable */
  {
    /* Set MAC Speed and Duplex Mode */
    HAL_ETH_WritePHYRegister(heth, PHY_BCR,  ((uint16_t)((heth->Init).DuplexMode >> 3U) | (uint16_t)((heth->Init).Speed >> 1U)  );
    /* Delay to assure PHY configuration */
    HAL_Delay(PHY_CONFIG_DELAY);
  }
  • 配置MAC和DMA
  /* Config MAC and DMA */
  ETH_MACDMAConfig(heth, err);

  /* Set ETH HAL State to Ready */
  heth->State = HAL_ETH_STATE_READY;

2.1.2 模式配置修改

  • 可以通过SMI总线,软件操作PHY的寄存器,配置PHY的工作模式
  • PHY处于 MII到copper的模式
  • PHY处于 MII到SGMII的模式

2.1.3 配置收发的缓冲区

  • 链模式 Chain Mode
  /* Initialize Tx Descriptors list: Chain Mode */
  HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);

  /* Initialize Rx Descriptors list: Chain Mode  */
  HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);

2.1.4 配置网口信息

  • 硬件MAC地址
  • 最大传输单元
  • 接收广播和ARP

2.1.5 创建信号量

  • 生成一个信号量,用于通知数据的到来
/* create a binary semaphore used for informing ethernetif of frame reception */	
gRxSem = rt_sem_create("gRxSem", 0, RT_IPC_FLAG_FIFO);

2.1.6 创建一个接收线程

mytask   = rt_thread_create("if_input", ethernetif_input, &gnetif, 512, 4, 1);
if(mytask != NULL)
{
		rt_thread_startup(mytask);
}
  • 接收线程,函数定义如下。通过等待信号量的方式进行线程的切换实现。
void ethernetif_input(void* argument)
{
  struct pbuf *p;
  struct netif *netif = (struct netif *) argument;
  struct eth_hdr *ethhdr;
  for( ;; )
  {
   // if (osSemaphoreAcquire(s_xSemaphore, TIME_WAITING_FOR_INPUT) == osOK)
	waitEthData();	//等待信号量
    {
        do
        {
	        LOCK_TCPIP_CORE();
	        p = low_level_input( netif );
	        if   (p != NULL)
	        {
				ethhdr = p->payload;//指向数据包有效负载,它以以太网报头开始
				switch (htons(ethhdr->type))
				{
					/* IP or ARP packet? */
					case ETHTYPE_IP:
					case ETHTYPE_ARP:
						/* 将完整数据包发送到tcpip_thread以进行处理 */
						//tcpip_input(p, netif)  --> sys_mbox_trypost(&mbox, msg) 发送邮箱信息,tcpip主线程接收邮箱信息
						if (netif->input(p, netif)!=ERR_OK) 
						{
							 pbuf_free(p);
							 p = NULL;
						}
						break;
					default:
					    pbuf_free(p);
						p = NULL;
						break;
		       }//end of switch
            }//end of  if   (p != NULL)
            UNLOCK_TCPIP_CORE();
        } while(p!=NULL);
    }//end of  if (osSem...
    }//end of for(;;)
}

2.1.7 启动网卡

/* Enable MAC and DMA transmission and reception */ 
  HAL_ETH_Start(&heth);

2.2 low_level_output

2.2.1 获取驱动发送缓冲区

__IO ETH_DMADescTypeDef *DmaTxDesc = heth.TxDesc;
uint8_t                 *buffer    = (uint8_t *)(heth.TxDesc->Buffer1Addr);

以太网外设ETH_第3张图片

2.2.2 检查 能否访问

  • 驱动发送缓冲区描述块 DmaTxDesc
  • DmaTxDesc->Status 的 Bit 31 OWN: Own bit
    1 - 置位时,该位指示描述符归 DMA 所有。(DMA占用中)
    0 - 复位时,表示描述符归 CPU 所有。(CPU可控制)DMA 在完成帧传输或在描述符中分配的缓冲区被完全读取时清除该位。
    以太网外设ETH_第4张图片
/* Is this buffer available? If not, goto error */
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
  errval = ERR_USE;
  goto error;
}

2.2.3 转移数据

  • 把数据从pbufs中,转移到驱动缓冲区中,用于发送。
//遍历全部pbuf数据块
for(q = p; q != NULL; q = q->next)
{
     //(1) 检查当前的驱动发送缓冲区能否访问
     ... ...
      //(2) 获取当前 lwIP的pbuf 缓冲区的字节长度
      byteslefttocopy = q->len;
      payloadoffset = 0;
     //(3) 检查 剩余需要复制的字节长度是否超过驱动设备缓冲区的长度,如果超过,则一次搬运 ETH_TX_BUF_SIZE 字节的数据
      while( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
      {
        //(3.1) 将数据从pbuf 拷贝到 驱动发送缓冲区buffer ,bufferoffset=0
        memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset) );
        //(3.2) 指向下一个驱动发送缓冲区块
        DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
        //(3.3) 检查能否被访问
        ... ...
        //(3.4) 获取驱动发送缓冲区
        buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr);

        byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
        payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
        framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
        bufferoffset = 0;
      }

     //(4) 复制转移剩余的字节
      memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), byteslefttocopy );
      bufferoffset = bufferoffset + byteslefttocopy;
      framelength = framelength + byteslefttocopy;
    }

2.2.4 传递给DMA

  • 把驱动发送描述块 传递给DMA,DMA自动发送出数据
 /* Prepare transmit descriptors to give to DMA */
  HAL_ETH_TransmitFrame(&heth, framelength);

2.3 low_level_input

  • 分配pbuf,将传入数据包的字节从接口传输到pbuf。

2.3.1 获取接收的帧数据

HAL_ETH_GetReceivedFrame_IT(&heth)
len = heth.RxFrameInfos.length;
buffer = (uint8_t *)heth.RxFrameInfos.buffer;

2.3.2 分配pbuf

  • 如果收到了数据,需要创建接收存放数据的pbuf。用于在lwip协议栈里面流转。
/* We allocate a pbuf chain of pbufs from the Lwip buffer pool */
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);

2.3.3 获取驱动接收缓冲区

  • 存放帧数据的驱动接收缓冲区
dmarxdesc = heth.RxFrameInfos.FSRxDesc;
bufferoffset = 0;

2.3.4 转移数据

  • 分配pbuf成功后,开始转移数据。
for(q = p; q != NULL; q = q->next)
{
byteslefttocopy = q->len;
payloadoffset = 0;

/* Check if the length of bytes to copy in current pbuf is bigger than Rx buffer size*/
//(1) 检查 剩余的需要转移的数据字节 是否 大于 当前的pbuf空间大小
while( (byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE )
{
  /* Copy data to pbuf */
  memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), (ETH_RX_BUF_SIZE - bufferoffset));

  //获取驱动接收缓冲区
  dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
  buffer = (uint8_t *)(dmarxdesc->Buffer1Addr);

  byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);
  payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);
  bufferoffset = 0;
}

//(2) 拷贝剩余的数据,到pbuf中。
memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), byteslefttocopy);
bufferoffset = bufferoffset + byteslefttocopy;
}

2.3.5 释放接收描述块到DMA

  • 1 - 置位时,该位指示描述符归 DMA 所有。(DMA占用中)
  • 0 - 复位时,表示描述符归 CPU 所有。(CPU可控制)
/* Release descriptors to DMA */
/* Point to first descriptor */
dmarxdesc = heth.RxFrameInfos.FSRxDesc;
/* Set Own bit in Rx descriptors: gives the buffers back to DMA */
for (i=0; i< heth.RxFrameInfos.SegCount; i++)
{
  dmarxdesc->Status |= ETH_DMARXDESC_OWN;
  dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
}

2.3.6 恢复DMA接收

/* Clear Segment_Count */
heth.RxFrameInfos.SegCount =0;

/* When Rx Buffer unavailable flag is set: clear it and resume reception */
if ((heth.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET)
{
  /* Clear RBUS ETHERNET DMA flag */
  heth.Instance->DMASR = ETH_DMASR_RBUS;
  /* Resume DMA reception */
  heth.Instance->DMARPDR = 0;
}

2.3.7 返回pbuf数据

return p;

2.4 函数调用

  • 主线程 tcpip_thread
  • 接收线程 ethernetif_input
    以太网外设ETH_第5张图片

2.5 其他函数

u32_t sys_now(void);
u32_t sys_jiffies(void);

3. HHD32F1xx的以太网外设

  • 海威华芯
  • 未使用low_level 低级别驱动函数。使用“设备Device”模块化接口。
  • 因工程代码由多人接手,此处调用结构略微混乱。已做简化处理。
    以太网外设ETH_第6张图片

3.1 rt_stm32_eth_init

3.1.1 配置MAC地址

在函数rt_hw_hhd_eth_init中定义了MAC地址。

    /* OUI 00-80-E1 STMICROELECTRONICS. */
    stm32_eth_device.dev_addr[0] = 0x00;
    stm32_eth_device.dev_addr[1] = 0x80;
    stm32_eth_device.dev_addr[2] = 0xE1;
	
    /* generate MAC addr from 96bit unique ID (only for test). */
	mark_mac = get_mark();
    stm32_eth_device.dev_addr[3] = ETH_MAC3;
    stm32_eth_device.dev_addr[4] = ETH_MAC4;
    stm32_eth_device.dev_addr[5] = (mark_mac & 0x0F) + ETH_MAC5;

在调用rt_stm32_eth_init时,会将MAC地址写入到相应的寄存器中。

ETH_MACAddressConfig(ETH_MAC_Address0, (u8*)&stm32_eth->dev_addr[0]);
void ETH_MACAddressConfig(uint32_t MacAddr, uint8_t *Addr)
{
    uint32_t tmpreg;
    /* Calculate the selectecd MAC address high register */
    tmpreg = ((uint32_t)Addr[5] << 8) | (uint32_t)Addr[4];
    /* Load the selectecd MAC address high register */
    (*(__IO uint32_t *) (ETH_MAC_ADDR_HBASE + MacAddr)) = tmpreg;
    /* Calculate the selectecd MAC address low register */
    tmpreg = ((uint32_t)Addr[3] << 24) | ((uint32_t)Addr[2] << 16) | ((uint32_t)Addr[1] << 8) | Addr[0];

    /* Load the selectecd MAC address low register */
    (*(__IO uint32_t *) (ETH_MAC_ADDR_LBASE + MacAddr)) = tmpreg;
}

3.1.2 初始化chain mode

  • 初始化环形链表,并赋值关联到收发空间。
  • 参考目录的2.0小节。

(1)空间大小定义

#define ETH_MAX_PACKET_SIZE    1520    /*!< ETH_HEADER + ETH_EXTRA + MAX_ETH_PAYLOAD + ETH_CRC */
#define ETH_RXBUFNB        			6
#define ETH_TXBUFNB        			4

(2)数组定义

static ETH_DMADESCTypeDef  DMARxDscrTab[ETH_RXBUFNB], DMATxDscrTab[ETH_TXBUFNB];
static rt_uint8_t Rx_Buff[ETH_RXBUFNB][ETH_MAX_PACKET_SIZE], Tx_Buff[ETH_TXBUFNB][ETH_MAX_PACKET_SIZE];

(3)初始化

/* Initialize Tx Descriptors list: Chain Mode */
ETH_DMATxDescChainInit(DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);
/* Initialize Rx Descriptors list: Chain Mode  */
ETH_DMARxDescChainInit(DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);

(4)启用 DMA 接收描述块的 接收中断标志。

/* Enable Ethernet Rx interrrupt */
int i;
for(i=0; i<ETH_RXBUFNB; i++)
{
     ETH_DMARxDescReceiveITConfig(&DMARxDscrTab[i], ENABLE);
}

3.1.3 启动ETH外设

  • 启动DMA相关的功能
/* Enable MAC and DMA transmission and reception */
ETH_Start();
void ETH_Start(void)
{
  /* Enable transmit state machine of the MAC for transmission on the MII */  
  ETH_MACTransmissionCmd(ENABLE);
  /* Flush Transmit FIFO */
  ETH_FlushTransmitFIFO();
  /* Enable receive state machine of the MAC for reception from the MII */  
  ETH_MACReceptionCmd(ENABLE);
   /* Start DMA reception */
  ETH_DMAReceptionCmd(ENABLE);   
  /* Start DMA transmission */
  ETH_DMATransmissionCmd(ENABLE);
}

3.2 rt_stm32_eth_tx

3.2.1 获取当前发送缓冲区

uint8_t *buffer =  (rt_uint8_t*)ETH_GetCurrentTxBuffer();
u32 ETH_GetCurrentTxBuffer(void)
{ 
  /* Return Buffer address */
  return (DMATxDescToSet->Buffer1Addr);   
}

3.2.2 转移数据

  • 将pbuf数据复制到驱动缓冲区中。
for(q = p; q != NULL; q = q->next) 
{
	memcpy((rt_uint8_t*)&buffer[offset], q->payload, q->len);

	offset = offset + q->len;
}

3.2.3 使能发送

  • offset 表示数据长度
ETH_TxPkt_ChainMode(offset);
(1)检查能否访问
/* Check if the descriptor is owned by the ETHERNET DMA (when set) or CPU (when reset) */
if((DMATxDescToSet->Status & ETH_DMATxDesc_OWN) != (u32)RESET)
{  
/* Return ERROR: OWN bit set */
  return RT_ERROR;
}
(2)控制发送缓冲区
  • 设置待发送的数据的长度。
  • 同时设置帧头帧尾标志,表示一帧数据全在一个发送描述块中。
/* Setting the Frame Length: bits[12:0] */
DMATxDescToSet->ControlBufferSize = (FrameLength & ETH_DMATxDesc_TBS1);
/* Setting the last segment and first segment bits (in this case a frame is transmitted in one descriptor) */    
DMATxDescToSet->ControlBufferSize |= ETH_DMATxDesc_LS | ETH_DMATxDesc_FS;
DMATxDescToSet->ControlBufferSize |= ETH_DMATxDesc_TCH;  // /*!< Second Address Chained 和ST不一样 
(3)启动发送
  • 将描述块的访问权限还给DMA,用于DMA的发送。
  • 恢复DMA的发送。
/* Set Own bit of the Tx descriptor Status: gives the buffer back to ETHERNET DMA */
DMATxDescToSet->Status |= ETH_DMATxDesc_OWN;
/* When Tx Buffer unavailable flag is set: clear it and resume transmission */
if ((HHD_ETH->DMASR & ETH_DMASR_TBUS) != (u32)RESET)
{
  /* Clear TBUS ETHERNET DMA flag */
  HHD_ETH->DMASR = ETH_DMASR_TBUS;
  /* Resume DMA transmission*/
  HHD_ETH->DMATPDR = 0;
}
(4)切换当前发送缓冲区
  • 用于下一次获取。
 /* Update the ETHERNET DMA global Tx descriptor with next Tx decriptor */  
 /* Chained Mode */
 /* Selects the next DMA Tx descriptor list for next buffer to send */ 
if((DMATxDescToSet->ControlBufferSize & ETH_DMATxDesc_TCH) != (uint32_t)RESET)
{
  DMATxDescToSet = (ETH_DMADESCTypeDef*) (DMATxDescToSet->Buffer2NextDescAddr); 
}

3.3 rt_stm32_eth_rx

3.3.1 检查能否访问

  • 判断接收描述块,属于CPU还是属于DMA。
  • 当属于DMA是,不可以被访问。
p = RT_NULL;
/* Check if the descriptor is owned by the ETHERNET DMA (when set) or CPU (when reset) */
if(((DMARxDescToGet->Status & ETH_DMARxDesc_OWN) != (uint32_t)RESET))
    return p;

3.3.2 接收数据

  • 检测数据块完整性,是不是一个接收描述块 包含了帧头帧尾。ETH_DMARxDesc_FS,ETH_DMARxDesc_LS。
  • 检测是否发生错误。ETH_DMARxDesc_ES。
  • 获取驱动接收缓冲区的数据长度。
  • 分配pbuf空间。
  • 复制数据。
if (((DMARxDescToGet->Status & ETH_DMARxDesc_ES) == (uint32_t)RESET) &&
         ((DMARxDescToGet->Status & ETH_DMARxDesc_LS) != (uint32_t)RESET) &&
         ((DMARxDescToGet->Status & ETH_DMARxDesc_FS) != (uint32_t)RESET))
 {
     /* Get the Frame Length of the received packet: substruct 4 bytes of the CRC */
     framelength = ((DMARxDescToGet->Status & ETH_DMARxDesc_FL) >> ETH_DMARXDESC_FRAME_LENGTHSHIFT) - 4;
     //rt_kprintf("%pkg len %d\n", framelength);
     /* allocate buffer */
     p = pbuf_alloc(PBUF_LINK, framelength, PBUF_RAM);
     if (p != RT_NULL)
     {
         struct pbuf* q;

         for (q = p; q != RT_NULL; q= q->next)
         {
          /* Copy the received frame into buffer from memory pointed by the current ETHERNET DMA Rx descriptor */
             memcpy(q->payload, (uint8_t *)((DMARxDescToGet->Buffer1Addr) + offset), q->len);
             offset += q->len;
         }

     }
 }

3.3.3 恢复DMA接收

  • 将驱动描述块的访问权限,还给DMA。
  • 恢复DMA的接收功能。
/* Set Own bit of the Rx descriptor Status: gives the buffer back to ETHERNET DMA */
DMARxDescToGet->Status = ETH_DMARxDesc_OWN;

/* When Rx Buffer unavailable flag is set: clear it and resume reception */
if ((HHD_ETH->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET)
{
    /* Clear RBUS ETHERNET DMA flag */
    HHD_ETH->DMASR = ETH_DMASR_RBUS;
    /* Resume DMA reception */
    HHD_ETH->DMARPDR = 0;
}

3.3.4 切换当前接收描述块

  • 用于下一次接收。
  • 如果是已经是最后一个环节,则需要切换到第一个描述块。
/* Update the ETHERNET DMA global Rx descriptor with next Rx decriptor */
/* Chained Mode */
if((DMARxDescToGet->ControlBufferSize & ETH_DMARxDesc_RCH) != (uint32_t)RESET)
{
    /* Selects the next DMA Rx descriptor list for next buffer to read */
    DMARxDescToGet = (ETH_DMADESCTypeDef*) (DMARxDescToGet->Buffer2NextDescAddr);
}
else /* Ring Mode */
{
    if((DMARxDescToGet->ControlBufferSize & ETH_DMARxDesc_RER) != (uint32_t)RESET)
    {
        /* Selects the first DMA Rx descriptor for next buffer to read: last Rx descriptor was used */
        DMARxDescToGet = (ETH_DMADESCTypeDef*) (HHD_ETH->DMARDLAR);
    }
    else
    {
        /* Selects the next DMA Rx descriptor list for next buffer to read */
        DMARxDescToGet = (ETH_DMADESCTypeDef*) ((uint32_t)DMARxDescToGet + 0x10 + ((HHD_ETH->DMABMR & ETH_DMABMR_DSL) >> 2));
    }
}

3.4 eth_device_init

  • 调用,参数为 stm32_eth_device.parent。
  • 下面提到的dev,都是指这里的stm32_eth_device.parent。
eth_device_init(&(stm32_eth_device.parent), "NT");

3.4.1 创建一个netif网络接口

  • 传递给dev,作为dev的数据成员。
struct netif* netif;
netif = (struct netif*) rt_malloc (sizeof(struct netif));
if (netif == RT_NULL)
{
	rt_kprintf("malloc netif failed\n");
	return -RT_ERROR;
}
rt_memset(netif, 0, sizeof(struct netif));
dev->netif = netif;

3.4.2 注册设备

  • 函数rt_device_register。
  • 设备类型为“网络接口”。
  • 初始化信号量。
/* register to rt-thread device manager */
rt_device_register(&(dev->parent), name, RT_DEVICE_FLAG_RDWR);

dev->parent.type = RT_Device_Class_NetIf;
rt_sem_init(&(dev->tx_ack), name, 0, RT_IPC_FLAG_FIFO);

3.4.3 对netif进行部分赋值

  • 函数rt_device_control,也就是rt_stm32_eth_control函数,用于将MAC硬件地址 stm32_eth_device.dev_addr,复制到 网络接口的硬件地址区 netif->hwaddr。
/* set name */
netif->name[0] = name[0];//N
netif->name[1] = name[1];//T

/* set hw address to 6 */
netif->hwaddr_len	= 6;
/* maximum transfer unit */
netif->mtu			= ETHERNET_MTU;
/* broadcast capability */
netif->flags		= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;

/* get hardware address */
rt_device_control(&(dev->parent), NIOCTL_GADDR, netif->hwaddr);

/* set output */
netif->output		= etharp_output;
netif->linkoutput	= ethernetif_linkoutput;
	

3.5 函数调用

以太网外设ETH_第7张图片

4. GD32F307的以太网外设

  • 和HHD32一样使用device的框架。

4.1 rt_stm32_eth_init

4.1.1 配置MAC地址

参考3.1.1。
在调用rt_stm32_eth_init时,会将MAC地址写入到相应的寄存器中。

enet_mac_address_set(ENET_MAC_ADDRESS0, (uint8_t*)&stm32_eth->dev_addr[0]);//netif->hwaddr

low_level_init();

4.1.2 初始化chain mode

参考3.1.2。
(1)初始化

/* Initialize Tx Descriptors list: Chain Mode */
enet_descriptors_chain_init(ENET_DMA_TX);
/* Initialize Rx Descriptors list: Chain Mode  */
enet_descriptors_chain_init(ENET_DMA_RX);

(2)启用 DMA 接收描述块的 接收中断标志。

/* enable ethernet Rx interrrupt */
{   int i;
	for(i=0; i<ENET_RXBUF_NUM; i++){ 
	    enet_rx_desc_immediate_receive_complete_interrupt(&rxdesc_tab[i]);
	}
 }

4.1.3 启动ETH外设

/* Enable MAC and DMA transmission and reception */
enet_enable();
void enet_enable(void)
{
   enet_tx_enable();
   enet_rx_enable();
}

4.2 rt_stm32_eth_tx

4.2.1 获取当前发送缓冲区

uint8_t *buffer =  (uint8_t *)(enet_desc_information_get(dma_current_txdesc, TXDESC_BUFFER_1_ADDR));
uint32_t enet_desc_information_get(enet_descriptors_struct *desc, enet_descstate_enum info_get)
{
    uint32_t reval = 0xFFFFFFFFU;

    switch(info_get){
    case RXDESC_BUFFER_1_SIZE:
        reval = GET_RDES1_RB1S(desc->control_buffer_size);
        break;
    case RXDESC_BUFFER_2_SIZE:
        reval = GET_RDES1_RB2S(desc->control_buffer_size);
        break;
    case RXDESC_FRAME_LENGTH:
        reval = GET_RDES0_FRML(desc->status);
        if(reval > 4U){
            reval = reval - 4U;

            /* if is a type frame, and CRC is not included in forwarding frame */ 
            if((RESET != (ENET_MAC_CFG & ENET_MAC_CFG_TFCD)) && (RESET != (desc->status & ENET_RDES0_FRMT))){
                reval = reval + 4U;
            }
        }else{
             reval = 0U;
        }

        break;
    case RXDESC_BUFFER_1_ADDR:    
        reval = desc->buffer1_addr;    
        break;
    case TXDESC_BUFFER_1_ADDR:    
        reval = desc->buffer1_addr;  
        break;
    case TXDESC_COLLISION_COUNT:    
        reval = GET_TDES0_COCNT(desc->status);
        break;
    default:
        break;
    }
    return reval;
}

4.2.2 转移数据

for(q = p; q != NULL; q = q->next) 
{
	memcpy((rt_uint8_t*)&buffer[offset], q->payload, q->len);

	offset = offset + q->len;
}

4.2.3 使能发送

参考3.2.3。

ETH_TxPkt_ChainMode(offset);
uint32_t ETH_TxPkt_ChainMode(uint16_t FrameLength)
{ 
	return ENET_NOCOPY_FRAME_TRANSMIT(FrameLength); 
}

4.3 rt_stm32_eth_rx

4.3.1 获取接收缓冲区的数据

/* init p pointer */
p = RT_NULL;
/* obtain the size of the packet and put it into the "len" variable. */
len = enet_desc_information_get(dma_current_rxdesc, RXDESC_FRAME_LENGTH);
buffer = (uint8_t *)(enet_desc_information_get(dma_current_rxdesc, RXDESC_BUFFER_1_ADDR));

4.3.2 复制数据

  • 创建pbuf,用于存放接收的数据。
if (len > 0){
     /* We allocate a pbuf chain of pbufs from the Lwip buffer pool */
     p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
}
if (p != NULL){
     for(q = p; q != NULL; q = q->next){
         memcpy((uint8_t *)q->payload, (u8_t*)&buffer[l], q->len);
         l = l + q->len;
     }
 }

4.3.3 恢复dma接收

ENET_NOCOPY_FRAME_RECEIVE();//enet_frame_receive(NULL, 0U)

X. 后续补充,欢迎指正!

你可能感兴趣的:(单片机,单片机,stm32,嵌入式硬件)