8. 无操作系统移植LwIP

本章开始正式进入LwIP移植的学习,在前面的章节中,都是打基础的部分,俗话说“基础不牢,地动山摇”,我们只有在叫了解LwIP的时候才开始移植,这样子会更加加深我们的印象。

LwIP的官方源代码中并没有提供对任何芯片的底层驱动移植程序,因此这部分程序需要由开发者自己完成,但是在i.MX RT系列的SDK组件中也已经帮我们把底层驱动部分写好,我们可以直接拿过来用即可。

8.1. 将LwIP添加到裸机工程

首先我们在i.MX RT的固件库中得到的裸机工程作为工程模板,这里我们选取比较简单的例程—“GPIO输出—使用固件库点亮LED”作为裸机工程模板。该裸机工程模板均可以在对应板子的野火【i.MX RT1052开发板】光盘资料1-野火开源图书合集【优先学习】[野火]《i.MX RT库开发实战指南》系列1-书籍配套例程目录下获取到,将工程名字改为“LwIP移植”;

然后再将官方SDK中的LwIP源码拿过来,但是LwIP源码太大了,我们不需要那么多东西, 所以我们只需要将LwIP源码中的src文件文件夹添加进去即可,其中src文件夹是源码, port文件夹是官方写好的底层驱动部分。具体见 图7_1,但是我发现src表示源码, 但是不够明显,无法一眼就知道它是LwIP中的源码,那我们直接把文件夹重命名为“LwIP”, 这样子就能让别人一目了然知道文件夹是什么内容了,具体见 图7_2

图 7‑1将LwIP源码添加到工程中

图 7‑1将LwIP源码添加到工程中

图 7‑2 将src文件夹重命名为LwIP

图 7‑2 将src文件夹重命名为LwIP

在前面的章节中也讲解了LwIP源码文件夹的功能及作用,那么我们现在直接添加到工程分组中即可, 首先我们在工程中创建4个与LwIP相关的工程分组,其中三个分别用于存放LwIP源码中的三个文件,其对应关系具体见 图7_3

图 7‑3工程中创建分组存放LwIP源码

图 7‑3工程中创建分组存放LwIP源码

每个分组中都添加对应文件夹下的源码,添加完成后的源码具体见 图7_4图7_5图7_6图7_7

图 7‑4

图 7‑4

图 7‑5

图 7‑5

图 7‑6

图 7‑6

图 7‑7

图 7‑7

然后在工程中添加LwIP头文件路径,并且把C99模式也勾选上,添加完成的示意图具体见 图7_8

图 7‑8

图 7‑8在工程中添加LwIP头文件路径

8.2. 移植头文件

经过上面的步骤,我们的工程基本就添加完成,但是要想LwIP跑起来,还需一些头文件的支持,分别是lwipopts.h、cc.h、pref.h等。 lwipopts.h就是用于配置LwIP的相关参数的,一般来说LwIP默认会有参数的配置,存放在opt.h文件中,如果用户没有在lwipopts.h文件进行配置,那么LwIP就会使用opt.h默认的参数,注意,在移植的时候出现定义某些参数是非常重要的,这对我们LwIP的性能至关重要,甚至在配置错误的时候能直接导致LwIP的运行崩溃,下面一起来看看lwipopts.h文件的内容,这个文件的内容是由我们自己添加进来的,这里仅仅是介绍一部分配置。

我们先在工程的user\bsp文件夹下面新建一个arch文件夹,用于存放与底层接口相关的文件,然后打开LwIP的contrib包,把路径“contrib\examples\example_app”下的lwipopts.h文件拷贝到arch文件夹中;为了方便我们直接把野火【i.MX RT1052开发板】光盘资料\5-NXP官方资料\2-NXP官方SDK\MIMXRT1052xxxxB\middleware\lwip\port\arch

目录下的cc.h、pref.h、sys_arch.h文件中直接复制过来,并且将工程的头文件路径添加进去,这样子就能对LwIP进行配置了。具体见 图7_9.

图 7‑9 arch的文件

图 7‑9 arch的文件

然后我们打开lwipopts.h,并在文件内部写入以下配置,具体见 代码清单7_1

