目录
新版本(区别就是直接操作pbuf,pbuf贯穿于lwIP各层之间)
1.什么是以太网DMA描述符?
发送:不需要CPU的参与下,把描述符指向的缓冲区数据传输到Tx FIFO当中
接收:不需要CPU的参与下,将Rx FIFO中的数据传输到描述符指向的缓冲区当中
目前使用常规描述符
常规描述符结构(精简):
typedef struct
{
__IO uint32_t Status; /* 状态 */
uint32_t ControlBufferSize; /* 缓冲区1和2的大小 */
uint32_t Buffer1Addr; /* 缓冲区1的地址 */
uint32_t Buffer2NextDescAddr; /* 缓冲区2的地址/指向下一个描述符 */
/* 以下成员变量为增强描述符的 */
/* ……………………*/
}
ETH_DMADescTypeDef;
TX DMA描述符成员变量:
● TDES0[31]置0:CPU可将数据拷贝到描述符中,拷贝完成之后把该位置1,告诉DMA可以发送数据
● TDES0[20]置1:描述符中的第二个地址是下一个描述符地址
● TDES1[28:16]:如果TDES0[20] 位置1,则该字段无效
● TDES3[31:0]:取决于TDES0[20]的值,为1,则指向下一个描述符地址
RX DMA描述符成员变量 :
● RDES0[31]置1:MAC将数据从RX FIFO传输到RX描述符中,拷贝完成之后该位置0,告诉CPU可以接收数据
● RDES0[14]置1:描述符中的第二个地址是下一个描述符地址
● RDES1[28:16]:如果RDES0[14] 位置1,则该字段无效
● RDES3[31:0]:取决于RDES0[14]的值,为1,则指向下一个描述符地址
2.以太网DMA描述符结构
环形单向链表 :将一系列节点链接成链,并且单链表的最后一个节点指向链表的第一个节点,构成环状的链表
注意:
● 旧版本HAL库:申请一个新的缓冲区,DMA描述符指向它,然后将网络层下达的数据(pbuf缓冲区的内容)拷贝其中
● 新版本HAL库:直接管理pbuf缓冲区,不需要另外申请新的缓冲区
以太网DMA描述符注意细节 :
3.如何追踪描述符
ETH_HandleTypeDef中定义了RxDesc和TxDesc指针,它们是用来追踪Rx/Tx的DMA描述符(旧版本)
ETH_InitTypeDef中定义了RxDesc和TxDesc指针,它们是用来追踪Rx/Tx的DMA描述符(新版本)
新旧版本原理是一样的
TxDesc和RxDesc分别指向下 一个要发送或者接收的描述符
4.总结归纳
还是要注意:
老版本是申请一个缓存区,将pbuf缓存区的数据拷贝进去,再进行传输
新版本不用申请缓存区,直接操作pbuf
相关代码
老版本
①内存申请:
uint8_t ethernet_mem_malloc(void)
{
// 为接收描述符表申请内存,内存区域为SRAMIN,大小为接收缓冲区数量乘以接收描述符类型的大小
g_eth_dma_rx_dscr_tab = mymalloc(SRAMIN, ETH_RXBUFNB * sizeof(ETH_DMADescTypeDef));
// 为发送描述符表申请内存,内存区域为SRAMIN,大小为发送缓冲区数量乘以发送描述符类型的大小
g_eth_dma_tx_dscr_tab = mymalloc(SRAMIN, ETH_TXBUFNB * sizeof(ETH_DMADescTypeDef));
// 为接收缓冲区申请内存,内存区域为SRAMIN,大小为接收缓冲区大小乘以接收缓冲区数量
g_eth_rx_buf = mymalloc(SRAMIN, ETH_RX_BUF_SIZE * ETH_RXBUFNB);
// 为发送缓冲区申请内存,内存区域为SRAMIN,大小为发送缓冲区大小乘以发送缓冲区数量
g_eth_tx_buf = mymalloc(SRAMAM, ETH_TX_BUF_SIZE * ETH_TXBUFNB);
// 如果接收描述符表、发送描述符表、接收缓冲区或发送缓冲区的指针为NULL(即内存分配失败)
if (!( (uint32_t)&g_eth_dma_rx_dscr_tab ||!(uint32_t)&g_eth_dma_tx_dscr_tab ||!(uint32_t)&g_eth_rx_buf ||!(uint32_t)&g_eth_tx_buf) )
{
// 释放已分配的内存
ethernet_mem_free();
// 返回失败标志
return 1;
}
// 如果所有内存都分配成功,返回成功标志
return 0;
}
②描述符初始化:
HAL_ETH_DMATxDescListInit(&g_eth_handler,g_eth_dma_tx_dscr_tab,g_eth_tx_buf,ETH_TXBUFNB); /* 初始化发送描述符 */
HAL_ETH_DMARxDescListInit(&g_eth_handler,g_eth_dma_rx_dscr_tab,g_eth_rx_buf,ETH_RXBUFNB); /* 初始化接收描述符 */
代码详解(以发送描述符为例):
HAL_StatusTypeDef HAL_ETH_DMATxDescListInit(ETH_HandleTypeDef *heth,
ETH_DMADescTypeDef *DMATxDescTab,
uint8_t *TxBuff,
uint32_t TxBuffCount)
{
uint32_t i = 0U;
ETH_DMADescTypeDef *dmatxdesc;
// 1. 设置状态等于BUSY
heth->State = HAL_ETH_STATE_BUSY;
// 2. 指向第一个描述符
heth->TxDesc = DMATxDescTab;
// 3. 通过for循环来添加每一个描述符
for(i=0U; i < TxBuffCount; i++)
{
// 获取Tx Desc列表的第i个成员的指针
dmatxdesc = DMATxDescTab + i;
// TDES0[20]置1:设置第二个地址链接位
dmatxdesc->Status = ETH_DMATXDESC_TCH;
// 给描述符的buffer赋值地址
dmatxdesc->Buffer1Addr = (uint32_t)(&TxBuff[i*ETH_TX_BUF_SIZE]);
// 如果小于最大值
if(i < (TxBuffCount-1U))
{
// 描述符的next就指向下一个,否则就指向第一个描述符
dmatxdesc->Buffer2NextDescAddr = (uint32_t)(DMATxDescTab+i+1U);
}
else
{
// 最后一个描述符,设置下一个描述符地址寄存器等于第一个描述符基地址
dmatxdesc->Buffer2NextDescAddr = (uint32_t)DMATxDescTab;
}
}
// 4. 把描述符的地址赋值给DMA的寄存器
(heth->Instance)->DMATDLAR = (uint32_t)DMATxDescTab;
// 5. 设置状态等于Ready
heth->State = HAL_ETH_STATE_READY;
return HAL_OK;
}
新版本(区别就是直接操作pbuf,pbuf贯穿于lwIP各层之间)
①为发送 / 接收描述符申请内存
ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */
ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */
ETH_RX/TX_DESC_CNT = 4
②追踪 RX/TX 描述符
g_eth_handler.Init.RxDesc = DMARxDscrTab;
g_eth_handler.Init.TxDesc = DMATxDscrTab;
③初始化 RX/TX 描述符
/*------------------- DMA Tx Descriptors Configuration -------------------*/
ETH_DMATxDescListInit(heth);
/*------------------- DMA Rx Descriptors Configuration -------------------*/
ETH_DMARxDescListInit(heth);
代码详解(以发送描述符为例)
static void ETH_DMATxDescListInit(ETH_HandleTypeDef *heth)
{
ETH_DMADescTypeDef *dmatxdesc;
uint32_t i;
// 通过for循环来添加每一个描述符
for (i = 0; i < (uint32_t)ETH_TX_DESC_CNT; i++)
{
// 指向第一个描述符
dmatxdesc = heth->Init.TxDesc + i;
// 对描述符中的成员变量清零
WRITE_REG(dmatxdesc->DESC0, 0x0);
WRITE_REG(dmatxdesc->DESC1, 0x0);
WRITE_REG(dmatxdesc->DESC2, 0x0);
WRITE_REG(dmatxdesc->DESC3, 0x0);
// 描述符链表指针指向当前描述符
WRITE_REG(heth->TxDescList.TxDesc[i], (uint32_t)dmatxdesc);
// TDES0[20]置1:设置第二个地址链接位
SET_BIT(dmatxdesc->DESC0, ETH_DMATXDESC_TCH);
if (i < ((uint32_t)ETH_TX_DESC_CNT - 1U))
{
// 描述符的next就指向下一个,否则就指向第一个描述符
WRITE_REG(dmatxskip->DESC3, (uint32_t)(heth->Init.TxDesc + i + 1U));
}
else
{
// 最后一个描述符,设置下一个描述符地址寄存器等于第一个描述符基地址
WRITE_REG(dmatxdesc->DESC3, (uint32_t)(heth->Init.TxDesc));
}
// 设置DMA Tx描述符校验和插入
SET_BIT(dmatxdesc->DESC0, ETH_DMATXDESC_CHECKSUMTCPUDPICMPFULL);
}
// 设置TX描述符链表当前索引为0
heth->TxDescList.CurTxDesc = 0;
// 设置传输描述符列表地址
WRITE_REG(heth->Instance->DMATDLAR, (uint32_t)heth->Init.TxDesc);
}
④建立描述符与pbuf缓冲区的联系
static ETH_StatusTypeDef ETH_Prepare_Tx_Descriptors(ETH_HandleTypeDef *heth, ETH_TxPacketConfig *pTxConfig, uint32_t ItMode)
{
ETH_TxDescListTypeDef *dmatxdesclist = &heth->TxDescList;
uint32_t descidx = dmatxdesclist->CurTxDesc;
uint32_t firstdescdma = descidx;
uint32_t idx;
uint32_t descnbr = 0U;
ETH_DMADescTypeDef *dmatxdesc = (ETH_DMADescTypeDef *)dmatxdesclist->TxDesc[descidx];
ETH_BufferTypeDef *txbuffer = pTxConfig->TxBuffer;
uint32_t bd_count = 0U;
descnbr += 1U;
// 如果descnbr为1,它就是指向第一个描述符,并且指向缓冲区1
if (descnbr == 1U)
{
WRITE_REG(dmatxdesc->DESC2, (uint32_t)txbuffer->buffer);
// 设置描述符指向的缓冲区大小
MODIFY_REG(dmatxdesc->DESC1, ETH_DMATXDESC_TBS1, txbuffer->len);
// 设置第一个描述符的OWN位
SET_BIT(dmatxdesc->DESC0, ETH_DMATXDESC_OWN);
}
// 只有当报文被分成多个描述符(>1时)
while (txbuffer->next!= NULL)
{
// descidx索引加一
INCR_TX_DESC_INDEX(descidx, 1U);
// 指向第二个描述符
dmatxdesc = (ETH_DMADescTypeDef *)dmatxdesclist->TxDesc[descidx];
descnbr += 1U;
// 在列表中获得下一个tx缓冲区
txbuffer = txbuffer->next;
// 设置该描述符指向的tx缓冲区
WRITE_REG(dmatxdesc->DESC2, (uint32_t)txbuffer->buffer);
// 设置描述符指向的缓冲区大小
MODIFY_REG(dmatxdesc->DESC1, ETH_DMATXDESC_TBS1, txbuffer->len);
bd_count += 1U;
// 设置描述符的OWN位
SET_BIT(dmatxdesc->DESC0, ETH_DMATXDESC_OWN);
}
// 以下忽略多行代码
}