LWIP——无操作系统移植

这篇具有很好参考价值的文章主要介绍了LWIP——无操作系统移植。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

移植说明

LwIP前期准备

 以太网DMA描述符

LwIP移植流程

添加网卡驱动程序

添加LwIP源文件

 移植头文件

网卡驱动编写

移植总结 


移植说明

LwIP的移植可以分为两大类:第一类是只移植内核核心,此时用户应用程序编写只能基于RaW/CallBack API进行;第二类是移植内核核心和上层API函数模块,此时用户可以使用所有三种API进行编程,即除了RaW/CallBack API外,还有Netconn API和Socket API。第一种移植比较简单,只需完成头文件的定义以及根据使用的具体网卡完成ethernetif.c中的函数(即网卡驱动)的编写;当进行第二种移植时,除了实现第一种移植的所有文件和函数之外,还必须使用操作系统提供的邮箱和信号量机制,完成操作系统模拟层文件sys_arch.c和sys_arch.h的编写。这也就是说,在进行第一种移植时,目标环境中有无操作系统是不必要的,而第二种移植则必须基于操作系统进行。

不管是哪一种移植,重点都在网卡驱动的移植上,网卡驱动是整个网络协议栈功能实现的基础,协议栈的本质是对输入、输出数据包的处理,而网卡是数据包接收和发送过程中最重要的部件。LwIP内核要求数据包封装为LwIP熟悉的格式,然后递交给内核,将上层发送的数据包解析为网卡熟悉的结构并控制网卡发送数据,主要包括:网卡初始化函数,网卡数据发送函数,网卡数据接收函数,LwIP的移植只要将这三个函数进行实现即可。

LwIP前期准备

在移植这个LwIP之前,我们需要一个基础工程,使用一个基于HAL的裸机工程进行移植,这里可以通过CubeMX,也可以手动进行创建一个工程。然后创建一个文件夹,下面存放LwIP的源码以及我们需要移植的头文件的文件夹arch以及一个应用文件夹lwip_app。

lwip移植,LWIP,网络,移植,LwIP,以太网,STM32

 以太网DMA描述符

这里涉及到ST的以太网DMA描述符的相关知识,ST以太网模块中的接收/发送FIFO和内存之间的以太网数据包传输是以太网DMA描述符完成的。。它们一共有两个描述符列表:一个用于接收,一个用于发送,这两个列表的基地址 分别写入 DMARDLAR 寄存器和 DMATDLAR 寄存器,当然这两个描述符列表支持两种链接 方式如下图所示:

lwip移植,LWIP,网络,移植,LwIP,以太网,STM32

 上图展示了 DMA 描述符的两种结构:环形结构和链接结构,在 ST 提供的以太网驱 动库 stm32f4/f7/h7xx_hal_eth.c 中初始化发送和接收描述符列表就使用的是链接结构,DMA 描 述符连接结构的具体描述如下图

lwip移植,LWIP,网络,移植,LwIP,以太网,STM32

 以太网DMA描述符代码如下:HAL库1.26和HAL库1.27版本有所不同,但这不是我们移植的重点


/** 
  * @brief  ETH DMA Descriptors data structure definition
  */ 

typedef struct  
{
  __IO uint32_t   Status;           /*!< Status */
  
  uint32_t   ControlBufferSize;     /*!< Control and Buffer1, Buffer2 lengths */
  
  uint32_t   Buffer1Addr;           /*!< Buffer1 address pointer */
  
  uint32_t   Buffer2NextDescAddr;   /*!< Buffer2 or next descriptor address pointer */
  
  /*!< Enhanced ETHERNET DMA PTP Descriptors */
  uint32_t   ExtendedStatus;        /*!< Extended status for PTP receive descriptor */
  
  uint32_t   Reserved1;             /*!< Reserved */
  
  uint32_t   TimeStampLow;          /*!< Time Stamp Low value for transmit and receive */
  
  uint32_t   TimeStampHigh;         /*!< Time Stamp High value for transmit and receive */

} ETH_DMADescTypeDef;

LwIP移植流程

添加网卡驱动程序

就如正点原子的提供的以太网驱动程序,ethernet.c和ethernet.h两个文件包含了以太网驱动初始化和MAC的驱动程序,这里需要用户自己根据自己的以太网芯片进行设置,通过设置以太网句柄ETH_HandleTypeDef进行设置。一般将ethernet.c添加到工程的Drivers/BSP分组,如下图所示那样:

lwip移植,LWIP,网络,移植,LwIP,以太网,STM32

#if (USE_HAL_ETH_REGISTER_CALLBACKS == 1)
typedef struct __ETH_HandleTypeDef
#else
typedef struct
#endif /* USE_HAL_ETH_REGISTER_CALLBACKS */
{
  ETH_TypeDef                *Instance;     /*!< Register base address       */
  
  ETH_InitTypeDef            Init;          /*!< Ethernet Init Configuration */
  
  uint32_t                   LinkStatus;    /*!< Ethernet link status        */
  
  ETH_DMADescTypeDef         *RxDesc;       /*!< Rx descriptor to Get        */
  
  ETH_DMADescTypeDef         *TxDesc;       /*!< Tx descriptor to Set        */
  
  ETH_DMARxFrameInfos        RxFrameInfos;  /*!< last Rx frame infos         */
  
  __IO HAL_ETH_StateTypeDef  State;         /*!< ETH communication state     */
  
  HAL_LockTypeDef            Lock;          /*!< ETH Lock                    */

#if (USE_HAL_ETH_REGISTER_CALLBACKS == 1)

  void    (* TxCpltCallback)     ( struct __ETH_HandleTypeDef * heth);  /*!< ETH Tx Complete Callback   */
  void    (* RxCpltCallback)     ( struct __ETH_HandleTypeDef * heth);  /*!< ETH Rx  Complete Callback   */
  void    (* DMAErrorCallback)   ( struct __ETH_HandleTypeDef * heth);  /*!< DMA Error Callback      */
  void    (* MspInitCallback)    ( struct __ETH_HandleTypeDef * heth);  /*!< ETH Msp Init callback       */
  void    (* MspDeInitCallback)  ( struct __ETH_HandleTypeDef * heth);  /*!< ETH Msp DeInit callback     */

#endif  /* USE_HAL_ETH_REGISTER_CALLBACKS */

} ETH_HandleTypeDef;

ethernet.c文件主要设计的函数如下表所示:

函数 描述
ethernet_init() 以太网芯片初始化
HAL_ETH_MspInit() ETH底层驱动,时钟使能,引脚配置
ethernet_read_phy() 读取以太网芯片寄存器值
ethernet_write_phy() 向以太网芯片指定地址写入寄存器值
ethernet_chip_get_speed() 获得网络芯片的速度模式
ETH_IRQHandler() 中断服务函数
ethernet_get_eth_rx_size() 获取接收到的帧长度
ethernet_mem_malloc() 为ETH底层驱动申请内存
ethernet_mem_free() 释放ETH底层驱动申请的内存

ethernet_init():在这个函数中,我们首先设置了MAC地址以及相关的ETH以太网配置,接着调用了HAL_ETH_Init()函数进行初始化以太网,很简单的一个函数。

HAL_ETH_MspInit:函数会被HAL_ETH_Init函数调用,该函数用来初始化以太网的GPIO,使能时钟和配置中断等,该函数代码如下:

/**
 * @brief       ETH底层驱动,时钟使能,引脚配置
 *    @note     此函数会被HAL_ETH_Init()调用
 * @param       heth:以太网句柄
 * @retval      无
 */