代码清单 7‑1lwipopts.h文件内容

 #ifndef __LWIPOPTS_H__
 #define __LWIPOPTS_H__

 #if USE_RTOS

 /**
 * SYS_LIGHTWEIGHT_PROT==1: if you want inter-task protection for certain
 * critical regions during buffer allocation, deallocation and memory
 * allocation and deallocation.
 */
 #define SYS_LIGHTWEIGHT_PROT 1

 /**
 * NO_SYS==0: Use RTOS
 */
 #define NO_SYS 0    (1)
 /**
 * LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
 */
 #define LWIP_NETCONN 1
 /**
 * LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
 */
 #define LWIP_SOCKET 1

 /**
 * LWIP_SO_RCVTIMEO==1: Enable receive timeout for sockets/netconns and
 * SO_RCVTIMEO processing.
 */
 #define LWIP_SO_RCVTIMEO 1

 #else
 /**
 * NO_SYS==1: Bare metal lwIP
 */
 #define NO_SYS 1
 /**
 * LWIP_NETCONN==0: Disable Netconn API (require to use api_lib.c)
 */
 #define LWIP_NETCONN 0 (2)
 /**
 * LWIP_SOCKET==0: Disable Socket API (require to use sockets.c)
 */
 #define LWIP_SOCKET 0 (3)

 #endif
 /* ---------- Memory options ---------- */
 /**
 * MEM_ALIGNMENT: should be set to the alignment of the CPU
 *    4 byte alignment -> #define MEM_ALIGNMENT 4
 *    2 byte alignment -> #define MEM_ALIGNMENT 2
 */
 #ifndef MEM_ALIGNMENT
 #define MEM_ALIGNMENT 4     (4)
 #endif

 /**
 * MEM_SIZE: the size of the heap memory. If the application will send
 * a lot of data that needs to be copied, this should be set high.
 */
 #ifndef MEM_SIZE
 #define MEM_SIZE (22 * 1024)      (5)
 #endif

 /* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
     sends a lot of data out of ROM (or other static memory), this
     should be set high. */
 #ifndef MEMP_NUM_PBUF
 #define MEMP_NUM_PBUF 15
 #endif
 /* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
     per active UDP "connection". */
 #ifndef MEMP_NUM_UDP_PCB
 #define MEMP_NUM_UDP_PCB 6
 #endif
 /* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP
     connections. */
 #ifndef MEMP_NUM_TCP_PCB
 #define MEMP_NUM_TCP_PCB 10
 #endif
 /* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP
     connections. */
 #ifndef MEMP_NUM_TCP_PCB_LISTEN
 #define MEMP_NUM_TCP_PCB_LISTEN 6
 #endif
 /* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP
     segments. */
 #ifndef MEMP_NUM_TCP_SEG
 #define MEMP_NUM_TCP_SEG 22
 #endif
 /* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active
 timeouts. */
 #ifndef MEMP_NUM_SYS_TIMEOUT
 #define MEMP_NUM_SYS_TIMEOUT 10
 #endif

 /* ---------- Pbuf options ---------- */
 /* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */
 #ifndef PBUF_POOL_SIZE
 #define PBUF_POOL_SIZE 9    (6)
 #endif

 #define PBUF_POOL_BUFSIZE   500     (7)

 /* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */
 /* Default value is defined in lwip\src\include\lwip\opt.h as
 *
 LWIP_MEM_ALIGN_SIZE(TCP_MSS+40+PBUF_LINK_ENCAPSULATION_HLEN+PBUF_LINK_HLEN)*/

 /* ---------- TCP options ---------- */
 #ifndef LWIP_TCP
 #define LWIP_TCP 1
 #endif

 #ifndef TCP_TTL
 #define TCP_TTL 255
 #endif

 /* Controls if TCP should queue segments that arrive out of
     order. Define to 0 if your device is low on memory. */
 #ifndef TCP_QUEUE_OOSEQ
 #define TCP_QUEUE_OOSEQ 0
 #endif

 /* TCP Maximum segment size. */
 #ifndef TCP_MSS
 #define TCP_MSS (1500 - 40)   (8)
 #endif

 /* TCP sender buffer space (bytes). */
 #ifndef TCP_SND_BUF
 #define TCP_SND_BUF (6 * TCP_MSS)  (9)
 #endif

 /* TCP sender buffer space (pbufs). This must be at least = 2 *
     TCP_SND_BUF/TCP_MSS for things to work. */
 #ifndef TCP_SND_QUEUELEN
 #define TCP_SND_QUEUELEN (3 * TCP_SND_BUF) / TCP_MSS // 6
 #endif

 /* TCP receive window. */
 #ifndef TCP_WND
 #define TCP_WND (2 * TCP_MSS)   (10)
 #endif

 /* Enable backlog*/
 #ifndef TCP_LISTEN_BACKLOG
 #define TCP_LISTEN_BACKLOG 1
 #endif

 /* ---------- Network Interfaces options ---------- */
 /* Support netif api (in netifapi.c). */
 #ifndef LWIP_NETIF_API
 #define LWIP_NETIF_API 1
 #endif

 /* ---------- ICMP options ---------- */
 #ifndef LWIP_ICMP
 #define LWIP_ICMP 1
 #endif

 /* ---------- RAW options ---------- */
 #if !defined LWIP_RAW
 #define LWIP_RAW 1
 #endif

 /* ---------- DHCP options ---------- */
 /* Define LWIP_DHCP to 1 if you want DHCP configuration of
     interfaces. DHCP is not implemented in lwIP 0.5.1, however, so
     turning this on does currently not work. */
 #ifndef LWIP_DHCP
 #define LWIP_DHCP 1
 #endif

 /* ---------- UDP options ---------- */
 #ifndef LWIP_UDP
 #define LWIP_UDP 1
 #endif
 #ifndef UDP_TTL
 #define UDP_TTL 255
 #endif

 /* ---------- Statistics options ---------- */
 #ifndef LWIP_STATS
 #define LWIP_STATS 0
 #endif
 #ifndef LWIP_PROVIDE_ERRNO
 #define LWIP_PROVIDE_ERRNO 1
 #endif

 /*
     --------------------------------------
     ---------- Checksum options ----------
     --------------------------------------
 */

 /*
 Some MCU allow computing and verifying the IP, UDP, TCP and ICMP checksums by hardware:
 - To use this feature let the following define uncommented.
 - To disable it and process by CPU comment the  the checksum.
 */
 //#define CHECKSUM_BY_HARDWARE

 #ifdef CHECKSUM_BY_HARDWARE
 /* CHECKSUM_GEN_IP==0: Generate checksums by hardware for outgoing IP packets.*/
 #define CHECKSUM_GEN_IP 0
 /* CHECKSUM_GEN_UDP==0: Generate checksums by hardware for outgoing UDP packets.*/
 #define CHECKSUM_GEN_UDP 0
 /* CHECKSUM_GEN_TCP==0: Generate checksums by hardware for outgoing TCP packets.*/
 #define CHECKSUM_GEN_TCP 0
 /* CHECKSUM_CHECK_IP==0: Check checksums by hardware for incoming IP packets.*/
 #define CHECKSUM_CHECK_IP 0
 /* CHECKSUM_CHECK_UDP==0: Check checksums by hardware for incoming UDP packets.*/
 #define CHECKSUM_CHECK_UDP 0
 /* CHECKSUM_CHECK_TCP==0: Check checksums by hardware for incoming TCP packets.*/
 #define CHECKSUM_CHECK_TCP 0
 #else
 /* CHECKSUM_GEN_IP==1: Generate checksums in software for outgoing IP packets.*/
 #define CHECKSUM_GEN_IP 1
 /* CHECKSUM_GEN_UDP==1: Generate checksums in software for outgoing UDP packets.*/
 #define CHECKSUM_GEN_UDP 1
 /* CHECKSUM_GEN_TCP==1: Generate checksums in software for outgoing TCP packets.*/
 #define CHECKSUM_GEN_TCP 1
 /* CHECKSUM_CHECK_IP==1: Check checksums in software for incoming IP packets.*/
 #define CHECKSUM_CHECK_IP 1
 /* CHECKSUM_CHECK_UDP==1: Check checksums in software for incoming UDP packets.*/
 #define CHECKSUM_CHECK_UDP 1
 /* CHECKSUM_CHECK_TCP==1: Check checksums in software for incoming TCP packets.*/
 #define CHECKSUM_CHECK_TCP 1
 #endif
 /**
 * DEFAULT_THREAD_STACKSIZE: The stack size used by any other lwIP thread.
 * The stack size value itself is platform-dependent, but is passed to
 * sys_thread_new() when the thread is created.
 */
 #ifndef DEFAULT_THREAD_STACKSIZE
 #define DEFAULT_THREAD_STACKSIZE 3000
 #endif

 /**
 * DEFAULT_THREAD_PRIO: The priority assigned to any other lwIP thread.
 * The priority value itself is platform-dependent, but is passed to
 * sys_thread_new() when the thread is created.
 */
 #ifndef DEFAULT_THREAD_PRIO
 #define DEFAULT_THREAD_PRIO 3
 #endif

 /*
     ------------------------------------
     ---------- Debugging options ----------
     ------------------------------------
 */

 #define LWIP_DEBUG

 #ifdef LWIP_DEBUG
 #define U8_F "c"
 #define S8_F "c"
 #define X8_F "02x"
 #define U16_F "u"
 #define S16_F "d"
 #define X16_F "x"
 #define U32_F "u"
 #define S32_F "d"
 #define X32_F "x"
 #define SZT_F "u"
 #endif

 #define TCPIP_MBOX_SIZE 32
 #define TCPIP_THREAD_STACKSIZE 1024
 #define TCPIP_THREAD_PRIO 8

 /**
 * DEFAULT_RAW_RECVMBOX_SIZE: The mailbox size for the incoming packets on a

 * NETCONN_RAW. The queue size value itself is platform-dependent, but is passed

 * to sys_mbox_new() when the recvmbox is created.
 */
 #define DEFAULT_RAW_RECVMBOX_SIZE 12

 /**
 * DEFAULT_UDP_RECVMBOX_SIZE: The mailbox size for the incoming packets on a

 * NETCONN_UDP. The queue size value itself is platform-dependent, but is passed

 * to sys_mbox_new() when the recvmbox is created.
 */
 #define DEFAULT_UDP_RECVMBOX_SIZE 12

 /**
 * DEFAULT_TCP_RECVMBOX_SIZE: The mailbox size for the incoming packets on a

 * NETCONN_TCP. The queue size value itself is platform-dependent, but is passed

 * to sys_mbox_new() when the recvmbox is created.
 */
 #define DEFAULT_TCP_RECVMBOX_SIZE 12

 /**
 * DEFAULT_ACCEPTMBOX_SIZE: The mailbox size for the incoming connections.
 * The queue size value itself is platform-dependent, but is passed to
 * sys_mbox_new() when the acceptmbox is created.
 */
 #define DEFAULT_ACCEPTMBOX_SIZE 12

 #if (LWIP_DNS || LWIP_IGMP || LWIP_IPV6) && !defined(LWIP_RAND)

 #include "lwip/arch.h"
 u32_t lwip_rand(void);
 #define LWIP_RAND() lwip_rand()
 #endif

 #endif /* __LWIPOPTS_H__ */

 /*****END OF FILE****/

