正点原子lwIP学习笔记——裸机lwIP启动流程

1.裸机lwIP启动流程——旧版本

lwIP初始化流程示意图
首先会先调用ethernet_mem_malloc这个函数来申请内存,两个是DMA描述符,还有两个就是缓冲区的数组,如下面的代码所示:

ETH_DMADescTypeDef *g_eth_dma_rx_dscr_tab;  /* 以太网DMA接收描述符数据结构体指针 */
ETH_DMADescTypeDef *g_eth_dma_tx_dscr_tab;  /* 以太网DMA发送描述符数据结构体指针 */
uint8_t *g_eth_rx_buf;                      /* 以太网底层驱动接收buffers指针 */
uint8_t *g_eth_tx_buf;                      /* 以太网底层驱动发送buffers指针 */

uint8_t ethernet_mem_malloc(void)
{
    g_eth_dma_rx_dscr_tab = mymalloc(SRAMIN, ETH_RXBUFNB * sizeof(ETH_DMADescTypeDef)); /* 申请内存 */
    g_eth_dma_tx_dscr_tab = mymalloc(SRAMIN, ETH_TXBUFNB * sizeof(ETH_DMADescTypeDef)); /* 申请内存 */
    g_eth_rx_buf = mymalloc(SRAMIN, ETH_RX_BUF_SIZE * ETH_RXBUFNB); /* 申请内存 */
    g_eth_tx_buf = mymalloc(SRAMIN, ETH_TX_BUF_SIZE * ETH_TXBUFNB); /* 申请内存 */

    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;       /* 申请成功 */
}

然后会调用ethernet_init()函数,实现以太网参数的初始化,配置传输速率、双工模式以及以太网芯片地址、MAC地址以及介质接口RMII地址,然后还对调用Msp函数对IO口进行初始化,最后硬件复位PHY芯片;

然后通过lwIP_init()进行lwIP的初始化,主要是完成内存、pbuf以及netif的初始化,还有通讯协议例如TCP和UDP的初始化(通过宏控制);

然后通过宏,判断是否DHCP动态分配IP地址,如果不是那就采取之前设置的静态的IP地址初始化;

虚拟网卡的控制块netif

我们需要添加虚拟网卡,才能发送和接受数据,所以要先设置虚拟网卡并添加进来,这一块是通过netif的函数进行操作的;通过设置完成后,需要通过etharp_output判断有没有对应的MAC地址,这一部分之后有讲解,涉及到ARP协议;

之后,通过low_level_output对数据进行传输,把网络层的数据包给到pbuf缓冲,pbuf在通过操作进入到Tx的缓冲区中,其中需要对1524字节的数据进行切分传输,代码如下:

low_level_output(struct netif *netif, struct pbuf *p)
{
    err_t errval;
    struct pbuf *q;
    
    uint8_t *buffer = (uint8_t *)(g_eth_handler.TxDesc->Buffer1Addr);
    __IO ETH_DMADescTypeDef *DmaTxDesc;
    uint32_t framelength = 0;
    uint32_t bufferoffset = 0;
    uint32_t byteslefttocopy = 0;
    uint32_t payloadoffset = 0;

    DmaTxDesc = g_eth_handler.TxDesc;
    bufferoffset = 0;

#if ETH_PAD_SIZE
  pbuf_remove_header(p, ETH_PAD_SIZE); /* drop the padding word */
#endif
    
    /* 从pbuf中拷贝要发送的数据 */
    for(q = p;q != NULL;q = q->next)
    {
        /* 判断此发送描述符是否有效,即判断此发送描述符是否归以太网DMA所有 */
        if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
        {
            errval = ERR_USE;
            goto error;               /* 发送描述符无效,不可用 */
        }
        
        byteslefttocopy = q->len;     /* 要发送的数据长度 */
        payloadoffset = 0; 
        
        /* 将pbuf中要发送的数据写入到以太网发送描述符中,有时候我们要发送的数据可能大于一个以太网
           描述符的Tx Buffer,因此我们需要分多次将数据拷贝到多个发送描述符中 */
        while((byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
        {
            /* 将数据拷贝到以太网发送描述符的Tx Buffer中 */
            memcpy((uint8_t*)((uint8_t*)buffer + bufferoffset),(uint8_t*)((uint8_t*)q->payload + payloadoffset),(ETH_TX_BUF_SIZE - bufferoffset));
            /* DmaTxDsc指向下一个发送描述符 */
            DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
            /* 检查新的发送描述符是否有效 */
            if((DmaTxDesc->Status&ETH_DMATXDESC_OWN) != (uint32_t)RESET)
            {
                errval = ERR_USE;
                goto error;     /* 发送描述符无效,不可用 */
            }
            
            buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr);   /* 更新buffer地址,指向新的发送描述符的Tx Buffer */
            byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
            payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
            framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
            bufferoffset = 0;
        }
        /* 拷贝剩余的数据 */
        memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset),(uint8_t*)((uint8_t*)q->payload+payloadoffset),byteslefttocopy );
        bufferoffset = bufferoffset + byteslefttocopy;
        framelength = framelength + byteslefttocopy;
    }
    
    /* 当所有要发送的数据都放进发送描述符的Tx Buffer以后就可发送此帧了 */
    HAL_ETH_TransmitFrame(&g_eth_handler,framelength);
    errval = ERR_OK;