void HAL_ETH_MspInit(ETH_HandleTypeDef *heth)
{
    GPIO_InitTypeDef gpio_init_struct;

    ETH_CLK_GPIO_CLK_ENABLE();          /* 开启ETH_CLK时钟 */
    ETH_MDIO_GPIO_CLK_ENABLE();         /* 开启ETH_MDIO时钟 */
    ETH_CRS_GPIO_CLK_ENABLE();          /* 开启ETH_CRS时钟 */
    ETH_MDC_GPIO_CLK_ENABLE();          /* 开启ETH_MDC时钟 */
    ETH_RXD0_GPIO_CLK_ENABLE();         /* 开启ETH_RXD0时钟 */
    ETH_RXD1_GPIO_CLK_ENABLE();         /* 开启ETH_RXD1时钟 */
    ETH_TX_EN_GPIO_CLK_ENABLE();        /* 开启ETH_TX_EN时钟 */
    ETH_TXD0_GPIO_CLK_ENABLE();         /* 开启ETH_TXD0时钟 */
    ETH_TXD1_GPIO_CLK_ENABLE();         /* 开启ETH_TXD1时钟 */
    ETH_RESET_GPIO_CLK_ENABLE();        /* 开启ETH_RESET时钟 */
    __HAL_RCC_ETH_CLK_ENABLE();         /* 开启ETH时钟 */


    /* 网络引脚设置 RMII接口
     * ETH_MDIO -------------------------> PA2
     * ETH_MDC --------------------------> PC1
     * ETH_RMII_REF_CLK------------------> PA1
     * ETH_RMII_CRS_DV ------------------> PA7
     * ETH_RMII_RXD0 --------------------> PC4
     * ETH_RMII_RXD1 --------------------> PC5
     * ETH_RMII_TX_EN -------------------> PG11
     * ETH_RMII_TXD0 --------------------> PG13
     * ETH_RMII_TXD1 --------------------> PG14
     * ETH_RESET-------------------------> PD3
     */

    /* PA1,2,7 */
    gpio_init_struct.Pin = ETH_CLK_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;                /* 推挽复用 */
    gpio_init_struct.Pull = GPIO_NOPULL;                    /* 不带上下拉 */
    gpio_init_struct.Speed = GPIO_SPEED_HIGH;               /* 高速 */
    gpio_init_struct.Alternate = GPIO_AF11_ETH;             /* 复用为ETH功能 */
    HAL_GPIO_Init(ETH_CLK_GPIO_PORT, &gpio_init_struct);    /* ETH_CLK引脚模式设置 */
    
    gpio_init_struct.Pin = ETH_MDIO_GPIO_PIN;
    HAL_GPIO_Init(ETH_MDIO_GPIO_PORT, &gpio_init_struct);   /* ETH_MDIO引脚模式设置 */
    
    gpio_init_struct.Pin = ETH_CRS_GPIO_PIN;    
    HAL_GPIO_Init(ETH_CRS_GPIO_PORT, &gpio_init_struct);    /* ETH_CRS引脚模式设置 */

    /* PC1 */
    gpio_init_struct.Pin = ETH_MDC_GPIO_PIN;
    HAL_GPIO_Init(ETH_MDC_GPIO_PORT, &gpio_init_struct);    /* ETH_MDC初始化 */

    /* PC4 */
    gpio_init_struct.Pin = ETH_RXD0_GPIO_PIN;
    HAL_GPIO_Init(ETH_RXD0_GPIO_PORT, &gpio_init_struct);   /* ETH_RXD0初始化 */
    
    /* PC5 */
    gpio_init_struct.Pin = ETH_RXD1_GPIO_PIN;
    HAL_GPIO_Init(ETH_RXD1_GPIO_PORT, &gpio_init_struct);   /* ETH_RXD1初始化 */
    
    
    /* PG11,13,14 */
    gpio_init_struct.Pin = ETH_TX_EN_GPIO_PIN; 
    HAL_GPIO_Init(ETH_TX_EN_GPIO_PORT, &gpio_init_struct);  /* ETH_TX_EN初始化 */

    gpio_init_struct.Pin = ETH_TXD0_GPIO_PIN; 
    HAL_GPIO_Init(ETH_TXD0_GPIO_PORT, &gpio_init_struct);   /* ETH_TXD0初始化 */
    
    gpio_init_struct.Pin = ETH_TXD1_GPIO_PIN; 
    HAL_GPIO_Init(ETH_TXD1_GPIO_PORT, &gpio_init_struct);   /* ETH_TXD1初始化 */
    
    
    /* 复位引脚 */
    gpio_init_struct.Pin = ETH_RESET_GPIO_PIN;      /* ETH_RESET初始化 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;    /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_NOPULL;            /* 无上下拉 */
    gpio_init_struct.Speed = GPIO_SPEED_HIGH;       /* 高速 */
    HAL_GPIO_Init(ETH_RESET_GPIO_PORT, &gpio_init_struct);

    ETHERNET_RST(0);     /* 硬件复位 */
    delay_ms(50);
    ETHERNET_RST(1);     /* 复位结束 */

    HAL_NVIC_SetPriority(ETH_IRQn, 1, 0);           /* 网络中断优先级应该高一点 */
    HAL_NVIC_EnableIRQ(ETH_IRQn);
}