代码清单 7‑1(1):NO_SYS表示无操作系统模拟层,这个宏非常重要, 因为无操作系统与有操作系统的移植和编写是完全不一样的,我们现在是无操作系统移植, 所以将这个宏定义为1。

代码清单 7‑1(2)(3):因为现在是无操作系统,就不使能NETCONN API和Socket API编程。

代码清单 7‑1(4):内存对齐,按照4字节对齐。

代码清单 7‑1(5):堆内存的大小。如果应用程序将发送很多需要复制的数据应该设置得大一点。

代码清单 7‑1(6):PBUF_POOL内存池中内存块数量。

代码清单 7‑1(7):PBUF_POOL内存池中每个内存块大小。

代码清单 7‑1(8):TCP协议报文最大长度。

代码清单 7‑1(9):允许TCP协议使用的最大发送缓冲区空间(字节)。

代码清单 7‑1(10):TCP接收窗口大小。

cc.h文件中包含处理器相关的变量类型、数据结构及字节对齐的相关宏。

LwIP中使用的基本变量类型均以位数进行命名,为抽象的变量类型定义,开发者需要根据所用处理器及编译器特性进行定义, 一般我们直接将变量直接定义为C语言的基本类型,如unsigned char、int等,这样子可以保证LwIP协议栈就与平台无关了。除此之外我们还可以定义大小端模式,输出调试的宏等,cc.h文件内容具体见 代码清单7_2