error:            
    /* 发送缓冲区发生下溢,一旦发送缓冲区发生下溢TxDMA会进入挂起状态 */
    if((g_eth_handler.Instance->DMASR&ETH_DMASR_TUS) != (uint32_t)RESET)
    {
        /* 清除下溢标志 */
        g_eth_handler.Instance->DMASR = ETH_DMASR_TUS;
        /* 当发送帧中出现下溢错误的时候TxDMA会挂起,这时候需要向DMATPDR寄存器 */
        /* 随便写入一个值来将其唤醒,此处我们写0 */
        g_eth_handler.Instance->DMATPDR=0;
    }
    
#if ETH_PAD_SIZE
  pbuf_add_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif
    
    return errval;
}

发送的output完成后,就会通过low_level_init(netif)完成初始化,配置MAC地址,允许的最大传输字节(1500),以及ARP协议使能(用来广播MAC地址),然后使能两个DMA描述符,最后HAL_ETH_Start()开启以太网。

当然除了发送,还有接收函数,其代码逻辑与output是非常类似的,通过内存池(512大小),最大1500的数据,所以申请3个512的内存池,然后for循环去把Rx的缓冲区转移到pbuf中,代码如下:

low_level_input(struct netif *netif)
{  
    struct pbuf *p, *q;
    u16_t len;
    uint8_t *buffer;
    __IO ETH_DMADescTypeDef *dmarxdesc;
    uint32_t bufferoffset = 0;
    uint32_t payloadoffset = 0;
    uint32_t byteslefttocopy = 0;
    uint32_t i = 0;
  
    if(HAL_ETH_GetReceivedFrame(&g_eth_handler) != HAL_OK)  /* 判断是否接收到数据 */
    return NULL;
    
    len = g_eth_handler.RxFrameInfos.length;                /* 获取接收到的以太网帧长度 */
    
#if ETH_PAD_SIZE
  len += ETH_PAD_SIZE; /* allow room for Ethernet padding */
#endif
    
    buffer = (uint8_t *)g_eth_handler.RxFrameInfos.buffer;  /* 获取接收到的以太网帧的数据buffer */
  
    p = pbuf_alloc(PBUF_RAW,len,PBUF_POOL);                 /* 申请pbuf */
    
    if(p != NULL)                                           /* pbuf申请成功 */
    {
        dmarxdesc = g_eth_handler.RxFrameInfos.FSRxDesc;    /* 获取接收描述符链表中的第一个描述符 */
        bufferoffset = 0;
        
        for(q = p;q != NULL;q = q->next)
        {
            byteslefttocopy = q->len;
            payloadoffset = 0;
            
            /* 将接收描述符中Rx Buffer的数据拷贝到pbuf中 */
            while((byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE )
            {
                /* 将数据拷贝到pbuf中 */
                memcpy((uint8_t*)((uint8_t*)q->payload+payloadoffset),(uint8_t*)((uint8_t*)buffer + bufferoffset),(ETH_RX_BUF_SIZE - bufferoffset));
                 /* dmarxdesc向下一个接收描述符 */
                dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
                /* 更新buffer地址,指向新的接收描述符的Rx Buffer */
                buffer = (uint8_t *)(dmarxdesc->Buffer1Addr);
 
                byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);
                payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);
                bufferoffset = 0;
            }
            /* 拷贝剩余的数据 */
            memcpy((uint8_t*)((uint8_t*)q->payload + payloadoffset),(uint8_t*)((uint8_t*)buffer + bufferoffset),byteslefttocopy);
            bufferoffset = bufferoffset + byteslefttocopy;
        }
    }
    else
    {
        /* drop packet();  丢包函数自行编写 */
        LINK_STATS_INC(link.memerr);
        LINK_STATS_INC(link.drop);
        MIB2_STATS_NETIF_INC(netif, ifindiscards);
    }
    
    /* 释放DMA描述符 */
    dmarxdesc = g_eth_handler.RxFrameInfos.FSRxDesc;
    
    for(i = 0;i < g_eth_handler.RxFrameInfos.SegCount; i ++)
    {  
        dmarxdesc->Status |= ETH_DMARXDESC_OWN;       /* 标记描述符归DMA所有 */
        dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
    }
    
    g_eth_handler.RxFrameInfos.SegCount = 0;           /* 清除段计数器 */
    
    if((g_eth_handler.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET)  /* 接收缓冲区不可用 */
    {
        /* 清除接收缓冲区不可用标志 */
        g_eth_handler.Instance->DMASR = ETH_DMASR_RBUS;
        /* 当接收缓冲区不可用的时候RxDMA会进去挂起状态,通过向DMARPDR写入任意一个值来唤醒Rx DMA */
        g_eth_handler.Instance->DMARPDR = 0;
    }
    
    return p;
}