这个函数在HAL库函数里有个同名的弱定义函数,当用户自己定义了同名的函数时,将会默认调用用户自己编写的代码。这里编写和具体的MCU相关的初始化,初始化了以太网的引脚和设置以太网中断的优先级,最后硬件复位一下PHY芯片。

/**
  * @brief  Initializes the ETH MSP.
  * @param  heth pointer to a ETH_HandleTypeDef structure that contains
  *         the configuration information for ETHERNET module
  * @retval None
  */
__weak void HAL_ETH_MspInit(ETH_HandleTypeDef *heth)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(heth);
  /* NOTE : This function Should not be modified, when the callback is needed,
  the HAL_ETH_MspInit could be implemented in the user file
  */
}

接下来就是介绍ethernet_read_phyethernet_write_phy这两个函数,这两个函数用来读取和配置PHY芯片内部寄存器的,其实它们就是对HAL库的HAL_ETH_ReadPHYRegister和HAL_ETH_WritePHYRegister做了一个简单的封装。

如下是函数的具体信息:

/**
 * @breif       读取以太网芯片寄存器值
 * @param       reg:读取的寄存器地址
 * @retval      无
 */
uint32_t ethernet_read_phy(uint16_t reg)
{
    uint32_t regval;

    HAL_ETH_ReadPHYRegister(&g_eth_handler, reg, &regval);
    return regval;
}

/**
 * @breif       向以太网芯片指定地址写入寄存器值
 * @param       reg   : 要写入的寄存器
 * @param       value : 要写入的寄存器
 * @retval      无
 */
void ethernet_write_phy(uint16_t reg, uint16_t value)
{
    uint32_t temp = value;
    
    HAL_ETH_WritePHYRegister(&g_eth_handler, reg, &temp);
}

ethernet_chip_get_speed():函数用来获取网络的速度和双工状态,该函数代码如下:

/**
 * @breif       获得网络芯片的速度模式
 * @param       无
 * @retval      1:获取100M成功
                0:失败
 */
uint8_t ethernet_chip_get_speed(void)
{
    uint8_t speed;
    #if(PHY_TYPE == LAN8720) 
    speed = ~((ethernet_read_phy(PHY_SR) & PHY_SPEED_STATUS));         /* 从LAN8720的31号寄存器中读取网络速度和双工模式 */
    #elif(PHY_TYPE == SR8201F)
    speed = ((ethernet_read_phy(PHY_SR) & PHY_SPEED_STATUS) >> 13);    /* 从SR8201F的0号寄存器中读取网络速度和双工模式 */
    #elif(PHY_TYPE == YT8512C)
    speed = ((ethernet_read_phy(PHY_SR) & PHY_SPEED_STATUS) >> 14);    /* 从YT8512C的17号寄存器中读取网络速度和双工模式 */
    #elif(PHY_TYPE == RTL8201)
    speed = ((ethernet_read_phy(PHY_SR) & PHY_SPEED_STATUS) >> 1);     /* 从RTL8201的16号寄存器中读取网络速度和双工模式 */
    #endif

    return speed;
}

ETH_IRQHandler函数为以太网DMA接收中断服务函数,中断服务函数代码如下:

在中断服务函数里,我们通过判断接收到的数据包长度是否为0,当接收到的数据包长度不为0时,程序就调用lwip_pkt_handle函数处理接收到的数据包,这个函数在下面有具体的说明,处理完成后清除相应的中断标志位,DMA接收中断标志位和DMA总中断标志位。

/**
 * @breif       中断服务函数
 * @param       无
 * @retval      无
 */
void ETH_IRQHandler(void)
{
    if (ethernet_get_eth_rx_size(g_eth_handler.RxDesc))
    {
        lwip_pkt_handle();      /* 处理以太网数据,即将数据提交给LWIP */
    }

    __HAL_ETH_DMA_CLEAR_IT(&g_eth_handler, ETH_DMA_IT_NIS);   /* 清除DMA中断标志位 */
    __HAL_ETH_DMA_CLEAR_IT(&g_eth_handler, ETH_DMA_IT_R);     /* 清除DMA接收中断标志位 */
}


/**
 * @breif       当接收到数据后调用
 * @param       无
 * @retval      无
 */
void lwip_pkt_handle(void)
{
    /* 从网络缓冲区中读取接收到的数据包并将其发送给LWIP处理 */
    ethernetif_input(&g_lwip_netif);
}

ethernet_get_eth_rx_size函数用来获取当前接收到的以太网帧长度,该函数代码如下:

/**
 * @breif       获取接收到的帧长度
 * @param       dma_rx_desc : 接收DMA描述符
 * @retval      frameLength : 接收到的帧长度
 */
uint32_t  ethernet_get_eth_rx_size(ETH_DMADescTypeDef *dma_rx_desc)
{
    uint32_t frameLength = 0;

    if (((dma_rx_desc->Status & ETH_DMARXDESC_OWN) == (uint32_t)RESET) &&
        ((dma_rx_desc->Status & ETH_DMARXDESC_ES)  == (uint32_t)RESET) &&
        ((dma_rx_desc->Status & ETH_DMARXDESC_LS)  != (uint32_t)RESET))
    {
        frameLength = ((dma_rx_desc->Status & ETH_DMARXDESC_FL) >> ETH_DMARXDESC_FRAME_LENGTHSHIFT);
    }

    return frameLength;
}

ethernet_mem_malloc 函数就是为我们前面提到的那四个指针内存分配,函数代码如下:

/**
* @breif 为 ETH 底层驱动申请内存
* @param 无
* @retval 0,正常
* 1,失败
*/
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_mem_free 函数为释放内存函数,功能是将 g_eth_rx_buf、g_eth_tx_buf、g_eth_dma_rx_dscr_tab 和 g_eth_dma_tx_dscr_tab 这四个指针的内存释放掉。

添加LwIP源文件

把LwIP源码下面的Src文件夹整个复制到工程中,其中该文件主要涉及的文件如下:

lwip移植,LWIP,网络,移植,LwIP,以太网,STM32

其中移植只需要关注api、core、include、netif即可,apps我们不需要关心,随后在工程中添加对应结构的分组,如api、core、netif:其中在添加core源码时只需添加IPV4文件夹下的源码即可,不用添加IPV6相应的源码。netif中其实这里也只需添加ethernet.c文件即可,你全部添加也可以。
lwip移植,LWIP,网络,移植,LwIP,以太网,STM32

 移植头文件

根据早期版本的LwIP它有一个sys_arch.txt文档,描述了一些移植工作的说明,但是2.1.3版本,我没看到这个文件,这里需要添加lwipopts.h和cc.h和pref.h三个头文件:

lwipopts.h:对LwIP内核进行裁剪配置的头文件

cc.h:为了进行平台无关性的移植,这个文件配置了相应的数据类型,这里你可以针对你使用的硬件资源是32位的还是16位进行相应的数据类型配置,这样LwIP就不会关注于具体的平台,用户可以结合自身情况进行设置。

pref.h:该文件是一个统计和测量资源有关的头文件,这里其实没有用到,你不配置也是可以的。

网卡驱动编写

上面提到的BSP里面的ethernet.c文件是针对具体的PHY芯片进行的配置等,而这里正点原子写了一个和PHY芯片无关的一些初始化单独写在另一个文件中,这里类似于HAL库写具体的协议如IIC时,它把和硬件MCU相关的写在MspInit里面,通用的协议初始化时序代码,写在另一个函数中,因为接口IIC时序协议是通用的,和硬件是无关的,你其实写在一个文件里也是可以的。

LwIP网卡驱动的移植本质上就是实现网卡初始化、网卡发送函数,网卡接收函数三个主要函数即可,网卡驱动相关的函数在ethernetif.c中完成,LwIP源码提供者将ethernetif.c中的函数实现为一个框架形式,移植者只需要根据实际使用的网卡特性完善这些函数即可。总体来说,在文件ethernetif.c中已经有5个函数的框架,包括函数名,函数参数,函数内容等。我们要做的就是完成这5个函数的编写

LwIP文件中给出的5个函数为:

lwip移植,LWIP,网络,移植,LwIP,以太网,STM32

 这5个函数只有后3个和网卡功能密切相关。

low_level_init:为网卡初始化函数,它主要用来完成网卡复位及参数初始化,同时根据网卡特性,设置协议栈网络接口管理结构netif(虚拟网卡)中与网卡属性相关的字段,例如MAC地址长度;

low_level_output:为网卡数据包发送函数,这个函数的工作是将内核数据结构pbuf描述的数据包发送出去

low_level_input:为网卡数据包接收函数,同样为了能让协议栈内核准确的处理数据,该函数必须将收到的数据封装为pbuf形式.数据包接收分为查询接收数据包和中断方式接收数据包。