代码清单 7‑2 cc.h文件内容

 #ifndef __CC_H__
 #define __CC_H__

 //FSL
 #ifdef __REDLIB__
 #define LWIP_NO_INTTYPES_H 1
 #endif

 #if defined(LWIP_TIMEVAL_PRIVATE) && (LWIP_TIMEVAL_PRIVATE == 0)
 #include <sys/time.h>
 #endif

 #ifndef LWIP_NO_STDINT_H
 #define LWIP_NO_STDINT_H 0
 #endif

 // Typedefs for the types used by lwip
 #if LWIP_NO_STDINT_H
 typedef unsigned char  u8_t;
 typedef signed   char  s8_t;
 typedef unsigned short u16_t;
 typedef signed   short s16_t;
 typedef unsigned int   u32_t;
 typedef signed   int   s32_t;
 typedef u32_t          mem_ptr_t;
 #endif

 // Compiler hints for packing lwip's structures
 //FSL: very important at high optimization level

 #if __GNUC__
 #define PACK_STRUCT_BEGIN
 #elif defined(__IAR_SYSTEMS_ICC__)
 #define PACK_STRUCT_BEGIN _Pragma("pack(1)")
 #elif defined(__arm__) && defined(__ARMCC_VERSION)
 #define PACK_STRUCT_BEGIN __packed
 #else
 #define PACK_STRUCT_BEGIN
 #endif

 #if __GNUC__
 #define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
 #elif defined(__IAR_SYSTEMS_ICC__)
 #define PACK_STRUCT_STRUCT
 #elif defined(__arm__) && defined(__ARMCC_VERSION)
 #define PACK_STRUCT_STRUCT
 #else
 #define PACK_STRUCT_STRUCT
 #endif

 #if __GNUC__
 #define PACK_STRUCT_END
 #elif defined(__IAR_SYSTEMS_ICC__)
 #define PACK_STRUCT_END _Pragma("pack()")
 #elif defined(__arm__) && defined(__ARMCC_VERSION)
 #define PACK_STRUCT_END
 #else
 #define PACK_STRUCT_END
 #endif

 #define PACK_STRUCT_FIELD(x) x

 // Platform specific diagnostic output
 #include "sys_arch.h"//FSL

 // non-fatal, print a message.
 #define LWIP_PLATFORM_DIAG(x)                     do {PRINTF x;PRINTF("\r\n");}
 while(0)
 // fatal, print message and abandon execution.
 #define LWIP_PLATFORM_ASSERT(x)                   sys_assert( x )

 #endif /* __CC_H__ */

perf.h文件是与系统统计与测量相关的头文件,我们暂时无需使用任何统计与测量功能,因此该头文件的量宏定义直接为空即可,具体见 代码清单7_3

代码清单 7‑3 perf.h文件内容

 #ifndef __PERF_H__
 #define __PERF_H__

 #define PERF_START    /* null definition */
 #define PERF_STOP(x)  /* null definition */

 #endif /* __PERF_H__ */

8.3. 移植网卡驱动

在前面的4.4 章节与6.8 章节中已经讲解了关于底层驱动的函数,这些函数在网卡中至关重要,而enet_ethernetif.c文件就是存放这些函数的,LwIP的contrib包中就包含这个文件的模板(ethernetif.c),为了方便,我们直接把野火【i.MX RT1052开发板】光盘资料\5-NXP官方资料\2-NXP官方SDK\MIMXRT1052xxxxB\middleware\lwip\port目录下的enet_ethernetif.c、enet_ethernetif.h、enet_ethernetif_kinetis.c、enet_ethernetif_priv.h、文件直接复制过来,然后我们拷贝到arch文件夹下, enet_ethernetif.c文件内容具体见 代码清单7_4

