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。

 以太网DMA描述符

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

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

 以太网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分组,如下图所示那样:

#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文件夹整个复制到工程中,其中该文件主要涉及的文件如下:

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

 移植头文件

根据早期版本的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个函数为:

 这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_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 服务器相应的网络信息。

移植总结 

  • 6
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
lwip是一个轻量级的网络通讯协议栈,用于嵌入式系统中实现TCP/IP通讯。而IAR是一款嵌入式开发工具,用于编译和调试嵌入式系统的程序。要在没有操作系统的嵌入式系统上移植lwip,可以通过以下步骤实现: 首先,需要将lwip的源码加入到项目中,并进行必要的配置。lwip的源码通常会包括TCP/IP协议栈的实现,以及与操作系统、硬件相关的部分。因为目标系统没有操作系统,所以需要根据实际情况修改lwip的配置,包括内存管理、任务调度等相关设置。 其次,需要根据目标硬件编写lwip的底层驱动程序,以适配目标系统的网卡或者以太网控制器。这部分工作涉及到硬件接口的调试和适配,需要对目标系统的硬件结构和寄存器进行深入了解。 接着,需要在IAR开发环境中编写应用程序,包括实现网络通讯的功能,并与lwip协议栈进行集成。这部分工作包括网络套接字的编程,数据包的发送和接收等。 最后,需要进行调试和测试工作,确保lwip在目标系统上能够正常工作,实现基本的网络通讯功能。这其中可能需要借助IAR提供的调试工具,对程序进行逐步调试和性能优化。 通过以上步骤,就可以在没有操作系统的嵌入式系统上成功移植lwip,并利用IAR进行开发和调试。这样就能够在嵌入式系统中实现基于TCP/IP协议的网络通讯功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值