ethernet_input:主要是调用网卡数据包接收函数low_level_input函数从网卡处读取一个数据包,然后解析该数据包的类型(ARP包或IP包),最后将该数据包递交给相应的上层,该函数已经可以直接使用,调用一次完成一次数据包的接收和递交。

ethernet_init:是上层在管理网络接口结构netif时会调用的函数。该函数主要是完成netif结构中某些字段的初始化,并最终调用low_evel_init完成网卡的初始化。ethernet_init函数是可以直接使用的函数,可以不进行任何改写。

low_level_init函数

static void
low_level_init(struct netif *netif)
{
    netif->hwaddr_len = ETHARP_HWADDR_LEN; /* 设置MAC地址长度,为6个字节 */
    /* 初始化MAC地址,设置什么地址由用户自己设置,但是不能与网络中其他设备MAC地址重复 */
    netif->hwaddr[0] = g_lwipdev.mac[0]; 
    netif->hwaddr[1] = g_lwipdev.mac[1]; 
    netif->hwaddr[2] = g_lwipdev.mac[2];
    netif->hwaddr[3] = g_lwipdev.mac[3];   
    netif->hwaddr[4] = g_lwipdev.mac[4];
    netif->hwaddr[5] = g_lwipdev.mac[5];
    
    netif->mtu = 1500; /* 最大允许传输单元,允许该网卡广播和ARP功能 */

    /* 网卡状态信息标志位,是很重要的控制字段,它包括网卡功能使能、广播 */
    /* 使能、 ARP 使能等等重要控制位 */
    netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;   /* 广播 ARP协议 链接检测 */
    
    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_ETH_Start(&g_eth_handler); /* 开启ETH */
}

网络接口描述结构netif是协议栈内核对系统网络接口设备进行管理的重要数据结构,内核会为每个网络接口分配一个netif 结构,以描述对应接口的属性。在上面这个函数中初始化了netif 结构的四个字段: hwaddr_ len、 hwaddr、 mtu和flags.

low_level_output

lwip移植,LWIP,网络,移植,LwIP,以太网,STM32

lwip移植,LWIP,网络,移植,LwIP,以太网,STM32

 到这里,就已经完成了网卡驱动的编写

协议栈初始化

在使用协议前,协议栈内核必须先初始化完毕,若要使用网卡进行通信,则网卡相关的网络接口结构也需要被注册到内核中。这里正点原子用一个lwip_comm.c文件实现,说是LwIP源码和以太网驱动结合的桥梁,其实就是协议栈的初始化。

lwip_comm_init: 该函数主要对以太网的 IO 初始化以及添加在网卡列表中添加一个网口。

/**
 * @breif       LWIP初始化(LWIP启动的时候使用)
 * @param       无
 * @retval      0,成功
 *              1,内存错误
 *              2,以太网芯片初始化失败
 *              3,网卡添加失败.
 */