代码清单 7‑4 enet_ethernetif.c文件全貌

 #include "lwip/opt.h"
 #include "lwip/def.h"
 #include "lwip/mem.h"
 #include "lwip/pbuf.h"
 #include "lwip/stats.h"
 #include "lwip/snmp.h"
 #include "lwip/ethip6.h"
 #include "netif/etharp.h"
 #include "netif/ppp/pppoe.h"
 #include "lwip/igmp.h"
 #include "lwip/mld6.h"

 #if USE_RTOS && defined(FSL_RTOS_FREE_RTOS)
 #include "FreeRTOS.h"
 #include "event_groups.h"
 #endif

 #include "enet_ethernetif.h"
 #include "enet_ethernetif_priv.h"

 #include "fsl_enet.h"
 #include "fsl_phy.h"

 /*****************************************************************
 * Definitions
 *****************************************************************/

 /******************************************************************
 * Code
 *****************************************************************/

 void ethernetif_phy_init(struct ethernetif *ethernetif,
                         const ethernetif_config_t *ethernetifConfig,
                         enet_config_t *config)
 {
     uint32_t sysClock;
     status_t status;
     bool link = false;
     uint32_t count = 0;
     phy_speed_t speed;
     phy_duplex_t duplex;

     sysClock = CLOCK_GetFreq(ethernetifConfig->clockName);

     LWIP_PLATFORM_DIAG(("Initializing PHY..."));

     while ((count < ENET_ATONEGOTIATION_TIMEOUT) && (!link)) {
         status = PHY_Init(*ethernetif_enet_ptr(ethernetif),
         ethernetifConfig->phyAddress, sysClock);

     if (kStatus_Success == status) {
         PHY_GetLinkStatus(*ethernetif_enet_ptr(ethernetif),
                         ethernetifConfig->phyAddress, &link);
         } else if (kStatus_PHY_AutoNegotiateFail == status) {
     LWIP_PLATFORM_DIAG(("PHY Auto-negotiation failed. Please check the ENET cable connection and link partner setting."));
         } else {
             LWIP_ASSERT("\r\nCannot initialize PHY.\r\n", 0);
         }

         count++;
     }

     if (link) {
         /* Get the actual PHY link speed. */
         PHY_GetLinkSpeedDuplex(*ethernetif_enet_ptr(ethernetif),
                                 ethernetifConfig->phyAddress, &speed, &duplex);
         /* Change the MII speed and duplex for actual link status. */
         config->miiSpeed = (enet_mii_speed_t)speed;
         config->miiDuplex = (enet_mii_duplex_t)duplex;
     }
 #if 0 /* Disable assert. If initial auto-negation is timeout, \ \
         the ENET is set to default (100Mbs and full-duplex). */
     else {
         LWIP_ASSERT("\r\nGiving up PHY initialization. Please check the ENET cable connection and link partner setting and reset the board.\r\n", 0);
     }
 #endif
 }

 /**
 * This function should be called when a packet is ready to be read
 * from the interface. It uses the function ethernetif_linkinput() that
 * should handle the actual reception of bytes from the network
 * interface. Then the type of the received packet is determined and
 * the appropriate input function is called.
 *
 * @param netif the lwip network interface structure for this ethernetif
 */
 void ethernetif_input(struct netif *netif)
 {
     struct pbuf *p;

     LWIP_ASSERT("netif != NULL", (netif != NULL));

     /* move received packet into a new pbuf */
     while ((p = ethernetif_linkinput(netif)) != NULL) {
         /* pass all packets to ethernet_input, which decides what packets it supports */
                 if (netif->input(p, netif) != ERR_OK) {
             LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
             pbuf_free(p);
             p = NULL;
         }
     }
 }

 static ENET_Type *ethernetif_get_enet_base(const uint8_t enetIdx)
 {
     ENET_Type* enets[] = ENET_BASE_PTRS;
     int arrayIdx;
     int enetCount;

     for (arrayIdx = 0, enetCount = 0; arrayIdx < ARRAY_SIZE(enets); arrayIdx++)
 {
         if (enets[arrayIdx] != 0U) {  /* process only defined positions */
             /* (some SOC headers count ENETs from 1 instead of 0) */
             if (enetCount == enetIdx) {
                 return enets[arrayIdx];
             }
             enetCount++;
         }
     }

     return NULL;
 }

 err_t ethernetif_init(struct netif *netif, struct ethernetif *ethernetif,
                     const uint8_t enetIdx,
                     const ethernetif_config_t *ethernetifConfig)
 {
     LWIP_ASSERT("netif != NULL", (netif != NULL));
     LWIP_ASSERT("ethernetifConfig != NULL", (ethernetifConfig != NULL));

 #if LWIP_NETIF_HOSTNAME
     /* Initialize interface hostname */
     netif->hostname = "lwip";
 #endif /* LWIP_NETIF_HOSTNAME */

     /*
     * Initialize the snmp variables and counters inside the struct netif.
     * The last argument should be replaced with your link speed, in units
     * of bits per second.
     */
     MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd,
                         LINK_SPEED_OF_YOUR_NETIF_IN_BPS);

     netif->state = ethernetif;
     netif->name[0] = IFNAME0;
     netif->name[1] = IFNAME1;
     /* We directly use etharp_output() here to save a function call.
     * You can instead declare your own function an call etharp_output()
     * from it if you have to do some checks before sending (e.g. if link
     * is available...) */
 #if LWIP_IPV4
     netif->output = etharp_output;
 #endif
 #if LWIP_IPV6
     netif->output_ip6 = ethip6_output;
 #endif /* LWIP_IPV6 */
     netif->linkoutput = ethernetif_linkoutput;

 #if LWIP_IPV4 && LWIP_IGMP
     netif_set_igmp_mac_filter(netif, ethernetif_igmp_mac_filter);
     netif->flags |= NETIF_FLAG_IGMP;
 #endif
 #if LWIP_IPV6 && LWIP_IPV6_MLD
     netif_set_mld_mac_filter(netif, ethernetif_mld_mac_filter);
     netif->flags |= NETIF_FLAG_MLD6;
 #endif

     /* Init ethernetif parameters.*/
     *ethernetif_enet_ptr(ethernetif) = ethernetif_get_enet_base(enetIdx);
     LWIP_ASSERT("*ethernetif_enet_ptr(ethernetif) != NULL", (*ethernetif_enet_ptr(ethernetif) != NULL));

     /* set MAC hardware address length */
     netif->hwaddr_len = ETH_HWADDR_LEN;

     /* set MAC hardware address */
     memcpy(netif->hwaddr, ethernetifConfig->macAddress, NETIF_MAX_HWADDR_LEN);

     /* maximum transfer unit */
     netif->mtu = 1500; /* TODO: define a config */

     /* device capabilities */
     /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
     netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;

     /* ENET driver initialization.*/
     ethernetif_enet_init(netif, ethernetif, ethernetifConfig);//初始化硬件

 #if LWIP_IPV6 && LWIP_IPV6_MLD
     /*
     * For hardware/netifs that implement MAC filtering.
     * All-nodes link-local is handled by default, so we must let the hardware know
     * to allow multicast packets in.
     * Should set mld_mac_filter previously. */
     if (netif->mld_mac_filter != NULL) {
         ip6_addr_t ip6_allnodes_ll;
         ip6_addr_set_allnodes_linklocal(&ip6_allnodes_ll);
         netif->mld_mac_filter(netif, &ip6_allnodes_ll, NETIF_ADD_MAC_FILTER);
     }
 #endif /* LWIP_IPV6 && LWIP_IPV6_MLD */

     return ERR_OK;
 }

