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‑2 将src文件夹重命名为LwIP
在前面的章节中也讲解了LwIP源码文件夹的功能及作用,那么我们现在直接添加到工程分组中即可, 首先我们在工程中创建4个与LwIP相关的工程分组,其中三个分别用于存放LwIP源码中的三个文件,其对应关系具体见 图7_3。
图 7‑3工程中创建分组存放LwIP源码
每个分组中都添加对应文件夹下的源码,添加完成后的源码具体见 图7_4、图7_5、图7_6 与 图7_7。
图 7‑4
图 7‑5
图 7‑6
图 7‑7
然后在工程中添加LwIP头文件路径,并且把C99模式也勾选上,添加完成的示意图具体见 图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的文件
然后我们打开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的控制台
如果不知道自己所处的局域网网关与电脑IP地址的,我们可以通过在Windows的控制台中执行“ipconfig”命令,就能得到电脑的IP地址,相应的就能得到网关地址,我们只需要将电脑IP地址中最后的3位数改成我们开发板上的地址即可,具体见 图7_11。
图 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 /?”命令
学习了ping命令,我们直接执行“ping xxx.xxx.xxx.xxx”命令,看一下我们的开发板看看能否ping得通,如果电脑发出的ping包能被开发板接收并且正确返回, 这代表这电脑与开发板在网络上已经是连通的,xxx对应自己开发板的IP地址,执行命令的结果具体见 图7_13, 然后我们再执行“arp -a”命令,可以看到电脑主机的ARP表,我们开发板的IP地址与MAC地址都正确出现在ARP表中,具体见 图7_14。
图 7‑13 ping结果
图 7‑14电脑主机ARP表
8.6.3. 中断方式¶
采用查询的方式虽然可行,但是这种方式的效率不高,因为查询就要CPU去看看有没有数据,就像一个人在房子中等朋友过来,但是不知道朋友什么时候来,那主人就要每隔一段时间去看看朋友有没有过来,这样子就占用了大量的资源,主人也没能做其他事情,而如果在门口装一个门铃,朋友来的时候就按下门铃,主人就知道朋友来了,就出去迎接,这样子就不会占用主人的时间,主人可以做其他事情。同样的,我们可以使用中断方式来接收数据,当接收完成的时候,就通知CPU来处理即可。如果你需要使用中断方式接收数据的话,使用带有操作系统的LwIP即可。