以上的两个数据传输的流程,可以总结成下图:
数据传输流程
以上netif全部完成之后,就会开启网卡,完成init。

全部的协议启动流程,可以如下图展示的来概括:
lwIP启动总结

2.裸机lwIP启动流程——新版本

总体流程是相似的,只不过一些小细节有所改动。
初始化的时候,PHY芯片的初始化要调用pcf8574_init(),是在pcf8574.c的文件中的。

之后的内容很类似,就是到了low_level_output()的源码有了区别,新版本是直接定义了一个数组,Tx的缓冲数据直接到定义的Txbuffer数组中,同样完成切片,但是操作起来简便了很多,如下所示:

static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
    uint32_t i = 0U;
    struct pbuf *q = NULL;
    err_t errval = ERR_OK;
    ETH_BufferTypeDef Txbuffer[ETH_TX_DESC_CNT] = {0};

    memset(Txbuffer, 0 , ETH_TX_DESC_CNT * sizeof(ETH_BufferTypeDef));

    for (q = p; q != NULL; q = q->next)
    {
        if (i >= ETH_TX_DESC_CNT)
          return ERR_IF;

        Txbuffer[i].buffer = q->payload;
        Txbuffer[i].len = q->len;

        if (i > 0)
        {
            Txbuffer[i - 1].next = &Txbuffer[i];
        }

        if (q->next == NULL)
        {
            Txbuffer[i].next = NULL;
        }

        i++;
    }

    TxConfig.Length = p->tot_len;
    TxConfig.TxBuffer = Txbuffer;
    TxConfig.pData = p;

    HAL_ETH_Transmit(&g_eth_handler, &TxConfig, ETH_DMA_TRANSMIT_TIMEOUT);

    return errval;
}

同时,在v1.27的版本中,IP地址等都需要手动设置(之前的v1.26都是可以自动设置的)。这些配置都在ethernet_chip.c中进行配置。

总体的流程仍然是一样的,就是output和input的实现方法略有不同,其他就没什么区别了。

总结

这一讲其实就是带着看看源码,如果有底层的学习需求,最好是自己F12进去看函数是怎么实现的,这个笔记也只是我自己的当前的一些理解和记录,后期真的做项目用到,移植出现了问题就会去看这些底层实现。
当然,经过后面的课程学习之后应该会更有理解,现在就是大概了解了一下。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值