enet_ethernetif.h文件,主要是对函数的一些声明,其内容具体见 代码清单7_5

代码清单 7‑5enet_ethernetif.h文件内容

 #ifndef ENET_ETHERNETIF_H
 #define ENET_ETHERNETIF_H

 #include "lwip/err.h"
 #include "fsl_enet.h"

 /************************************************************
 * Definitions
 ***********************************************************/
 #ifndef ENET_RXBD_NUM
 #define ENET_RXBD_NUM (5)
 #endif
 #ifndef ENET_TXBD_NUM
 #if defined(FSL_FEATURE_SOC_LPC_ENET_COUNT) && (FSL_FEATURE_SOC_LPC_ENET_COUNT > 0)
 #define ENET_TXBD_NUM (5)
 #else
 #define ENET_TXBD_NUM (3)
 #endif
 #endif
 #ifndef ENET_RXBUFF_SIZE
 #if defined(FSL_FEATURE_SOC_LPC_ENET_COUNT) && (FSL_FEATURE_SOC_LPC_ENET_COUNT > 0)
 #define ENET_RXBUFF_SIZE (ENET_FRAME_MAX_FRAMELEN + ETH_PAD_SIZE)
 #else
 #define ENET_RXBUFF_SIZE ENET_FRAME_MAX_FRAMELEN
 #endif
 #endif

 #ifndef ENET_TXBUFF_SIZE
 #define ENET_TXBUFF_SIZE (ENET_FRAME_MAX_FRAMELEN)
 #endif

 #define ENET_TIMEOUT        (0xFFFU)

 /* ENET IRQ priority. Used in FreeRTOS. */
 /* Interrupt priorities. */
 #ifdef __CA7_REV
 #ifndef ENET_PRIORITY
 #define ENET_PRIORITY       (21U)
 #endif
 #ifndef ENET_1588_PRIORITY
 #define ENET_1588_PRIORITY  (20U)
 #endif
 #else
 #ifndef ENET_PRIORITY
 #define ENET_PRIORITY       (6U)
 #endif
 #ifndef ENET_1588_PRIORITY
 #define ENET_1588_PRIORITY  (5U)
 #endif
 #endif

 /*  Defines Ethernet Autonegotiation Timeout during initialization.
 *  Set it to 0 to disable the waiting. */
 #ifndef ENET_ATONEGOTIATION_TIMEOUT
 #define ENET_ATONEGOTIATION_TIMEOUT     (0xFFFU)
 #endif
 /* Define those to better describe your network interface. */
 #define IFNAME0 'e'
 #define IFNAME1 'n'

 #if defined(FSL_SDK_ENABLE_DRIVER_CACHE_CONTROL) && FSL_SDK_ENABLE_DRIVER_CACHE_CONTROL
 #if defined(FSL_FEATURE_L2CACHE_LINESIZE_BYTE) \
         && ((!defined(FSL_SDK_DISBLE_L2CACHE_PRESENT)) || (FSL_SDK_DISBLE_L2CACHE_PRESENT == 0)
 #if defined(FSL_FEATURE_L1DCACHE_LINESIZE_BYTE)
 #define FSL_CACHE_LINESIZE_MAX MAX(FSL_FEATURE_L1DCACHE_LINESIZE_BYTE, FSL_FEATURE_L2CACHE_LINESIZE_BYTE)
 #define FSL_ENET_BUFF_ALIGNMENT MAX(ENET_BUFF_ALIGNMENT, FSL_CACHE_LINESIZE_MAX)
 #else
 #define FSL_ENET_BUFF_ALIGNMENT MAX(ENET_BUFF_ALIGNMENT, FSL_FEATURE_L2CACHE_LINESIZE_BYTE)
 #endif
 #elif defined(FSL_FEATURE_L1DCACHE_LINESIZE_BYTE)
 #define FSL_ENET_BUFF_ALIGNMENT MAX(ENET_BUFF_ALIGNMENT, FSL_FEATURE_L1DCACHE_LINESIZE_BYTE)
 #else
 #define FSL_ENET_BUFF_ALIGNMENT ENET_BUFF_ALIGNMENT
 #endif
 #else
 #define FSL_ENET_BUFF_ALIGNMENT ENET_BUFF_ALIGNMENT
 #endif

 #define ENET_RING_NUM 1U

 typedef uint8_t rx_buffer_t[SDK_SIZEALIGN(ENET_RXBUFF_SIZE, FSL_ENET_BUFF_ALIGNMENT)];
 typedef uint8_t tx_buffer_t[SDK_SIZEALIGN(ENET_TXBUFF_SIZE, FSL_ENET_BUFF_ALIGNMENT)];

 #if (defined(FSL_FEATURE_SOC_LPC_ENET_COUNT) && (FSL_FEATURE_SOC_LPC_ENET_COUNT > 0))
 typedef struct mem_range {
     uint32_t start;
     uint32_t end;
 } mem_range_t;
 #endif /* FSL_FEATURE_SOC_LPC_ENET_COUNT */

 /**
 * Helper struct to hold data for configuration of ethernet interface.
 */
 typedef struct ethernetif_config {
     uint32_t phyAddress;
     clock_name_t clockName;
     uint8_t macAddress[NETIF_MAX_HWADDR_LEN];
 #if (defined(FSL_FEATURE_SOC_LPC_ENET_COUNT) && (FSL_FEATURE_SOC_LPC_ENET_COUNT > 0))
     const mem_range_t *non_dma_memory;
 #endif /* FSL_FEATURE_SOC_LPC_ENET_COUNT */
 } ethernetif_config_t;

 #if defined(__cplusplus)
 extern "C" {
 #endif /* __cplusplus */

 /**
 * This function should be passed as a parameter to netif_add()
 * if you initialize the first ENET interface.
 */
 err_t ethernetif0_init(struct netif *netif);

 #if (defined(FSL_FEATURE_SOC_ENET_COUNT) && (FSL_FEATURE_SOC_ENET_COUNT > 1)) \
 || (defined(FSL_FEATURE_SOC_LPC_ENET_COUNT) && (FSL_FEATURE_SOC_LPC_ENET_COUNT > 1))
 /**
 * This function should be passed as a parameter to netif_add()
 * if you initialize the second ENET interface.
 */
 err_t ethernetif1_init(struct netif *netif);
 #endif /* FSL_FEATURE_SOC_*_ENET_COUNT */

 /**
 * This function should be called when a packet is ready to be read
 * from the interface.
 * It is used by bare-metal applications.
 *
 * @param netif the lwip network interface structure for this ethernetif
 */
 void ethernetif_input( struct netif *netif);

 #if defined(__cplusplus)
 }
 #endif /* __cplusplus */

 #endif /* ENET_ETHERNETIF_H */

8.4. LwIP时基

LwIP也是一个内核,与操作系统一样,也是由时基驱动的,LwIP作者为了能让内核正常运行,也引入了一个时钟来驱动,这样子可以处理内核中各种定时事件,如ARP定时、TCP定时等,LwIP已经实现处理超时(定时)事件的函数sys_check_timeouts(),具体怎么处理的就无需用户关心。由于时钟的来源是由用户提供的,这就需要用户实现一个sys_now()函数来获取系统的时钟,以毫秒为单位,LwIP通过两次获取的时间就能判断是否有超时,从而让内核去处理对应的事件。

我们在i.MX RT中,一般采用SysTick作为LwIP的时基定时器,将SysTick产生中断的频率设置为1000HZ,也就是1ms触发一次中断,每次产生中断的时候,系统变量就会加1,变量每次增加一也就是时间增加一毫秒,我们在sys_now()函数中直接返回这个变量即可。

代码清单 7‑6 sys_now()实现

 void SysTick_Handler(void)
 {
     time_isr();
 }
 void time_isr(void)
 {
 #ifdef __CA7_REV
     SystemClearSystickFlag();
 #endif
     time_now++;
 }
 void time_init(void)
 {
 #ifdef __CA7_REV
     SystemSetupSystick(1000U, (void *)time_isr, 0U);
     SystemClearSystickFlag();
 #else
     SysTick_Config(USEC_TO_COUNT(1000U, sourceClock));
 #endif
 }
 u32_t sys_now(void)
 {
     return (u32_t)time_now;
 }

8.5. 协议栈初始化

想要使用LwIP,那就必须先将协议栈初始化,我们就创建一个函数,在函数中初始化协议栈,注册网卡,设置主机的IP地址、子网掩码、网关地址等,比如作者个人电脑的IP地址是192.163.1.181,那么我们在开发板上设置的IP地址必须是与路由器处于同一子网的,我就设置为192.168.1.122,因为这个地址必须是路由器承认的合法地址,否则路由器不会对这个IP地址的数据包进行转发,网关就写对应的网关(路由器IP地址)192.168.1.1即可,255.255.255.0为整个局域网的子网掩码。

然后挂载我们的网卡,挂载网卡的函数我们也讲解过了,就是netif_add()函数,如果我们了解了前面章节的内容, 移植起来是一点都不费劲的。这里主要讲解一下ethernet_input()函数,这个函数在ethernet.c文件中( 在以前的版本如1.4.1,这个函数在etharp.c文件),主要是用于无操作系统时候LwIP去处理接收到的数据, 接收网卡的数据然后往上层递交,对于不同的数据包进行不同的处理,如果是ARP包,则调用etharp_input() 函数交给ARP去处理,更新ARP缓存表;如果是IP包,则调用ip4_input()函数递交给IP层去处理,在后续会详细讲解, 此处了解一下即可,协议栈初始化的源码具体见 代码清单7_7

提示:该函数由用户编写,可以随意放在任何地方,作者个人就将它放在main.c文件中。

代码清单 7‑7协议栈初始化

 void LwIP_Init()
 {
     /*配置网卡信息*/
     ethernetif_config_t fsl_enet_config0 = {
         .phyAddress = EXAMPLE_PHY_ADDRESS,
         .clockName = EXAMPLE_CLOCK_NAME,
         .macAddress = configMAC_ADDR,
     };
     time_init();
     /* 网络接口配置 */
     IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);
     IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3);
     IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);
     /*LWIP初始化*/
     lwip_init();

     netif_add(&gnetif, &ipaddr, &netmask, &gw, &fsl_enet_config0,
             ethernetif0_init, ethernet_input);
     netif_set_default(&gnetif);
     netif_set_up(&gnetif);

     PRINTF("本地IP地址是:%d.%d.%d.%d\n\n",  \
             ((gnetif.ip_addr.addr)&0x000000ff),       \
             (((gnetif.ip_addr.addr)&0x0000ff00)>>8),  \
             (((gnetif.ip_addr.addr)&0x00ff0000)>>16), \
             ((gnetif.ip_addr.addr)&0xff000000)>>24);

 }

