LwIP简介
LwIP是轻量化的TCP/IP协议,由瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。LwIP具有高度可移植性、代码开源,提供了三种编程接口(API):RAW API、NETCONN API 和 Socket API,用于与TCP/IP代码进行通信。
通过官网可获取LwIP源码包及contrib包。源代码包主要包含LwIP内核的源码文件,contrib包中包含部分移植和应用LwIP的demo。contrib包不属于LwIP内核的一部分,但很有参考价值。
以lwip-2.1.2版本的源码包为例,如图1所示,该源码包分为三部分, src 文件为LWIP源代码文件, doc 文件包含LwIP相关文档, test 为LwIP测试文件,使用时主要关注于 src 文件下的内容。
LwIP内核是由一系列模块组合而成,包括 TCP/IP 协议栈的各种协议、内存管理、数据包管理、网卡接口、基础功能类模块、API等,构成这些模块的源文件就分布在api、apps、core、netif中,头文件则汇总在include中。
api
NETCONN API和Socket API相关的源文件,只有在操作系统的环境中,才能被编译
apps
应用程序的源文件,包括常见的应用程序,如httpd、mqtt、tftp、sntp、snmp等
core
LwIP的内核源文件
include
LwIP所有模块对应的头文件
netif
与网卡移植有关的文件
图1 LwIP-2.1.2源码包
移植接口解析
LwIP使用数据结构体netif来描述网卡,并提供统一接口,需要与以太网底层驱动接口函数结合使用,例如底层驱动负责完成网卡的初始化、网卡的数据收发等,当LwIP内核需要发送一个数据包时,就会通过LWIP提供的接口函数去调用底层网卡的发送函数,将数据由硬件接口与软件内核衔接在一起。
contrib文件中包含部分可使用的网卡移植模板文件,其中ethernetif.c文件(contrib-2.1.0examplesethernetif目录下的ethernetif.c文件)为底层接口驱动的模板,以 LibSamples 为例,若要基于 LibSample的以太网驱动移植LwIP,则需参考ethernetif.c模板,根据以太网驱动及所需配置进行修改,将底层驱动 ethernet 相关函数填充到LwIP所需的指定功能函数中。
ethernetif.c文件中的函数通常为与硬件打交道的底层函数,当有数据需要通过网卡接收或者发送数据的时候就会被调用,经过LwIP协议栈内部进行处理后,从应用层就能得到数据或者可以发送数据。该文件中包括函数:low_level_init()、low_level_output()、low_level_input()、ethernetif_input()和ethernetif_init()函数。
ethernetif_init()
LwIP中默认的网卡初始化函数,内部封装了low_level_init()函数
ethernetif_input()
该函数用于接收网卡数据,内部封装了low_level_input()函数,在接收完毕时,将数据通过pbuf递交给上层。
low_level_init()
low_level_init()函数主要是根据实际情况对网卡进行一系列的初始化工作,例如:初始化MAC地址、长度, 设置最大传输包的大小,设置网卡的属性字段等功能。
该函数中需要调用以太网底层驱动中的相关初始化函数,以 LibSamples为例,该函数需要调用以太网底层驱动 hal_enet.c/.h 的 PHY、MAC、DMA相关初始化函数并进行配置。
low_level_output()
该函数用于实现网卡发送数据,是一个底层驱动函数,需根据以太网底层驱动进行相应修改,若想通过一个网卡发送数据,则需要将该数据传入LwIP内核中,经过层层封装最后存储在pbuf数据包中,需注意pbuf以链表的形式存在,数据发送时是以一整个数据包全部发送的。
low_level_input()
low_level_input()函数用于从网卡中接收一个数据包,并将该数据包封装在pbuf中递交给上层,该函数需要调用以太网底层驱动中的接收函数。
移植LwIP协议栈
基于LibSamples的以太网驱动对LwIP进行移植,需先将LwIP源文件中的部分文件添加到LibSamples中,如: src 源文件、 include 头文件。
若想令LwIP运行,还需补充contrib文件中部分内容,如图2所示,由于部分源文件中使用头文件写法为”arch/xx”,因此,在src文件下新建arch文件,并将需要修改的模板文件及contrib中的部分接口文件放入arch文件中。
ethernetif.c网卡移植模板文件
cc.h文件主要完成协议栈内部使用的数据类型的定义
lwipopts.h文件包含了用户对协议栈内核参数进行的配置,若未在lwipopts.h文件中进行配置,则LwIP会使用opt.h中的默认参数
perf.h文件是实现与系通通计和测量相关的功能,若未使用该功能,则无需修改
bpstruct.h、epstruct.h由contrib文件下的ports文件所提供,属于堆栈的一部分,无需修改
图2 LWIP移植所需部分文件
lwipopts.h文件中需要根据是否为操作系统模拟层、堆内存大小、是否使用TCP及TCP相关配置等进行宏定义配置,例如:宏定义 NO_SYS 表示无操作系统模拟层,因为当前为无操作系统的移植,所以设置该宏定义为1。
... /** * NO_SYS==1: Provides VERY minimal functionality. Otherwise, * use LwIPfacilities. */ #define NO_SYS 1 ...
cc.h文件中包含处理器相关的变量类型、数据结构及字节对齐的相关宏,需根据处理器及编译器进行修改。
... #define LWIP_NO_STDINT_H 1 typedef unsigned char u8_t; typedef signed char s8_t; typedef unsigned short u16_t; typedef signed short s16_t; typedef unsigned long u32_t; typedef signed long s32_t; typedef u32_t mem_ptr_t; typedef int sys_prot_t; #define U16_F "hu" #define S16_F "d" #define X16_F "hx" #define U32_F "u" #define S32_F "d" #define X32_F "x" #define SZT_F "uz" ... #elif defined (__GNUC__) #define PACK_STRUCT_BEGIN #define PACK_STRUCT_STRUCT __attribute__ ((__packed__)) #define PACK_STRUCT_END #define PACK_STRUCT_FIELD(x) x ...
low_level_init移植接口实现
头文件配置并修改完成后,需要对移植模板文件 ethernetif.c 进行修改。
在以太网底层驱动与LwIP初始化接口的衔接上,对low_level_init()进行修改,在对LwIP的netif结构体进行相关配置之前,需要通过以太网底层驱动使硬件被初始化;初始化后,配置 MAC 硬件地址,链接发送描述符及接收描述符并进行描述符内容配置,配置描述符地址,配置完成后,使能以太网 DMA 启动传输,此时,初始化完成。
static void low_level_init(struct netif *netif) { struct ethernetif *ethernetif = netif->state; /* set MAC hardware address length */ netif->hwaddr_len = ETHARP_HWADDR_LEN; /* set MAC hardware address */ netif->hwaddr[0] = BOARD_MAC_ADDR0; netif->hwaddr[1] = BOARD_MAC_ADDR1; netif->hwaddr[2] = BOARD_MAC_ADDR2; netif->hwaddr[3] = BOARD_MAC_ADDR3; netif->hwaddr[4] = BOARD_MAC_ADDR4; netif->hwaddr[5] = BOARD_MAC_ADDR5; /* maximum transfer unit */ netif->mtu = 1500; /* 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; #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 */ ETH_GPIOInit(); SysTick->CTRL |= ((uint32_t)0x00000004); SysTick_Config(120000000 / 1000); ETH_InitTypeDef ptr; ETH_StructInit(&ptr); ptr.ETH_AutoNegotiation = ETH_AutoNegotiation_Disable; ETH_Init(&ptr, ENET_PHY_ADDR); ETH->DMAOMR &= ~ETH_DMAOMR_OSF; /* Enable ETH DMA interrupt. */ ETH_DMAITConfig(ETH_DMA_IT_NIS|ETH_DMA_IT_R, ENABLE); NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = ENET_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); /* Config macd filter address. */ ENET_SetupMacAddrFilter(0x1u<<31|0x1u<<5, ENET_ADDR_FILTER_NUM, 0u, netif->hwaddr); /* Set tx dma desp link. */ memset(enet_txdma_desp_tbl, 0, sizeof(enet_txdma_desp_tbl)); for (uint32_t i = 0u; i < ENET_TX_NUM - 1; i++) { enet_txdma_desp_tbl[0].CS |= TXDMA_DES0_TCH; /* TCH = 1u. */ enet_txdma_desp_tbl[0].BUF1ADDR = (uint32_t)(enet_txbuf[i]); enet_txdma_desp_tbl[0].BUF2NDADDR = (uint32_t)(&enet_txdma_desp_tbl[i + 1]); } enet_txdma_desp_tbl[0].CS |= TXDMA_DES0_TCH; /* TCH = 1u. */ enet_txdma_desp_tbl[0].BUF1ADDR = (uint32_t)(enet_txbuf[ENET_TX_NUM - 1]); enet_txdma_desp_tbl[0].BUF2NDADDR = (uint32_t)(&enet_txdma_desp_tbl[0]); /* Set enet tx dma descriptor first address. */ ETH->DMATXDSAR = (uint32_t)(&enet_txdma_desp_tbl[0]); enet_usable_txdma_desp = &enet_txdma_desp_tbl[0]; /* Set rx dma desp link. */ memset(enet_rxdma_desp_tbl, 0, sizeof(enet_rxdma_desp_tbl)); for (uint32_t i = 0; i < ENET_RX_NUM - 1; i++) { enet_rxdma_desp_tbl[i].CS |= RXDMA_DES0_OWN; /* RDES0[OWN] = 1. */ enet_rxdma_desp_tbl[i].BL |= RXDMA_DES1_RCH; /* RDES1[RCH] = 1. */ enet_rxdma_desp_tbl[i].BL &= ~ RXDMA_DES1_RBS1; enet_rxdma_desp_tbl[i].BL |= ENET_RX_BUFLEN; /* RDES1[RBS1] = ENET_RX_BUFLEN. */ enet_rxdma_desp_tbl[i].BUF1ADDR = (uint32_t)enet_rxbuf[i]; enet_rxdma_desp_tbl[i].BUF2NDADDR = (uint32_t)(&enet_rxdma_desp_tbl[i+1]); } enet_rxdma_desp_tbl[ENET_RX_NUM - 1].CS |= RXDMA_DES0_OWN; /* RDES0[OWN] = 1. */ enet_rxdma_desp_tbl[ENET_RX_NUM - 1].BL |= RXDMA_DES1_RCH; /* RDES1[RCH] = 1. */ enet_rxdma_desp_tbl[ENET_RX_NUM - 1].BL &= ~ RXDMA_DES1_RBS1; enet_rxdma_desp_tbl[ENET_RX_NUM - 1].BL |= ENET_RX_BUFLEN; /* RDES1[RBS1] = ENET_RX_BUFLEN. */ enet_rxdma_desp_tbl[ENET_RX_NUM - 1].BUF1ADDR = (uint32_t)enet_rxbuf[ENET_RX_NUM - 1]; enet_rxdma_desp_tbl[ENET_RX_NUM - 1].BUF2NDADDR = (uint32_t)(&enet_rxdma_desp_tbl[0]); ETH->DMARXDSAR = (uint32_t)enet_rxdma_desp_tbl; enet_first_rxdma_desp = &enet_rxdma_desp_tbl[0]; ETH_Start(); }
low_level_output移植接口实现
low_level_output()函数与以太网底层驱动的发送功能函数相结合,将LwIP要发送的数据存储到以太网发送描述符中所指定的存储区域中,再对发送描述符进行配置并进行发送。
static err_t low_level_output(struct netif *netif, struct pbuf *p) { struct ethernetif *ethernetif = netif->state; struct pbuf *q; /* Get current destination address. */ ETH_DMADESCTypeDef * txdma_desp = enet_usable_txdma_desp; if (0u != (txdma_desp->CS & TXDMA_DES0_OWN) ){ return ERR_USE; } #if ETH_PAD_SIZE pbuf_remove_header(p, ETH_PAD_SIZE); /* drop the padding word */ #endif uint32_t e_offset = 0; /* record enet module buf offset. */ for (q = p; q != NULL; q = q->next) { /* Send the data from the pbuf to the interface, one pbuf at a time. The size of the data in each pbuf is kept in the ->len variable. */ for (uint32_t i = 0; i < q->len; i++) { ((uint8_t*)(txdma_desp->BUF1ADDR))[e_offset] = ((uint8_t*)(q->payload))[i]; e_offset++; if (e_offset == ENET_TX_BUFLEN) { txdma_desp = (ETH_DMADESCTypeDef*)(txdma_desp->BUF2NDADDR); if ((txdma_desp->CS & TXDMA_DES0_OWN) != 0u) { return ERR_USE; } e_offset = 0; } } } if (p->tot_len <= ENET_TX_BUFLEN) { enet_usable_txdma_desp->CS |= TXDMA_DES0_TFS | TXDMA_DES0_TLS | TXDMA_DES0_OWN; enet_usable_txdma_desp->BL &= ~0x1FFF; enet_usable_txdma_desp->BL |= p->tot_len; /* TBS1!< Transfer buffer size 1. */ enet_usable_txdma_desp = (ETH_DMADESCTypeDef*)enet_usable_txdma_desp->BUF2NDADDR; } else { enet_usable_txdma_desp->CS |= TXDMA_DES0_TFS; /* TFS = 1u. */ enet_usable_txdma_desp->CS &= ~TXDMA_DES0_TLS; /* TLS = 0u. */ enet_usable_txdma_desp->BL &= ~0x1FFF; enet_usable_txdma_desp->BL |= ENET_TX_BUFLEN; /*!< Transfer buffer size 1. */ enet_usable_txdma_desp = (ETH_DMADESCTypeDef*)enet_usable_txdma_desp->BUF2NDADDR; for (uint32_t i = ENET_TX_BUFLEN; i < p->tot_len - ENET_TX_BUFLEN; i+= ENET_TX_BUFLEN) { enet_usable_txdma_desp->CS &= ~TXDMA_DES0_TFS; /* TFS = 0u. */ enet_usable_txdma_desp->CS &= ~TXDMA_DES0_TLS; /* TLS = 0u. */ enet_usable_txdma_desp->BL &= ~0x1FFF; enet_usable_txdma_desp->BL |= ENET_TX_BUFLEN; enet_usable_txdma_desp = (ETH_DMADESCTypeDef*)enet_usable_txdma_desp->BUF2NDADDR; } enet_usable_txdma_desp = (ETH_DMADESCTypeDef*)enet_usable_txdma_desp->BUF2NDADDR; enet_usable_txdma_desp->CS &= ~TXDMA_DES0_TFS; /* TFS = 0u. */ enet_usable_txdma_desp->CS |= TXDMA_DES0_TLS; /* TLS = 1u. */ enet_usable_txdma_desp->BL &= ~0x1FFF; enet_usable_txdma_desp->BL |= (p->tot_len % ENET_TX_BUFLEN); } if (0 != (ETH->DMASR Ð_DMA_TransmitProcess_Suspended)){ ETH_ResumeDMATransmission(); } MIB2_STATS_NETIF_ADD(netif, ifoutoctets, p->tot_len); if (((u8_t *)p->payload)[0] & 1) { /* broadcast or multicast packet*/ MIB2_STATS_NETIF_INC(netif, ifoutnucastpkts); } else { /* unicast packet */ MIB2_STATS_NETIF_INC(netif, ifoutucastpkts); } /* increase ifoutdiscards or ifouterrors on error */ #if ETH_PAD_SIZE pbuf_add_header(p, ETH_PAD_SIZE); /* reclaim the padding word */ #endif LINK_STATS_INC(link.xmit); return ERR_OK; }
low_level_input移植接口实现
low_level_input()函数与以太网底层驱动的接收功能函数相结合,将接收到的数据存入LwIP的pbuf链中。ethernetif_input()函数调用low_level_input()函数。
static struct pbuf * low_level_input(struct netif *netif) { struct ethernetif *ethernetif = netif->state; struct pbuf *p, *q; u16_t len; ETH_DMADESCTypeDef * rxdma_desp = enet_first_rxdma_desp; for (uint32_t i = 0; i < ENET_RX_NUM; i++) { if ((rxdma_desp->CS & RXDMA_DES0_RLS) != 0) { len = (uint32_t)(rxdma_desp->CS & RXDMA_DES0_FL)>>16; break; } else if ((rxdma_desp->CS & RXDMA_DES0_OWN) != 0) { return NULL; } else { rxdma_desp = (ETH_DMADESCTypeDef*)(rxdma_desp->BUF2NDADDR); } } #if ETH_PAD_SIZE len += ETH_PAD_SIZE; /* allow room for Ethernet padding */ #endif /* We allocate a pbuf chain of pbufs from the pool. */ p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); if (p != NULL) { #if ETH_PAD_SIZE pbuf_remove_header(p, ETH_PAD_SIZE); /* drop the padding word */ #endif /* We iterate over the pbuf chain until we have read the entire * packet into the pbuf. */ uint32_t e_offset = 0; rxdma_desp = enet_first_rxdma_desp; for (q = p; q != NULL; q = q->next) { /* Read enough bytes to fill this pbuf in the chain. The * available data in the pbuf is given by the q->len * variable. * This does not necessarily have to be a memcpy, you can also preallocate * pbufs for a DMA-enabled MAC and after receiving truncate it to the * actually received size. In this case, ensure the tot_len member of the * pbuf is the sum of the chained pbuf len members. */ for (uint32_t i = 0; i < q->len; i++) { ((uint8_t*)q->payload)[i] = ((uint8_t*)rxdma_desp->BUF1ADDR)[e_offset]; e_offset++; if (e_offset == ENET_RX_BUFLEN) { rxdma_desp = (ETH_DMADESCTypeDef*)(rxdma_desp->BUF2NDADDR); e_offset = 0; } } } MIB2_STATS_NETIF_ADD(netif, ifinoctets, p->tot_len); if (((u8_t *)p->payload)[0] & 1) { /* broadcast or multicast packet*/ MIB2_STATS_NETIF_INC(netif, ifinnucastpkts); } else { /* unicast packet*/ MIB2_STATS_NETIF_INC(netif, ifinucastpkts); } #if ETH_PAD_SIZE pbuf_add_header(p, ETH_PAD_SIZE); /* reclaim the padding word */ #endif LINK_STATS_INC(link.recv); } else { LINK_STATS_INC(link.memerr); LINK_STATS_INC(link.drop); MIB2_STATS_NETIF_INC(netif, ifindiscards); } do { enet_first_rxdma_desp->CS |= RXDMA_DES0_OWN; /* Set OWN bit. */ enet_first_rxdma_desp = (ETH_DMADESCTypeDef*)enet_first_rxdma_desp->BUF2NDADDR; } while ((enet_first_rxdma_desp->CS&RXDMA_DES0_OWN) == 0); if (RESET != (ETH_GetDMAFlagStatus((0x4 << 17)) ) ){ /*!< ENET dma rx fifo not active, need to be weak up. */ ETH_ResumeDMAReception(); /* Wakeup enet dma receive. */ } return p; }
ENET_IRQHandler中断服务函数实现
/* ENET IRQHandler. */ void ENET_IRQHandler() { if (0 != ETH_GetDMAFlagStatus(ETH_DMA_FLAG_R)) { ethernetif_input(gnetif); ETH_DMAClearFlag(ETH_DMA_FLAG_R); } }
自定义参数声明及函数实现
需要根据实际选用的开发板和运行参数等进行宏定义配置,如 IP 地址、端口号、MAC地址需要根据实际的网络环境进行配置,这里以LwIP_TCP_Client样例为例,将这些参数定义到了 lwip_tcp_client.h 文件中。
/* initialization enet. */ #define ENET_PHY_ADDR 0x01 /* Select PHY address. */ #define ENET_PHY_CONTROLREG 0u /* PHY control register address. */ #define ENET_PHY_STATUSREG 1u /* PHY status register sddress. */ #define ENET_PHY_RESET 0x8000 /* Set PHY reset, use in ENET_PHY_CR registers */ #define ENET_PHY_SPEED100M 0x2000 /* Set PHY speed. */ #define ENET_PHY_FULLDUPLEX 0x0100 /* Set PHY duplex mode about full duplex. */ #define ENET_PHY_LINK 0x0004 /* PHY link-up. */ #define ENET_PHY_UNIDIRECTIONAL 0x0080 /* PHY has the ability to encode and transmit data from PHY through MII interface, regardless of whether PHY has determined that an effective link has been connected and established. */ #define ENET_PHY_AUTONEGOTIATION 0x1000 /* PHY auto negotiation. */ #define ENET_TX_BUFLEN 1500u /* Tx buffer length. */ #define ENET_TX_NUM 4u /* The number of tx. */ #define ENET_RX_BUFLEN 1500u /* Configure the frame length of a received frame. */ #define ENET_RX_NUM 4u /* The configured number of received descriptor that can be used for receiving. */ #define ENET_ADDR_FILTER_NUM 5u /* Select MAC address filter number from 0~5. */ #define BOARD_MAC_ADDR0 2u #define BOARD_MAC_ADDR1 1u #define BOARD_MAC_ADDR2 0u #define BOARD_MAC_ADDR3 0u #define BOARD_MAC_ADDR4 0u #define BOARD_MAC_ADDR5 0u #define BOARD_IP_ADDR0 192u #define BOARD_IP_ADDR1 168u #define BOARD_IP_ADDR2 105u #define BOARD_IP_ADDR3 98u #define BOARD_NETMASK_ADDR0 255u #define BOARD_NETMASK_ADDR1 255u #define BOARD_NETMASK_ADDR2 255u #define BOARD_NETMASK_ADDR3 0u #define BOARD_GW_ADDR0 192u #define BOARD_GW_ADDR1 168u #define BOARD_GW_ADDR2 1u #define BOARD_GW_ADDR3 1u #define BOARD_TCP_SERVER_IPADDR0 192u #define BOARD_TCP_SERVER_IPADDR1 168u #define BOARD_TCP_SERVER_IPADDR2 105u #define BOARD_TCP_SERVER_IPADDR3 85u #define BOARD_TCP_SERVER_PORT 6800u #define TXDMA_DES0_TCH 0x01u<<20 #define TXDMA_DES0_TFS 0x01u<<28 #define TXDMA_DES0_TLS 0x01u<<29 #define TXDMA_DES0_OWN 0x01u<<31 #define RXDMA_DES0_RLS 0x01u<<8 #define RXDMA_DES0_FL 0x3FFFu<<16 #define RXDMA_DES0_OWN 0x01u<<31 #define RXDMA_DES1_RCH 0x01u<<14 #define RXDMA_DES1_RBS1 0x1FFFu #define FILTERS_RECEIVE_ALL 0x01u<<31 #define FILTERS_BOARDCAST_FILTER 0x01u<<5
在lwip_tcp_client.c文件中除了对Ethernet相关的时钟引脚进行配置及使用到的系统时钟对应参数申明外,也根据LwIP协议栈实际的应用需求,实现了关于MAC地址过滤器的函数。
void ENET_SetupMacAddrFilter(uint32_t filter, uint32_t addr_id, uint32_t addr_mask, uint8_t * addr) { ETH->MACAFR |= filter; if ( (0u != (filter & ETH_SourceAddrFilter_Normal_Enable)) || (0u != (filter & 0x100)) ) /* Set source address filter. */ { ETH->MACA0HR = ( 0x1u<<31 | 0x1u<<30 | (uint32_t)addr[4u] | ((uint32_t)addr[5u]<<8u) );; ETH->MACA0LR = ( (uint32_t)addr[0u] | ((uint32_t)addr[1u] << 8u) | ((uint32_t)addr[2u] << 16u) | ((uint32_t)addr[3u] << 24u) );; } else if ( (0u != (filter & 0x10)) || (0u != (filter & 0x100)) ) /* Set destination address filter. */ { ETH->MACAFR &= ~(0x1u<<4 | 0x1u<<1); } if (0u != addr_mask) { ETH->MACA0HR|= addr_mask; } }
/* Returns the current time in milliseconds, this API from lwip/sys.h */ uint32_t sys_now(void) { return systime_ms; } uint32_t sys_jiffies(void) { return systime_ms * 1000000; }
样例说明
基于移植的 LwIP协议,LibSamples还提供了展示 TCP 协议客户端与服务器通信的 lwip_tcp_client、lwip_tcp_server样例,展示 UDP 协议客户端与服务器通信的 lwip_udp_client、lwip_udp_server。
样例实现环境搭建
本文基于搭载了MM32F5277E9P MCU的开发板 PLUS-F5270 V2.0进行实现,使用2根网线,分别连接电脑与路由器、开发板与路由器。
在官网(http://free.cmsoft.cn/reslink.php?id=205)下载网络调试助手NetAssist并安装,用于后续的样例功能验证。
打开电脑终端(WIN+R键,输入CMD),然后输入指令 ipconfig/all ,查看本机的以太网IP地址为 192.168.108.85 ;
在终端中输入命令 netstat -na 获取本地开放端口,这里我们获取到可用端口号为 49153 。
LwIP_TCP_Client
LwIP_TCP_Client 样例用于展示基于以太网及 LwIP使用 TCP 协议作为客户端,进行客户端与服务器之间的通信。
若想使用LwIP,则需要先将协议栈初始化,并设置主机的IP地址、子网掩码、网关地址等。需注意,样例工程中所设置的IP地址需要与路由器处于同一子网,如图3所示,在命令提示符(CMD)中使用命令 ipconfig/all 可查看各IP的详细信息,例如所查出的以太网IPx4地址为192.168.108.85,则在样例工程中可设置IP地址为192.168.108.98,网关地址与子网掩码的配置需与所查出的以太网默认网关及子网掩码相同。
void app_lwip_init(void) { ip4_addr_t ipaddr; ip4_addr_t netmask; ip4_addr_t gw; IP4_ADDR(&ipaddr, BOARD_IP_ADDR0, BOARD_IP_ADDR1, BOARD_IP_ADDR2, BOARD_IP_ADDR3); IP4_ADDR(&netmask, BOARD_NETMASK_ADDR0, BOARD_NETMASK_ADDR1, BOARD_NETMASK_ADDR2, BOARD_NETMASK_ADDR3); IP4_ADDR(&gw, BOARD_GW_ADDR0, BOARD_GW_ADDR1, BOARD_GW_ADDR2, BOARD_GW_ADDR3); lwip_init(); ... }
图3 在CMD界面通过命令查询以太网IP信息
在配置完IP地址等必要信息后,需挂载网卡,在LwIP中网卡挂载函数为 netif_add() 函数,将所配置的数据传入该函数中。
void app_lwip_init(void) { ... netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input); netif_set_default(&gnetif); if (netif_is_link_up(&gnetif)) { netif_set_up(&gnetif); } else { netif_set_down(&gnetif); } }
LwIP协议栈初始化后,需要对所使用的 TCP Client(TCP客户端)进行初始化配置。在 LwIP中存在多个与 TCP 相关的函数,LwIP_TCP_Client样例所使用到的函数包括:
tcp_new()
创建一个TCP的PCB控制块
tcp_connect()
连接远端主机
tcp_err()
控制块err字段注册的回调函数,遇到错误时被调用
tcp_write()
构造一个报文并放入控制块的发送缓冲队列中
tcp_recv()
控制块rev字段注册的回调函数,当接收到新数据是被调用
tcp_recved()
当程序处理完数据后调用该函数,通知内核更新接收窗口
tcp_close()
关闭一个TCP连接
TCP 客户端的工作流程包括:新建控制块、建立连接、发送请求与接收数据并处理。TCP客户端工作流程如图4所示。
图4 TCP客户端流程图
TCP Client(TCP客户端)进行初始化配置时,通过 IP4_ADDR() 函数将目标服务器的IP写入结构体;再通过 tcp_new() 函数为TCP客户端分配一个结构,当该结构不为空时,使用 tcp_connect() 函数与目标服务器进行连接,该函数中配置目标端口和目标IP参数并调用连接完成回调函数。
void app_tcp_client_init(void) { struct tcp_pcb *tcp_client_pcb; ip_addr_t app_server_ip; /* Write the IP of the target server into a structure, which is the local connection IP address of the pc. */ IP4_ADDR(&app_server_ip, BOARD_TCP_SERVER_IPADDR0, BOARD_TCP_SERVER_IPADDR1, BOARD_TCP_SERVER_IPADDR2, BOARD_TCP_SERVER_IPADDR3); /* Assign a structure to the TCP client */ tcp_client_pcb = tcp_new(); if (tcp_client_pcb != NULL) { /* Connect with the target server, and the parameters include the target port and the target IP. */ tcp_connect(tcp_client_pcb, &app_server_ip, BOARD_TCP_SERVER_PORT, app_tcp_client_connected); /* Registered connection error handling callback function. */ tcp_err(tcp_client_pcb, app_tcp_client_connecterror); } }
在连接完成回调函数中,使用 tcp_write() 函数发送问候字符串以建立连接,并使用 tcp_recv() 函数配置接收回调函数。
static err_t app_tcp_client_connected(void *arg, struct tcp_pcb *pcb, err_t err) { /* Send a greeting string to establish a connection */ tcp_write(pcb, clientstring, strlen(clientstring), 1u); /* Configure the receive callback function */ tcp_recv(pcb, app_tcp_client_xfer); return ERR_OK; }
在TCP客户端接收数据后的数据处理回调函数中,接收到有效数据后,通过tcp_recved()更新接收窗口,使用 tcp_write() 函数将接收到的服务器内容回显。
static err_t app_tcp_client_xfer(void *arg, struct tcp_pcb *pcb, struct pbuf *tcp_recv_pbuf, err_t err) { if (tcp_recv_pbuf != NULL) { /* Update the receiving window */ tcp_recved(pcb, tcp_recv_pbuf->tot_len); tcp_write(pcb, tcp_recv_pbuf->payload, tcp_recv_pbuf->len, 1u); pbuf_free(tcp_recv_pbuf); } else if (err == ERR_OK) { tcp_close(pcb); app_tcp_client_init(); return ERR_OK; } return ERR_OK; }
lwip_tcp_client 样例的实验现象如图5所示,通过网络调试助手可查看到连接成功后,远端服务器收到客户端发送的数据,服务器向客户端发送任意数据包后,客户端回显相同数据。
图5 lwip_tcp_client样例实验现象
注意事项
在官网下载网络调试助手NetAssist并安装,用于后续的样例功能验证。
打开电脑终端(WIN+R键,输入CMD),然后输入指令` ipconfig/all `,查看本机的以太网IP地址。
在配置 IP 地址和端口号时,当连接了WIFI后需要注意我们选用的是以太网的IP地址,而非WLAN的IP地址。
在终端中输入命令 `netstat -na` 获取本地开放端口。
关于灵动
上海灵动微电子股份有限公司成立于 2011 年,是中国本土领先的通用 32 位 MCU 产品及解决方案供应商。公司基于 Arm Cortex-M 系列内核开发的 MM32 MCU 产品目前已量产近 300 款型号,累计交付超 5 亿颗,每年都有近亿台配备了灵动 MM32MCU 的优秀产品交付到客户手中,在本土通用 32 位 MCU 公司中位居前列。
灵动客户涵盖智能工业、汽车电子、通信基建、医疗健康、智慧家电、物联网、个人设备、手机和电脑等应用领域。灵动是中国为数不多的同时获得了 Arm-KEIL、IAR、SEGGER 官方支持的本土 MCU 公司,并建立了独立、完整的通用 MCU 生态体系。灵动始终秉承着“诚信、承诺、创新、合作”的精神,为客户提供从硬件芯片到软件算法、从参考方案到系统设计的全方位支持。
全部0条评论
快来发表一下你的评论吧 !