uint8_t lwip_comm_init(void)
{
    uint8_t retry = 0;
    struct netif *netif_init_flag;              /* 调用netif_add()函数时的返回值,用于判断网络初始化是否成功 */
    ip_addr_t ipaddr;                           /* ip地址 */
    ip_addr_t netmask;                          /* 子网掩码 */
    ip_addr_t gw;                               /* 默认网关 */

    if (ethernet_mem_malloc())return 1;         /* 内存申请失败*/

    lwip_comm_default_ip_set(&g_lwipdev);       /* 设置默认IP等信息 */

    while (ethernet_init())                     /* 初始化以太网芯片,如果失败的话就重试5次 */
    {
        retry++;

        if (retry > 5)
        {
            retry = 0;                          /* 以太网芯片初始化失败 */
            return 3;
        }
    }

    lwip_init();                                /* 初始化LWIP内核 */

#if LWIP_DHCP                                   /* 使用动态IP */
    ipaddr.addr = 0;
    netmask.addr = 0;
    gw.addr = 0;
#else   /* 使用静态IP */
    IP4_ADDR(&ipaddr, g_lwipdev.ip[0], g_lwipdev.ip[1], g_lwipdev.ip[2], g_lwipdev.ip[3]);
    IP4_ADDR(&netmask, g_lwipdev.netmask[0], g_lwipdev.netmask[1], g_lwipdev.netmask[2], g_lwipdev.netmask[3]);
    IP4_ADDR(&gw, g_lwipdev.gateway[0], g_lwipdev.gateway[1], g_lwipdev.gateway[2], g_lwipdev.gateway[3]);
    printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n", g_lwipdev.mac[0], g_lwipdev.mac[1], g_lwipdev.mac[2], g_lwipdev.mac[3], g_lwipdev.mac[4], g_lwipdev.mac[5]);
    printf("静态IP地址........................%d.%d.%d.%d\r\n", g_lwipdev.ip[0], g_lwipdev.ip[1], g_lwipdev.ip[2], g_lwipdev.ip[3]);
    printf("子网掩码..........................%d.%d.%d.%d\r\n", g_lwipdev.netmask[0], g_lwipdev.netmask[1], g_lwipdev.netmask[2], g_lwipdev.netmask[3]);
    printf("默认网关..........................%d.%d.%d.%d\r\n", g_lwipdev.gateway[0], g_lwipdev.gateway[1], g_lwipdev.gateway[2], g_lwipdev.gateway[3]);
#endif  /* 向网卡列表中添加一个网口 */
    netif_init_flag = netif_add(&g_lwip_netif, (const ip_addr_t *)&ipaddr, (const ip_addr_t *)&netmask, (const ip_addr_t *)&gw, NULL, &ethernetif_init, &ethernet_input);


    if (netif_init_flag == NULL)
    {
        return 4;                             /* 网卡添加失败 */
    }
    else                                      /* 网口添加成功后,设置netif为默认值,并且打开netif网口 */
    {
        netif_set_default(&g_lwip_netif);     /* 设置netif为默认网口 */

        if (netif_is_link_up(&g_lwip_netif))
        {
            netif_set_up(&g_lwip_netif);      /* 打开netif网口 */
        }
        else
        {
            netif_set_down(&g_lwip_netif);
        }
    }

#if LWIP_DHCP                               /* 如果使用DHCP的话 */
    g_lwipdev.dhcpstatus = 0;                 /* DHCP标记为0 */
    dhcp_start(&g_lwip_netif);                /* 开启DHCP服务 */
#endif
    return 0;                               /* 操作OK. */
}

此函数调用 ethernet_mem_malloc 申请 DMA 描述符的内存,其次我们调用 lwip_comm_default_ip_set 函数设置网络参数,然后调 用函数 ethernet_init 初始化以太网 IO 并且初始化 lwIP 内核,最后我们调用函数 netif_add 向网 卡列表添加一个网口,该函数比较特殊,它的第二到第四个形参传入本地 IP 地址、子网掩码 以及网关,而第五和第六形参需要用户传入 ethernetif_init 以及 ethernet_input 函数地址。

lwip_comm_default_ip_set :此函数非常简单,主要设置网络的信息,比如MAC地址,IP地址,网关和子网掩码。

lwip_pkt_handle :这个函数间接调用 ethernetif_input 函数,该函数在裸机移植时,一般放在 ETH 中断当中。

lwip_periodic_handle : 该函数与 DHCP 相关,如果工程不启用 DHCP,则系统只调用 sys_check_timeouts 检测超 时;如果工程启用 DHCP,则系统会根据 DHCP 响应时间内来获取动态 IP 地址等网络数据。

 lwip_dhcp_process_handle : 该函数与 DHCP 相关,如果工程不启用 DHCP,则默认使用 lwip_comm_default_ip_set 函 数的网络信息;如果工程启用 DHCP,则获取 DHCP 服务器相应的网络信息。

移植总结 

lwip移植,LWIP,网络,移植,LwIP,以太网,STM32文章来源地址https://www.toymoban.com/news/detail-650503.html

