1. 概述
近几年,项目需要,在多款单片机上使用了以太网外设。
本文为阶段知识整理,查缺补漏,方便以后再次遇到相关任务时,可以游刃有余的完成工作。
1.1 修改时间
- 2023年5月6日创建本文。包含STM32的ETH外设。
- 2023年5月9日修改本文。包含HHD32,GD32的ETH外设。
2. STM32F107的以太网外设
- 没有使用中断引脚,通过DMA的方式进行收发。
- RT-THREAD
- LwIP
2.0 Chain Mode
#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时钟)。
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);
2.2.2 检查 能否访问
- 驱动发送缓冲区描述块 DmaTxDesc
- DmaTxDesc->Status 的 Bit 31 OWN: Own bit
1 - 置位时,该位指示描述符归 DMA 所有。(DMA占用中)
0 - 复位时,表示描述符归 CPU 所有。(CPU可控制)DMA 在完成帧传输或在描述符中分配的缓冲区被完全读取时清除该位。
/* 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
2.5 其他函数
u32_t sys_now(void);
u32_t sys_jiffies(void);
3. HHD32F1xx的以太网外设
- 海威华芯
- 未使用low_level 低级别驱动函数。使用“设备Device”模块化接口。
- 因工程代码由多人接手,此处调用结构略微混乱。已做简化处理。
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 函数调用
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)