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文件中需要根据是否为操作系统interwetten与威廉的赔率体系 层、堆内存大小、是否使用TCP及TCP相关配置等进行宏定义配置,例如:宏定义 NO_SYS 表示无操作系统模拟层,因为当前为无操作系统的移植,所以设置该宏定义为1。
... /** *NO_SYS==1:ProvidesVERYminimalfunctionality.Otherwise, *useLwIPfacilities. */ #defineNO_SYS1 ...
cc.h文件中包含处理器相关的变量类型、数据结构及字节对齐的相关宏,需根据处理器及编译器进行修改。
... #defineLWIP_NO_STDINT_H1 typedefunsignedcharu8_t; typedefsignedchars8_t; typedefunsignedshortu16_t; typedefsignedshorts16_t; typedefunsignedlongu32_t; typedefsignedlongs32_t; typedefu32_tmem_ptr_t; typedefintsys_prot_t; #defineU16_F"hu" #defineS16_F"d" #defineX16_F"hx" #defineU32_F"u" #defineS32_F"d" #defineX32_F"x" #defineSZT_F"uz" ... #elifdefined(__GNUC__) #definePACK_STRUCT_BEGIN #definePACK_STRUCT_STRUCT__attribute__((__packed__)) #definePACK_STRUCT_END #definePACK_STRUCT_FIELD(x)x ...
low_level_init移植接口实现
头文件配置并修改完成后,需要对移植模板文件 ethernetif.c 进行修改。
在以太网底层驱动与LwIP初始化接口的衔接上,对low_level_init()进行修改,在对LwIP的netif结构体进行相关配置之前,需要通过以太网底层驱动使硬件被初始化;初始化后,配置 MAC 硬件地址,链接发送描述符及接收描述符并进行描述符内容配置,配置描述符地址,配置完成后,使能以太网 DMA 启动传输,此时,初始化完成。
staticvoid low_level_init(structnetif*netif) { structethernetif*ethernetif=netif->state; /*setMAChardwareaddresslength*/ netif->hwaddr_len=ETHARP_HWADDR_LEN; /*setMAChardwareaddress*/ 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; /*maximumtransferunit*/ netif->mtu=1500; /*devicecapabilities*/ /*don'tsetNETIF_FLAG_ETHARPifthisdeviceisnotanethernetone*/ netif->flags=NETIF_FLAG_BROADCAST|NETIF_FLAG_ETHARP|NETIF_FLAG_LINK_UP; #ifLWIP_IPV6&&LWIP_IPV6_MLD /* *Forhardware/netifsthatimplementMACfiltering. *All-nodeslink-localishandledbydefault,sowemustletthehardwareknow *toallowmulticastpacketsin. *Shouldsetmld_mac_filterpreviously.*/ if(netif->mld_mac_filter!=NULL){ ip6_addr_tip6_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_InitTypeDefptr; ETH_StructInit(&ptr); ptr.ETH_AutoNegotiation=ETH_AutoNegotiation_Disable; ETH_Init(&ptr,ENET_PHY_ADDR); ETH->DMAOMR&=~ETH_DMAOMR_OSF; /*EnableETHDMAinterrupt.*/ ETH_DMAITConfig(ETH_DMA_IT_NIS|ETH_DMA_IT_R,ENABLE); NVIC_InitTypeDefNVIC_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); /*Configmacdfilteraddress.*/ ENET_SetupMacAddrFilter(0x1u<<31|0x1u<<5, ENET_ADDR_FILTER_NUM, 0u, netif->hwaddr); /*Settxdmadesplink.*/ memset(enet_txdma_desp_tbl,0,sizeof(enet_txdma_desp_tbl)); for(uint32_ti=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]; /*Setrxdmadesplink.*/ memset(enet_rxdma_desp_tbl,0,sizeof(enet_rxdma_desp_tbl)); for(uint32_ti=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要发送的数据存储到以太网发送描述符中所指定的存储区域中,再对发送描述符进行配置并进行发送。
staticerr_t low_level_output(structnetif*netif,structpbuf*p) { structethernetif*ethernetif=netif->state; structpbuf*q; /*Getcurrentdestinationaddress.*/ ETH_DMADESCTypeDef*txdma_desp=enet_usable_txdma_desp; if(0u!=(txdma_desp->CS&TXDMA_DES0_OWN)){ returnERR_USE; } #ifETH_PAD_SIZE pbuf_remove_header(p,ETH_PAD_SIZE);/*dropthepaddingword*/ #endif uint32_te_offset=0;/*recordenetmodulebufoffset.*/ for(q=p;q!=NULL;q=q->next){ /*Sendthedatafromthepbuftotheinterface,onepbufata time.Thesizeofthedataineachpbufiskeptinthe->len variable.*/ for(uint32_ti=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){ returnERR_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_ti=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){ /*broadcastormulticastpacket*/ MIB2_STATS_NETIF_INC(netif,ifoutnucastpkts); }else{ /*unicastpacket*/ MIB2_STATS_NETIF_INC(netif,ifoutucastpkts); } /*increaseifoutdiscardsorifouterrorsonerror*/ #ifETH_PAD_SIZE pbuf_add_header(p,ETH_PAD_SIZE);/*reclaimthepaddingword*/ #endif LINK_STATS_INC(link.xmit); returnERR_OK; }
low_level_input移植接口实现
low_level_input()函数与以太网底层驱动的接收功能函数相结合,将接收到的数据存入LwIP的pbuf链中。ethernetif_input()函数调用low_level_input()函数。
staticstructpbuf* low_level_input(structnetif*netif) { structethernetif*ethernetif=netif->state; structpbuf*p,*q; u16_tlen; ETH_DMADESCTypeDef*rxdma_desp=enet_first_rxdma_desp; for(uint32_ti=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; }elseif((rxdma_desp->CS&RXDMA_DES0_OWN)!=0){ returnNULL; }else{ rxdma_desp=(ETH_DMADESCTypeDef*)(rxdma_desp->BUF2NDADDR); } } #ifETH_PAD_SIZE len+=ETH_PAD_SIZE;/*allowroomforEthernetpadding*/ #endif /*Weallocateapbufchainofpbufsfromthepool.*/ p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL); if(p!=NULL){ #ifETH_PAD_SIZE pbuf_remove_header(p,ETH_PAD_SIZE);/*dropthepaddingword*/ #endif /*Weiterateoverthepbufchainuntilwehavereadtheentire *packetintothepbuf.*/ uint32_te_offset=0; rxdma_desp=enet_first_rxdma_desp; for(q=p;q!=NULL;q=q->next){ /*Readenoughbytestofillthispbufinthechain.The *availabledatainthepbufisgivenbytheq->len *variable. *Thisdoesnotnecessarilyhavetobeamemcpy,youcanalsopreallocate *pbufsforaDMA-enabledMACandafterreceivingtruncateittothe *actuallyreceivedsize.Inthiscase,ensurethetot_lenmemberofthe *pbufisthesumofthechainedpbuflenmembers. */ for(uint32_ti=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){ /*broadcastormulticastpacket*/ MIB2_STATS_NETIF_INC(netif,ifinnucastpkts); }else{ /*unicastpacket*/ MIB2_STATS_NETIF_INC(netif,ifinucastpkts); } #ifETH_PAD_SIZE pbuf_add_header(p,ETH_PAD_SIZE);/*reclaimthepaddingword*/ #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;/*SetOWNbit.*/ 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中断服务函数实现
/*ENETIRQHandler.*/ voidENET_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 文件中。
/*initializationenet.*/ #defineENET_PHY_ADDR0x01/*SelectPHYaddress.*/ #defineENET_PHY_CONTROLREG0u/*PHYcontrolregisteraddress.*/ #defineENET_PHY_STATUSREG1u/*PHYstatusregistersddress.*/ #defineENET_PHY_RESET0x8000/*SetPHYreset,useinENET_PHY_CRregisters*/ #defineENET_PHY_SPEED100M0x2000/*SetPHYspeed.*/ #defineENET_PHY_FULLDUPLEX0x0100/*SetPHYduplexmodeaboutfullduplex.*/ #defineENET_PHY_LINK0x0004/*PHYlink-up.*/ #defineENET_PHY_UNIDIRECTIONAL0x0080/*PHYhastheabilitytoencodeandtransmitdatafromPHYthroughMIIinterface,regardlessofwhetherPHYhasdeterminedthataneffectivelinkhasbeenconnectedandestablished.*/ #defineENET_PHY_AUTONEGOTIATION0x1000/*PHYautonegotiation.*/ #defineENET_TX_BUFLEN1500u/*Txbufferlength.*/ #defineENET_TX_NUM4u/*Thenumberoftx.*/ #defineENET_RX_BUFLEN1500u/*Configuretheframelengthofareceivedframe.*/ #defineENET_RX_NUM4u/*Theconfigurednumberofreceiveddescriptorthatcanbeusedforreceiving.*/ #defineENET_ADDR_FILTER_NUM5u/*SelectMACaddressfilternumberfrom0~5.*/ #defineBOARD_MAC_ADDR02u #defineBOARD_MAC_ADDR11u #defineBOARD_MAC_ADDR20u #defineBOARD_MAC_ADDR30u #defineBOARD_MAC_ADDR40u #defineBOARD_MAC_ADDR50u #defineBOARD_IP_ADDR0192u #defineBOARD_IP_ADDR1168u #defineBOARD_IP_ADDR2105u #defineBOARD_IP_ADDR398u #defineBOARD_NETMASK_ADDR0255u #defineBOARD_NETMASK_ADDR1255u #defineBOARD_NETMASK_ADDR2255u #defineBOARD_NETMASK_ADDR30u #defineBOARD_GW_ADDR0192u #defineBOARD_GW_ADDR1168u #defineBOARD_GW_ADDR21u #defineBOARD_GW_ADDR31u #defineBOARD_TCP_SERVER_IPADDR0192u #defineBOARD_TCP_SERVER_IPADDR1168u #defineBOARD_TCP_SERVER_IPADDR2105u #defineBOARD_TCP_SERVER_IPADDR385u #defineBOARD_TCP_SERVER_PORT6800u #defineTXDMA_DES0_TCH0x01u<<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地址过滤器的函数。
voidENET_SetupMacAddrFilter(uint32_tfilter,uint32_taddr_id,uint32_taddr_mask,uint8_t*addr) { ETH->MACAFR|=filter; if((0u!=(filterÐ_SourceAddrFilter_Normal_Enable))||(0u!=(filter&0x100)))/*Setsourceaddressfilter.*/ { 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; } }
/*Returnsthecurrenttimeinmilliseconds,thisAPIfromlwip/sys.h*/ uint32_tsys_now(void) { returnsystime_ms; } uint32_tsys_jiffies(void) { returnsystime_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,网关地址与子网掩码的配置需与所查出的以太网默认网关及子网掩码相同。
voidapp_lwip_init(void) { ip4_addr_tipaddr; ip4_addr_tnetmask; ip4_addr_tgw; 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() 函数,将所配置的数据传入该函数中。
voidapp_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参数并调用连接完成回调函数。
voidapp_tcp_client_init(void) { structtcp_pcb*tcp_client_pcb; ip_addr_tapp_server_ip; /*WritetheIPofthetargetserverintoastructure,whichisthelocalconnectionIPaddressofthepc.*/ IP4_ADDR(&app_server_ip,BOARD_TCP_SERVER_IPADDR0,BOARD_TCP_SERVER_IPADDR1,BOARD_TCP_SERVER_IPADDR2,BOARD_TCP_SERVER_IPADDR3); /*AssignastructuretotheTCPclient*/ tcp_client_pcb=tcp_new(); if(tcp_client_pcb!=NULL) { /*Connectwiththetargetserver,andtheparametersincludethetargetportandthetargetIP.*/ tcp_connect(tcp_client_pcb,&app_server_ip,BOARD_TCP_SERVER_PORT,app_tcp_client_connected); /*Registeredconnectionerrorhandlingcallbackfunction.*/ tcp_err(tcp_client_pcb,app_tcp_client_connecterror); } }
在连接完成回调函数中,使用 tcp_write() 函数发送问候字符串以建立连接,并使用 tcp_recv() 函数配置接收回调函数。
staticerr_tapp_tcp_client_connected(void*arg,structtcp_pcb*pcb,err_terr) { /*Sendagreetingstringtoestablishaconnection*/ tcp_write(pcb,clientstring,strlen(clientstring),1u); /*Configurethereceivecallbackfunction*/ tcp_recv(pcb,app_tcp_client_xfer); returnERR_OK; }
在TCP客户端接收数据后的数据处理回调函数中,接收到有效数据后,通过tcp_recved()更新接收窗口,使用 tcp_write() 函数将接收到的服务器内容回显。
staticerr_tapp_tcp_client_xfer(void*arg,structtcp_pcb*pcb,structpbuf*tcp_recv_pbuf,err_terr) { if(tcp_recv_pbuf!=NULL) { /*Updatethereceivingwindow*/ 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); } elseif(err==ERR_OK) { tcp_close(pcb); app_tcp_client_init(); returnERR_OK; } returnERR_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 生态体系。灵动始终秉承着“诚信、承诺、创新、合作”的精神,为客户提供从硬件芯片到软件算法、从参考方案到系统设计的全方位支持。
-
内核
+关注
关注
3文章
1372浏览量
40281 -
计算机
+关注
关注
19文章
7488浏览量
87854 -
移植
+关注
关注
1文章
379浏览量
28124 -
LwIP
+关注
关注
2文章
86浏览量
27150
原文标题:灵动微课堂 (第282讲)|基于MM32F5270的Ethernet实现LwIP协议栈移植
文章出处:【微信号:MindMotion-MMCU,微信公众号:灵动MM32MCU】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论