LWIP网卡实现原理
1. LWIP协议栈内存管理
1.1 内存管理需求
内存管理需求分为两类
- 常用内存管理需求:静态分配的变量(RAM),任务堆栈,动态存储器管理malloc/free
- LWIP内存管理需求:协议栈各层封装的数据
1.2 内存管理方案
LWIP内存管理有两种方案:堆(heap)和池(pool)
- 堆:堆内存管理机制会根据需要分配的内存大小在空闲的内存块中找到最佳拟合(best fit)的内存区域
LWIP内存堆管理API函数:
//内存堆初始化
void mem_init(void);
//内存堆分配内存
void *mem_malloc(mem_size_t size);
//内存堆释放内存
void mem_free(void *mem);
- 池:池内存管理机制将内存分配成多个大小不一的内存池,每个内存池中又被分为N个相同大小的内存块。程序可根据需要使用的内存大小直接到不同的内存池中取用即可。池内存管理机制分配内存更快,效率更高
LWIP内存池管理API函数:
//内存池初始化
void memp_init(void);
//内存池分配
void *memp_malloc(memp_t type);
//内存池释放
void memp_free(memp_t type, void *mem);
1.3 网络数据包管理
pbuf就是一个描述协议栈中数据包的数据结构,LWIP 中在 pbuf.c和 pubf.h实现了协议栈数据包管理的所有函数与数据结构
- pbuf数据结构
pbuf结构体
//在pbuf.h中定义
struct pbuf {
/** 指向下一个pbuf结构体,每个pbuf存放的数据有限,若应用有大量的数据的话
就需要多个pbuf来存放,可以将同一个数据包的pbuf连接在一起构成一个链表 */
struct pbuf *next;
/** 指向该pbuf真正的数据存储区的首地址。STM32F4内部网络模块收到数据,并
将数据提交给LWIP时,就是将数据存储在payload指定的存储区中;同样在发送数
据的时候将payload指向的存储区数据转给STM32F4的网络模块去发送 */
void *payload;
/** 在接收或发送数据的时候数据会存放在pbuf链表中,tot_Len表示当前pbuf和链
表中后面所有pbuf的数据长度,它们属于一个数据包 */
u16_t tot_len;
/** 当前pbuf总数据的长度 */
u16_t len;
/** 当前pbuf类型,共有四种:PBUF_RAM/PBUF_ROM/PBUF_REF/PBUF_POOL */
u8_t type;
/** 保留位 */
u8_t flags;
/** 该pbuf被引用的次数,当还有其他指针指向这个pbuf的时候ref字段就加一 */
u16_t ref;
};
pbuf类型:共有四种:PBUF_RAM、PBUF_ROM、PBUF_REF、PBUF_POOL
//在pbuf.h中定义
typedef enum {
/** PBUF_RAM类型的pbuf是通过内存堆分配来的,其payload并未指向数据区的起始
地址,而是隔了一段区域,在这个区域(offset)里通常存放TCP报文首部、IP首部
、以太网帧首部等等 */
PBUF_RAM,
/** PBUF_ROM的数据指针payload指向外部存储区,外部存储区指不由TCP/IP协议
栈管理的存储区,它可以是应用程序管理的存储器为用户数据分配的缓存,也可以是
ROM区域,如静态网页中的字符串常量等 */
PBUF_ROM,
/** PBUF_REF和PBUF_ROM的特性非常相似,都可以实现数据的零拷贝 */
PBUF_REF,
/** PBUF_POOL类型的pbuf是通过内存池分类来的,pbuf链表的第一个pbuf的payload
未指向数据区的起始位置,原因通PBUF_RAM一样,用来存放一些首部,pbuf链表后面
的pbuf结构体中的payload就指向了数据区的起始位置 */
PBUF_POOL
} pbuf_type;
pbuf层:由于LWIP各层禁止数据拷贝,所以存在不同层次对数据包pbuf的alloc,前面的offest就是为不同层预留的头部字段,下面枚举了4种层次,分配时除了要知道大小、类型还要传入这个层次
//在pbuf.h中定义
typedef enum {
/** 传输层,预留以太首部+IP首部+TCP首部 */
PBUF_TRANSPORT,
/** 网络层,预留以太首部+IP首部 */
PBUF_IP,
/** 链路层,预留以太首部 */
PBUF_LINK,
/** 原始层,不预留空间 */
PBUF_RAW_TX,
/** Use this for input packets in a netif driver when calling netif->input()
* in the most common case - ethernet-layer netif driver. */
PBUF_RAW
} pbuf_layer;
- pbuf典型API函数
pbuf_alloc:内存申请函数
struct pbuf *pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type);
参数:
layer 申请的pbuf所在的层,以此来确定offset值
length 存放数据的大小
type pbuf的类型
pbuf_free:数据包释放函数
u8_t pbuf_free(struct pbuf *p);
参数:
p 要释放的pbuf数据包
pbuf_realloc:调整收缩pbuf的大小,在相应pbuf(链表)尾部释放一定的空间,将数据包pbuf中的数据长度减少为某个长度值
void pbuf_realloc(struct pbuf *p, u16_t new_len);
参数:
p 要收缩的pbuf数据包
new_len 新的长度值
pbuf_header:调整payload指针和长度字段以便为pbuf中的数据预置包头,常用于实现对pbuf预留孔间的操作
u8_t pbuf_header(struct pbuf *p, s16_t header_size_increment);
参数:
p 要操作的pbuf数据包
header_size_increment >0,payload前移,数据传递下层;<0,payload后移,数据传递上层
pbuf_take:用于向pbuf的数据区域拷贝数据
err_t pbuf_take(struct pbuf *buf, const void *dataptr, u16_t len);
参数:
buf 要填充数据的pbuf
dataptr 应用程序中的数据缓冲区
len 数据缓冲区的长度
2. LWIP网卡设计与实现
2.1 LWIP网络接口管理
在LWIP中对于网络接口的描述是通过一个netif结构体完成的,netif结构体在netif.h文件中有定义
- netif结构体
//在netif.h中定义
struct netif {
struct netif *next; //指向下一个netif结构体
#if LWIP_IPV4
ip_addr_t ip_addr; //ip地址
ip_addr_t netmask; //子网掩码
ip_addr_t gw; //网关地址
#endif /* LWIP_IPV4 */
netif_input_fn input; //netif数据包输入接口函数指针
#if LWIP_IPV4
netif_output_fn output;//netif数据包输出接口函数指针
#endif /* LWIP_IPV4 */
netif_linkoutput_fn linkoutput;//链路层数据输出接口函数指针
#if LWIP_NETIF_STATUS_CALLBACK
netif_status_callback_fn status_callback;//当netif状态发生变化时,此接口函数会调用
#endif /* LWIP_NETIF_STATUS_CALLBACK */
#if LWIP_NETIF_LINK_CALLBACK
/** PHY必须和交换机或者路由器或者其他具备网卡的主机相连接,我们才可能正常通信
比如路由器突然断电,这个函数就会被调用 */
netif_status_callback_fn link_callback;
#endif /* LWIP_NETIF_LINK_CALLBACK */
#if LWIP_NETIF_REMOVE_CALLBACK
netif_status_callback_fn remove_callback;//netif移除网络驱动接口,这个函数会被调用
#endif /* LWIP_NETIF_REMOVE_CALLBACK */
void *state; //主机的状态
#if LWIP_NETIF_HOSTNAME
const char* hostname; //自定义的主机名称
#endif /* LWIP_NETIF_HOSTNAME */
#if LWIP_CHECKSUM_CTRL_PER_NETIF
u16_t chksum_flags;
#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/
u16_t mtu; //数据链路层最大传输大小
u8_t hwaddr_len; //mac地址长度
u8_t hwaddr[NETIF_MAX_HWADDR_LEN];//mac地址
u8_t flags; //当前的netif的状态,其实就是下面的netif_flag
char name[2]; //网卡驱动的名称
u8_t num; //网卡驱动的硬件编号
#if LWIP_IPV4 && LWIP_IGMP
netif_igmp_mac_filter_fn igmp_mac_filter;//组播底层接口
#endif /* LWIP_IPV4 && LWIP_IGMP */
};
- netif flag宏定义
/** netif网络接口,可以进行正常使用(即lwIP可以正常使用)了 */
#define NETIF_FLAG_UP 0x01U
/** 广播通讯的标志 */
#define NETIF_FLAG_BROADCAST 0x02U
/** STM32 MAC和PHY可以正常使用 */
#define NETIF_FLAG_LINK_UP 0x04U
/** ARP标志 */
#define NETIF_FLAG_ETHARP 0x08U
/** TCP/IP协议正常通信 */
#define NETIF_FLAG_ETHERNET 0x10U
2.2 netif典型API函数
netif的API函数是供应用层调用的函数
netif_add:添加网卡驱动到lwip
struct netif *netif_add(struct netif *netif,
const ip4_addr_t *ipaddr,
const ip4_addr_t *netmask,
const ip4_addr_t *gw,
void *state,
netif_init_fn init,
netif_input_fn input);
netif_set_default:把网卡恢复出厂设置,目前lwip有一套默认参数
void netif_set_default(struct netif *netif);
netif_set_up&netif_set_down:设置我们网卡工作状态,是上线还是离线
void netif_set_up(struct netif *netif);
void netif_set_down(struct netif *netif);
callback:需要自己实现link_callback函数
#if LWIP_NETIF_LINK_CALLBACK
void netif_set_link_callback(struct netif *netif,
netif_status_callback_fn link_callback);
#endif /* LWIP_NETIF_LINK_CALLBACK */
2.3 netif底层接口函数介绍
netif底层接口即与硬件打交道的函数接口,主要包括以下函数
ethernetif_init:初始化网卡驱动(会调用底层驱动)
err_t ethernetif_init(struct netif *netif){
#if LWIP_IPV4
#if LWIP_ARP || LWIP_ETHERNET
//arp相关的函数接口赋值
#if LWIP_ARP
netif->output = etharp_output;
#else
netif->output = low_level_output_arp_off;
#endif /* LWIP_ARP */
#endif /* LWIP_ARP || LWIP_ETHERNET */
#endif /* LWIP_IPV4 */
//链路层数据输出函数接口赋值
netif->linkoutput = low_level_output;
/* 底层接口初始化 */
low_level_init(netif);
return ERR_OK;
}
ethernetif_input:网卡数据输入(会调用底层接口)
void ethernetif_input(void const * argument){
struct pbuf *p;
struct netif *netif = (struct netif *) argument;
for( ;; )
{
if (osSemaphoreWait(s_xSemaphore, TIME_WAITING_FOR_INPUT) == osOK)
{
do
{
p = low_level_input( netif );
if (p != NULL)
{
if (netif->input( p, netif) != ERR_OK )
{
pbuf_free(p);
}
}
} while(p!=NULL);
}
}
}
low_level_init:网卡底层驱动,主要针对硬件(STM32网卡初始化会在此调用)
/** 硬件初始化,其实就STM32 ETH外设初始化 */
static void low_level_init(struct netif *netif){
uint32_t regvalue = 0;
HAL_StatusTypeDef hal_eth_init_status;
/* Init ETH */
uint8_t MACAddr[6] ;
heth.Instance = ETH;
heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;
heth.Init.PhyAddress = DP83848_PHY_ADDRESS;
MACAddr[0] = 0x00;
MACAddr[1] = 0x80;
MACAddr[2] = 0xE1;
MACAddr[3] = 0x00;
MACAddr[4] = 0x00;
MACAddr[5] = 0x00;
heth.Init.MACAddr = &MACAddr[0];
heth.Init.RxMode = ETH_RXINTERRUPT_MODE;
heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;
heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
hal_eth_init_status = HAL_ETH_Init(&heth);
if (hal_eth_init_status == HAL_OK)
{
/* 当初始化成功后,会置位flag,同时在tcp/ip初始化完毕后,会
进行判断,此标志位决定网卡驱动是否 可以正常使用 */
netif->flags |= NETIF_FLAG_LINK_UP;
}
/* Initialize Tx Descriptors list: Chain Mode */
HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);
/* Initialize Rx Descriptors list: Chain Mode */
HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);
#if LWIP_ARP || LWIP_ETHERNET
/* MAC地址初始化 */
netif->hwaddr_len = ETH_HWADDR_LEN;
netif->hwaddr[0] = heth.Init.MACAddr[0];
netif->hwaddr[1] = heth.Init.MACAddr[1];
netif->hwaddr[2] = heth.Init.MACAddr[2];
netif->hwaddr[3] = heth.Init.MACAddr[3];
netif->hwaddr[4] = heth.Init.MACAddr[4];
netif->hwaddr[5] = heth.Init.MACAddr[5];
/* maximum transfer unit */
netif->mtu = 1500;
/* Accept broadcast address and ARP traffic */
/* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
#if LWIP_ARP
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
#else
netif->flags |= NETIF_FLAG_BROADCAST;
#endif /* LWIP_ARP */
/* 二值信号量,用于信息同步,当网卡接口到数据后,会释放二值信号量让其他任务进行解析 */
osSemaphoreDef(SEM);
s_xSemaphore = osSemaphoreCreate(osSemaphore(SEM), 1);
/* 创建网卡数据接收解析任务-ethernetif_input */
osThreadDef(EthIf, ethernetif_input, osPriorityRealtime, 0, INTERFACE_THREAD_STACK_SIZE);
osThreadCreate (osThread(EthIf), netif);
/* 使能 网卡 发送和接口 */
HAL_ETH_Start(&heth);
/* 上面的都是针对STM32 ETH外设进行初始化
但是实际网络交互是用过PHY,下面就是初始化PHY */
/* Read Register Configuration */
HAL_ETH_ReadPHYRegister(&heth, PHY_MICR, ®value);
regvalue |= (PHY_MICR_INT_EN | PHY_MICR_INT_OE);
/* Enable Interrupts */
HAL_ETH_WritePHYRegister(&heth, PHY_MICR, regvalue );
/* Read Register Configuration */
HAL_ETH_ReadPHYRegister(&heth, PHY_MISR, ®value);
regvalue |= PHY_MISR_LINK_INT_EN;
/* Enable Interrupt on change of link status */
HAL_ETH_WritePHYRegister(&heth, PHY_MISR, regvalue);
#endif /* LWIP_ARP || LWIP_ETHERNET */
}
low_level_output:底层网卡的数据输出,实际的数据输出,是通过pbuf进行封装管理的
static err_t low_level_output(struct netif *netif, struct pbuf *p){
err_t errval;
struct pbuf *q;
uint8_t *buffer = (uint8_t *)(heth.TxDesc->Buffer1Addr);
__IO ETH_DMADescTypeDef *DmaTxDesc;
uint32_t framelength = 0;
uint32_t bufferoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t payloadoffset = 0;
DmaTxDesc = heth.TxDesc;
bufferoffset = 0;
/* copy frame from pbufs to driver buffers */
for(q = p; q != NULL; q = q->next)
{
/* Is this buffer available? If not, goto error */
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error;
}
/* Get bytes in current lwIP buffer */
byteslefttocopy = q->len;
payloadoffset = 0;
/* Check if the length of data to copy is bigger than Tx buffer size*/
while( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
{
/* Copy data to Tx buffer*/
memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset) );
/* Point to next descriptor */
DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
/* Check if the buffer is available */
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error;
}
buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr);
byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
/* Copy the remaining bytes */
memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), byteslefttocopy );
bufferoffset = bufferoffset + byteslefttocopy;
framelength = framelength + byteslefttocopy;
}
/* 把pbuf里面的数据,发送到ETH外设里面 */
HAL_ETH_TransmitFrame(&heth, framelength);
errval = ERR_OK;
error:
/* When Transmit Underflow flag is set, clear it and issue a Transmit Poll Demand to resume transmission */
if ((heth.Instance->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET)
{
/* Clear TUS ETHERNET DMA flag */
heth.Instance->DMASR = ETH_DMASR_TUS;
/* Resume DMA transmission*/
heth.Instance->DMATPDR = 0;
}
return errval;
}
low_level_input:底层网卡的数据接口,当接收到网卡数据后,会通过此函数,封装为pbuf提供上层使用
static struct pbuf * low_level_input(struct netif *netif){
struct pbuf *p = NULL;
struct pbuf *q = NULL;
uint16_t len = 0;
uint8_t *buffer;
__IO ETH_DMADescTypeDef *dmarxdesc;
uint32_t bufferoffset = 0;
uint32_t payloadoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t i=0;
/* 通过HAL库,获取网卡帧数据 */
if (HAL_ETH_GetReceivedFrame_IT(&heth) != HAL_OK)
return NULL;
/* 获取网卡数据超度,及内存地址 */
len = heth.RxFrameInfos.length;
buffer = (uint8_t *)heth.RxFrameInfos.buffer;
//网卡中数据有效
if (len > 0){
/* 网卡数据不能大于1500,属于原始层接口 */
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
}
//如果pbuf创建成功,则从ETH中拷贝数据到pbuf里,最终把pbuf返回给上层应用
if (p != NULL){
dmarxdesc = heth.RxFrameInfos.FSRxDesc;
bufferoffset = 0;
for(q = p; q != NULL; q = q->next){
byteslefttocopy = q->len;
payloadoffset = 0;
/* Check if the length of bytes to copy in current pbuf is bigger than Rx buffer size*/
while( (byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE ){
/* Copy data to pbuf */
memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), (ETH_RX_BUF_SIZE - bufferoffset));
/* Point to next descriptor */
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
buffer = (uint8_t *)(dmarxdesc->Buffer1Addr);
byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
/* Copy remaining data in pbuf */
memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), byteslefttocopy);
bufferoffset = bufferoffset + byteslefttocopy;
}
}
/* Release descriptors to DMA */
/* Point to first descriptor */
dmarxdesc = heth.RxFrameInfos.FSRxDesc;
/* Set Own bit in Rx descriptors: gives the buffers back to DMA */
for (i=0; i< heth.RxFrameInfos.SegCount; i++){
dmarxdesc->Status |= ETH_DMARXDESC_OWN;
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
}
/* Clear Segment_Count */
heth.RxFrameInfos.SegCount =0;
/* When Rx Buffer unavailable flag is set: clear it and resume reception */
if ((heth.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET) {
/* Clear RBUS ETHERNET DMA flag */
heth.Instance->DMASR = ETH_DMASR_RBUS;
/* Resume DMA reception */
heth.Instance->DMARPDR = 0;
}
return p;
}
tcpip_init:工作在操作系统下的初始化
/**
* @工作在操作系统下的初始化:
* - 初始化所有的子功能模块
* - 启动tcp/ip任务(tcp/ip网络协议栈的实现是一个任务里面执行的)
* @param 用于用户初始化的函数指针,在lwip初始化完成,tcp/ip任务开始执行就是进行调用
* @param 用户初始化相关参数传入
*/
void tcpip_init(tcpip_init_done_fn initfunc, void *arg){
//lwip的初始化---初始化lwip所有功能模块
lwip_init();
//用户初始化函数指针赋值,参数赋值
tcpip_init_done = initfunc;
tcpip_init_done_arg = arg;
//消息邮箱(freeRTOS是通过消息队列实现),任务与任务间消息通信,网卡收到数据,网络分层解析,我们的任务怎么知道呢,就是通过消息邮箱进行传输
if (sys_mbox_new(&mbox, TCPIP_MBOX_SIZE) != ERR_OK) {
LWIP_ASSERT("failed to create tcpip_thread mbox", 0);
}
#if LWIP_TCPIP_CORE_LOCKING
//创建互斥锁(互斥信号量),保护共享资源的
if (sys_mutex_new(&lock_tcpip_core) != ERR_OK) {
LWIP_ASSERT("failed to create lock_tcpip_core", 0);
}
#endif /* LWIP_TCPIP_CORE_LOCKING */
//这是标准的cmis接口,其实内部调用的freeRTOS的创建任务接口
sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);
}
lwip_init:工作在裸机模式下的初始化
/**
* @工作在裸机模式下的初始化
* Use this in NO_SYS mode. Use tcpip_init() otherwise.
*/
void lwip_init(void){
/* Modules initialization */
//状态初始化
stats_init();
#if !NO_SYS
//与操作系统相关的初始化
sys_init();
#endif /* !NO_SYS */
//内存堆 内存池 pbuf netif初始化
mem_init();
memp_init();
pbuf_init();
netif_init();
#if LWIP_IPV4
//ip层初始化
ip_init();
#if LWIP_ARP
//arp+以太网相关的初始化
etharp_init();
#endif /* LWIP_ARP */
#endif /* LWIP_IPV4 */
#if LWIP_RAW
//原生接口初始化
raw_init();
#endif /* LWIP_RAW */
#if LWIP_UDP
udp_init();
#endif /* LWIP_UDP */
#if LWIP_TCP
tcp_init();
#endif /* LWIP_TCP */
#if LWIP_IGMP
igmp_init();
#endif /* LWIP_IGMP */
#if LWIP_DNS
dns_init();
#endif /* LWIP_DNS */
#if PPP_SUPPORT
ppp_init();
#endif
#if LWIP_TIMERS
//lwip内部有很多超时机制,就是通过下面这个timeouts实现的(一个软件定时器)
sys_timeouts_init();
#endif /* LWIP_TIMERS */
}
MX_LWIP_Init:HAL库实现的lwip初始化函数
void MX_LWIP_Init(void){
/* IP 地址初始化 */
IP_ADDRESS[0] = 192;
IP_ADDRESS[1] = 168;
IP_ADDRESS[2] = 1;
IP_ADDRESS[3] = 10;
NETMASK_ADDRESS[0] = 255;
NETMASK_ADDRESS[1] = 255;
NETMASK_ADDRESS[2] = 255;
NETMASK_ADDRESS[3] = 0;
GATEWAY_ADDRESS[0] = 192;
GATEWAY_ADDRESS[1] = 168;
GATEWAY_ADDRESS[2] = 1;
GATEWAY_ADDRESS[3] = 1;
/* 初始化lwip协议栈 */
tcpip_init( NULL, NULL );
/* 数组格式的IP地址转换为lwip格式的地址 */
IP4_ADDR(&ipaddr, IP_ADDRESS[0], IP_ADDRESS[1], IP_ADDRESS[2], IP_ADDRESS[3]);
IP4_ADDR(&netmask, NETMASK_ADDRESS[0], NETMASK_ADDRESS[1] , NETMASK_ADDRESS[2], NETMASK_ADDRESS[3]);
IP4_ADDR(&gw, GATEWAY_ADDRESS[0], GATEWAY_ADDRESS[1], GATEWAY_ADDRESS[2], GATEWAY_ADDRESS[3]);
/* 装载网卡驱动,并初始化网卡 */
netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
/* gnetif注册为默认网卡驱动 */
netif_set_default(&gnetif);
// 判断phy和mac层是否正常工作
if (netif_is_link_up(&gnetif)){
/* netif 网卡驱动可以正常使用,上线 */
netif_set_up(&gnetif);
}
else{
/* netif 网卡驱动下线 */
netif_set_down(&gnetif);
}
}