8.6. 获取数据包

通过上面的步骤,我们能使用开发板获取网络的数据包了,但是获取数据包的方式有两种,一种是查询方式,另一种是中断方式。查询方式通过主函数的while循环进行周期性处理,去获取网卡中是否接收到数据包,然后递交给上层协议去处理,而中断方式则不一样,在网卡接收到一个数据包的时候,就触发中断,通知CPU去处理,这样子效率就会高很多,特别是在操作系统环境下,我们都采用中断方式去获取数据包。当然,查询方式与中断方式的网卡底层初始化是不一样的,主要是通过网卡接收方式的配置进行初始化,

8.6.1. 查询方式

使用查询方式获取数据包的时候,我们只需要在程序中周期性调用网卡接收函数即可,具体见 代码清单7_8.

代码清单 7‑8 查询方式获取数据包

 int main(void)
 {
     //板级外设初始化
     BSP_Init();

     //LwIP协议栈初始化
     LwIP_Init();

     while (1)
     {
         //调用网卡接收函数
         ethernetif_input(&gnetif);

         //处理LwIP中定时事件
         sys_check_timeouts();
     }
 }

然后我们编译一下程序,并且将程序下载到开发板,然后我们打开Windows的控制台,可以直接通过win+r快捷键输入“cmd”快速打开,具体见 图7_10

