目录
移植说明
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_phy和ethernet_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, ®val); 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, ðernetif_init, ðernet_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 服务器相应的网络信息。文章来源:https://www.toymoban.com/news/detail-650503.html
移植总结
文章来源地址https://www.toymoban.com/news/detail-650503.html
到了这里,关于LWIP——无操作系统移植的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!