到了这里,关于LWIP——无操作系统移植的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • LwIP 协议栈移植教程

            官网地址:lwIP - A Lightweight TCP/IP stack - Summary [Savannah]         按照官网的描述:lwIP is a small independent implementation of the TCP/IP protocol suite that has been initially developed by Adam Dunkels and is now continued here.(lwIP是TCP/IP协议套件的一个小型独立实现,最初由Adam Dunkels开发,现在在

    2024年02月14日
    浏览(29)
  • STM32 CubeMX LwIP + freertOS 移植

    开发板: 官方 STM32F746  MCU型号:STM32F746NGH 网卡型号:LAN8742A  原理图如下 先用裸机测试LAN8742A的网卡驱动 使用CubeMX创建工程 系统时钟和时基定时器如下 无系统LWIP协议栈设置,静态IP地址,关闭DHCP 生产代码,下载进开发板。开发板网口与电脑网口通过网线直连,设置电脑本

    2024年01月16日
    浏览(72)
  • LwIP系列(3):以太网帧、IP、TCP、UDP、IGMP、ICMP帧格式详解

    TCP/IP 本质上是软件协议,而LwIP也是对软件协议进行解析处理,所以我们有必要了解下以太网帧、IP、TCP、UDP、IGMP、ICMP帧格式,这样在代码中,才能有的放矢。 以太网帧是最底层的原始数据,帧框架如下图所示: 其中【前同步码】和【以太网尾部】我们在抓包的时候,并不

    2024年02月14日
    浏览(36)
  • 关于STM32F4和GD32F4以太网,LAN8720+lwip+freemodbus,实现modbus tcp

    关于STM32F4和GD32F4以太网,LAN8720+lwip+freemodbus 这里使用了大佬 小灰灰搞电子 的代码,文章看 STM32F407+LAN8720移植Lwip和freeModbus实现MODBUS TCP 代码看 STM32F407+LAN8720+LWIP移植freemodbus TCP.zip 他的代码是基于正点原子F407的板子开发的,如果是别的板子,需要修改引脚 小灰灰的代码里,没

    2024年02月14日
    浏览(26)
  • 2.物联网LWIP网络

    注意:学习路径 1.如果想要深入了解,学习LWIP的ARM API(对底层操作) 2.如果想要快速开发,学习LWIP的Socket API(针对操作系统) 一。LWIP协议栈 (1)LWIP应用场景          上述是以太网DTU,通过这个器件可以很快捷的把数据传递到以太网上,即DTU就是硬件与以太网传递的中

    2024年02月13日
    浏览(24)
  • 【lwIP(第四章)】网络接口

    lwIP协议栈支持多种不同的网络接口(网卡),由于网卡是直接和硬件平台打交道,硬件不同则处理也是不同的,所以由用户提供最底层的接口函数,lwIP提供统一的接口,但是底层的实现需要用户自己去完成(ethernetif.c) 一个系统中可能有多个网络接口,有可能是以太网,有

    2023年04月22日
    浏览(27)
  • 【LWIP】stm32用CubeMX(6.4版)配置LwIP+Ping+TCPclient+TCPserver发送信息到PC(操作部分)

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 这是我写的第一篇博客,欢迎大家给点鼓励和提出建议! 本人由于理想和爱好,辞去土木工作,于不到一个月前入职某科技公司开始从事嵌入式,专业能力和刚毕业的大学生一样都是很薄弱的。然后被

    2024年02月03日
    浏览(24)
  • 1.物联网LWIP网络,TCP/IP协议簇

    一。TCP/IP协议簇 1.应用层:FTP,HTTP,Telent,DNS,RIP 2.传输层:TCP,UDP 3.网络层:IPV4,IPV6,OSPF,EIGRP 4.数据链路层:Ethernet,FrameRelay,Is-Is 注意:IWIP是完全按照TCP/IP协议来创建 2.OSI七层模型与TCP/IP协议模型 3.通讯过程 应用层:FTP协议 传输层:TCP协议 网络层:IP协议 链路层:

    2024年02月11日
    浏览(28)
  • RT-Thread 组件-网络组件-Lwip UDP说明①

    1.1 协议简介 UDP (User Datagram Protocol):用户数据报协议,是一种简单、无连接、不可靠的传输协议。 无需建立连接、没有提供任何流量控制、拥塞控制机制,收到的报文也没有确认,因此 UDP 的传输速度快,但不能保证数据到达目的地。 与我们熟知的 TCP 协议一样,都属于 OS

    2024年02月22日
    浏览(31)
  • GD32F470之网络lwip+UDP配置+lan8720芯片

    先申明,本栏目用的都是GD32F470芯片240M,软件用的是keil,编写用的是C++(其实和C没有区别). 和STM32的lwip配置大致一样,主要不一样的地方在于 PHY的配置顺序问题,下面会讲到. 我用的是lan8720,所以头文件要修改一下,在gd32f4xx_enet.h中。 把PHY_TYPE改为LAN8700, PHY_ADDRESS改为0, 这是

    2023年04月09日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包