图 7‑10打开Windows的控制台

图 7‑10打开Windows的控制台

如果不知道自己所处的局域网网关与电脑IP地址的,我们可以通过在Windows的控制台中执行“ipconfig”命令,就能得到电脑的IP地址,相应的就能得到网关地址,我们只需要将电脑IP地址中最后的3位数改成我们开发板上的地址即可,具体见 图7_11

图 7‑11执行“ipconfig”命令查看IP地址

图 7‑11执行“ipconfig”命令查看IP地址

8.6.2. ping命令详解

然后我们在命令行中执行ping命令,看看网卡驱动与协议栈是否能正常工作,关于ping命令的基本介绍如下(“[ ]”中的命令是非必须填充字段):

ping [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS] [-r count] [-s count] [[-j host-list] | [-k host-list]][-w timeout] [-R] [-S srcaddr] [-c compartment] [-p][-4] [-6] target_name

选项:

  • -t Ping 指定的主机,直到停止。若要查看统计信息并继续操作,请键入 Ctrl+Break;若要停止,请键入 Ctrl+C。

  • -a 将地址解析为主机名。

  • -n count 要发送的回显请求数,默认值为 4。

  • -l size 发送缓冲区大小。

  • -f 在数据包中设置“不分段”标记(仅适用于 IPv4),数据包就不会被路由上的网关分段。

  • -i TTL 指定生存时间。

  • -v TOS 服务类型(仅适用于 IPv4。该设置已被弃用,对 IP 标头中的服务类型字段没有任何影响)

  • -r count 记录计数跃点的路由(仅适用于 IPv4)。

  • -s count 计数跃点的时间戳(仅适用于 IPv4)。

  • -j host-list 与主机列表一起使用的松散源路由(仅适用于 IPv4)。

  • -k host-list 与主机列表一起使用的严格源路由(仅适用于 IPv4)。

  • -w timeout 等待每次回复的超时时间(毫秒)。

  • -R 同样使用路由标头测试反向路由(仅适用于 IPv6)。根据 RFC 5095,已弃如果使用此标头,某些系统可能丢弃用此路由标头,如果使用此标头,某些系统可能丢弃回显请求

  • -S srcaddr 要使用的源地址。

  • -c compartment 路由隔离舱标识符。

  • -p Ping Hyper-V 网络虚拟化提供程序地址。

  • -4 强制使用 IPv4。

  • -6 强制使用 IPv6。

  • target_name 指定要 ping 的远程计算机。

当然,那么多命令肯定也是记不住的,那我们可以通过在控制台中执行“ping /?”命令,就能得到ping命令说明了,具体见 图7_12

图 7‑12“ping /?”命令

图 7‑12“ping /?”命令

学习了ping命令,我们直接执行“ping xxx.xxx.xxx.xxx”命令,看一下我们的开发板看看能否ping得通,如果电脑发出的ping包能被开发板接收并且正确返回, 这代表这电脑与开发板在网络上已经是连通的,xxx对应自己开发板的IP地址,执行命令的结果具体见 图7_13, 然后我们再执行“arp -a”命令,可以看到电脑主机的ARP表,我们开发板的IP地址与MAC地址都正确出现在ARP表中,具体见 图7_14

图 7‑13 ping结果

图 7‑13 ping结果

图 7‑14电脑主机ARP表

图 7‑14电脑主机ARP表

8.6.3. 中断方式

采用查询的方式虽然可行,但是这种方式的效率不高,因为查询就要CPU去看看有没有数据,就像一个人在房子中等朋友过来,但是不知道朋友什么时候来,那主人就要每隔一段时间去看看朋友有没有过来,这样子就占用了大量的资源,主人也没能做其他事情,而如果在门口装一个门铃,朋友来的时候就按下门铃,主人就知道朋友来了,就出去迎接,这样子就不会占用主人的时间,主人可以做其他事情。同样的,我们可以使用中断方式来接收数据,当接收完成的时候,就通知CPU来处理即可。如果你需要使用中断方式接收数据的话,使用带有操作系统的LwIP即可。