基于MM32F5270 MCU的Ethernet实现LwIP协议栈移植
LwIP简介
LwIP是轻量化的TCP/IP协议,由瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。LwIP具有高度可移植性、代码开源,提供了三种编程接口(API):RAW API、NETCONN API 和 Socket API,用于与TCP/IP代码进行通信。
通过官网(http://savannah.nongnu.org/projects/lwip/)可获取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中。
1.api
NETCONN API和Socket API相关的源文件,只有在操作系统的环境中,才能被编译
2.apps
应用程序的源文件,包括常见的应用程序,如httpd、mqtt、tftp、sntp、snmp等
3.core
LwIP的内核源文件
4.include
LwIP所有模块对应的头文件
5.netif
与网卡移植有关的文件
图1 LwIP-2.1.2源码包
移植接口解析
LwIP使用数据结构体netif来描述网卡,并提供统一接口,需要与以太网底层驱动接口函数结合使用,例如底层驱动负责完成网卡的初始化、网卡的数据收发等,当LwIP内核需要发送一个数据包时,就会通过LWIP提供的接口函数去调用底层网卡的发送函数,将数据由硬件接口与软件内核衔接在一起。
contrib文件中包含部分可使用的网卡移植模板文件,其中ethernetif.c文件(contrib-2.1.0\examples\ethernetif目录下的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()函数。
1.ethernetif_init()
LwIP中默认的网卡初始化函数,内部封装了low_level_init()函数
2.ethernetif_input()
该函数用于接收网卡数据,内部封装了low_level_input()函数,在接收完毕时,将数据通过pbuf递交给上层。
3.low_level_init()
low_level_init()函数主要是根据实际情况对网卡进行一系列的初始化工作,例如:初始化MAC地址、长度, 设置最大传输包的大小,设置网卡的属性字段等功能。
该函数中需要调用以太网底层驱动中的相关初始化函数,以 LibSamples为例,该函数需要调用以太网底层驱动 hal_enet.c/.h 的 PHY、MAC、DMA相关初始化函数并进行配置。
4.low_level_output()
该函数用于实现网卡发送数据,是一个底层驱动函数,需根据以太网底层驱动进行相应修改,若想通过一个网卡发送数据,则需要将该数据传入LwIP内核中,经过层层封装最后存储在pbuf数据包中,需注意pbuf以链表的形式存在,数据发送时是以一整个数据包全部发送的。
5.low_level_input()
low_level_input()函数用于从网卡中接收一个数据包,并将该数据包封装在pbuf中递交给上层,该函数需要调用以太网底层驱动中的接收函数。
移植LwIP协议栈
基于LibSamples的以太网驱动对LwIP进行移植,需先将LwIP源文件中的部分文件添加到LibSamples中,如: src 源文件、 include 头文件。
若想令LwIP运行,还需补充contrib文件中部分内容,如图2所示,由于部分源文件中使用头文件写法为”arch/xx”,因此,在src文件下新建arch文件,并将需要修改的模板文件及contrib中的部分接口文件放入arch文件中。
1.ethernetif.c网卡移植模板文件
2.cc.h文件主要完成协议栈内部使用的数据类型的定义
3.lwipopts.h文件包含了用户对协议栈内核参数进行的配置,若未在lwipopts.h文件中进行配置,则LwIP会使用opt.h中的默认参数
4.perf.h文件是实现与系通通计和测量相关的功能,若未使用该功能,则无需修改
5.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样例所使用到的函数包括:
1.tcp_new()
创建一个TCP的PCB控制块
2.tcp_connect()
连接远端主机
3.tcp_err()
控制块err字段注册的回调函数,遇到错误时被调用
4.tcp_write()
构造一个报文并放入控制块的发送缓冲队列中
5.tcp_recv()
控制块rev字段注册的回调函数,当接收到新数据是被调用
6.tcp_recved()
当程序处理完数据后调用该函数,通知内核更新接收窗口
7.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样例实验现象
注意事项
在官网(http://free.cmsoft.cn/reslink.php?id=205)下载网络调试助手NetAssist并安装,用于后续的样例功能验证。
打开电脑终端(WIN+R键,输入CMD),然后输入指令` ipconfig/all `,查看本机的以太网IP地址。
在配置 IP 地址和端口号时,当连接了WIFI后需要注意我们选用的是以太网的IP地址,而非WLAN的IP地址。
在终端中输入命令 `netstat -na` 获取本地开放端口。
- |
- +1 赞 0
- 收藏
- 评论 0
本文由赵优秀转载自MindMotion(灵动MM32MCU公众号),原文标题为:灵动微课堂 (第282讲)|基于MM32F5270的Ethernet实现LwIP协议栈移植,本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。
相关研发服务和供应服务
相关推荐
Keil分散加载文件浅析
分散加载文件(scatter file)是一个文本文件,用于描述 ARM 链接器生成映像文件所需要的信息,在一些应用场景中嵌入式系统可能会使用分散加载。本章节简要介绍了分散加载文件的基本概念和语法,旨在对分散加载文件有初步认识。
设计经验 发布时间 : 2023-12-28
灵动微电子MM32F0160系列MCU FlexCAN-FD通信应用指南
MM32F0160系列MCU具有一个 FlexCAN 模块,该模块遵循 ISO 11898-1 标准、 CAN FD 和 CAN 2.0B 协议规范,不仅兼容传统CAN,还支持CAN FD模式。本章节初步学习使用MM32F0160 FlexCAN-FD接口实现CAN FD通信,相关例程参考灵动官网的LibSamples或在此基础上修改。
设计经验 发布时间 : 2024-01-07
使用灵动MM32F5270 MCU UART配置实现LIN通信
本文主要简述MM32F5270 UART是如何实现LIN通信的。从LIN驱动程序、 主机程序、从机程序和验证等方面来讲代码实现。
设计经验 发布时间 : 2024-02-23
“MindMotion·Star“ Series: Major Member MM32F5260 Officially in Mass Production
At the Shenzhen Elexcon 2024 in late August, MindMotion Microelectronics officially unveiled the “MindMotion·Star” series of high-performance MM32 MCU products, featuring four sub-series: MM32F3, MindMotion Dubhe MM32F5, MindMotion Alioth MM32G5, and MindMotion Phecda MM32H5. MindMotion is excited to announce the formal launch of a key member of the MindMotion Dubhe sub-series: the MM32F5260.
产品 发布时间 : 2024-10-25
HVAC FLAP以MM32SPIN023C为主控设计提供All in one方案
暖通空调 (HVAC) 系统旨在调节、加热、冷却、通风、清洁或除湿车厢内的空气质量 (IAQ)。暖通空调系统由前端的传感器和机械/电子开关、鼓风机电机、执行器(用于新鲜空气循环控制、气流控制和温度控制)以及制冷装置组成,这些装置将空气输送到后端的座舱,以确保驾驶员和乘客的热舒适性。HVAC FLAP以 MM32SPIN023C 为主控。
应用方案 发布时间 : 2024-03-25
MindMotion MM32H5480 Triumphs with “2024 Hardcore MCU Chip Award“
MindMotion Microelectronics, as a frontrunner in the domestic market for general-purpose 32-bit MCU products and solutions, proudly secured the “2024 Hardcore MCU Chip Award“ for its high-performance product, the MM32H5480.
产品 发布时间 : 2024-10-23
MindMotion(灵动微) MCU选型表
32位高性能,高性价比MCU,Core核心有M0、M0+、2XM0、M3、STAR-MC1,Flash存储容量范围 16KB~2048KB,RAM存储容量范围 2KB~128KB,工作电压:1.8~48V,CPU频率(MHz):48~180MHz,GPIO 端口数(个):6~86,LQFP/TSSOP/QFN等多种封装形式。
产品型号
|
品类
|
内核
|
管脚数(个)
|
工作温度(℃)
|
CPU频率(MHz)
|
工作电压(V)
|
GPIO 端口数(个)
|
Flash(KB)
|
SRAM (KB)
|
封装/外壳/尺寸
|
MM32F0163D7PV
|
32位MCU
|
M0
|
64
|
-40℃~105℃
|
96MHz
|
2.0~5.5V
|
57
|
128KB
|
16KB
|
LQFP64
|
选型表 - MindMotion 立即选型
【IC】“灵动·星”系列MM32高性能MCU产品搭载Star-MC1提供强劲内核动力,性能提升20%
“灵动·星”系列 MM32 高性能 MCU 产品,搭载安谋科技(Arm China)Star-MC1 内核性能,其1.5 DMIPS / MHz,提升 20% ,4.02 CoreMark / MHz,提升20% 。目前包含 4 个子系列: MM32F3,灵动·天枢 MM32F5,灵动·玉衡 MM32G5,灵动·天玑 MM32H5,为用户提供灵活、丰富的选择。
产品 发布时间 : 2024-10-23
MindMotion(灵动微)MM32系列32位MCU选型指南
描述- 灵动微电子成立于2011年,是中国本土领先的通用32位MCU产品及解决方案供应商。灵动微电子的 MCU 产品以 MM32 为标识,基于 Arm Cortex-M 系列内核,自主研发软硬件和生态系统。
型号- MM32G0163D4PV,MM32L0XX,MM32F0144C6PM,MM32F5230,MM32SPIN580C,MM32G0020,MM32G0140,MM32L073PF,MM32F0133C4Q,MM32SPIN080GN,MM32SPIN560CM,MM32SPIN023C,MM32F0133C4P,MM32F5287L8PV,MM32SPIN533AM,MM32F0162D7P,MM32F0121C6P,MM32F0163D4Q,MM32SPIN0230,MM32SPIN080C,MM32F0141C1T,MM32L062NT,MM32F0020B1N,MM32F0144C1TV,MM32F5330,MM32G0001,MM32G0120,MM32F0144C1TM,MM32F0163D7P,MM32SPIN030CN,MM32G0121C1TV,MM32F0010A1TV,MM32SPIN07PF,MM32SPIN05PFOP,MM32SPIN0230B1TV,MM32SPIN37PSD,MM32G0001A6T,MM32F3270,MM32F0121C4P,MM32F0121C4Q,MM32F5277E9PV,MM32F5287L9PV,MM32F0163D6P,MM32F5333D6PV,MM32SPIN0280DAPV,MM32F0121C4N,MM32SPIN080G,MM32SPIN560,MM32F0144C4PM,MM32F0144C4PV,MM32F5280,MM32SPIN06NT,MM32F0140,MM32SPIN560C,MM32F0020,MM32G5333D6QV,MM32F0162D4Q,MM32F0050C1TV,MM32F0273D6P,MM32SPIN0230B3NV,MM32SPIN06PF,MM32G0144C4QV,MM32SPIN0280,MM32L052NT,MM32F5270,MM32F0144C4P,MM32F5277E8PV,MM32F031C6T6,MM32F0130,MM32F0010,MM32F0020B1TV,MM32F5333D7PV,MM32G0144C4PV,MM32SPIN080CN,MM32F0144C4Q,MM32SPIN060G,MM32L0136C7P,MM32F0133C7P,MM32SPIN05NW,MM32F5331D3NV,MM32F0040B1T,MM32SPIN05NT,MM32G0001A6T1V,MM32SPIN06PT,MM32F0120,MM32F0162D6P,MM32F5331D3NM,MM32F003NW,MM32G0160,MM32SPIN27NF,MM32F0163D4QM,MM32SPIN05PF,MM32F031,MM32F3273G6P,MM32F0163D4QV,MM32L0136B6P,MM32G0001A1TV,MM32F0133C6P,MM32SPIN0280D7PV,MM32F5277E7PV,MM32F0144C6P,MM32SPIN222C,MM32F0144C6PV,MM32F0010A6T,MM32G0001A1T,MM32F3273G7P,MM32L0130,MM32G0001A1N,MM32SPIN05PT,MM32G0121C4PV,MM32F0010A1T,MM32F5233D7PV,MM32SPIN27PF,MM32F0010A1N,MM32G5330,MM32SPINEBK,MM32SPIN07,MM32F3273G8P,MM32SPIN160C,MM32SPIN06,MM32F0040B1N,MM32SPIN0230B1NV,MM32L0136C6P,MM32SPIN05,MM32L0020,MM32F0050,MM32SPIN0280D6PV,MM32SPIN533,MM32F031K6U6,MM32F3273G9P,MM32F0163D6PM,MM32SPIN040C,MM32G0020B1T,MM32F0163D6PV,MM32F003,MM32SPIN37,MM32G0020B1N,MM32F0273D8P,MM32F0160,MM32L0136C3T,MM32F0040,MM32G0001A1NV,MM32SPIN0280D6QV,MM32F0144C1T,MM32F103CBT6,MM32F5233D6PV,MM32F0144C4QM,MM32F3273E6P,MM32SPIN27PQ,MM32SPIN27PS,MM32SPIN27PT,MM32L0136C4N,MM32SPIN27,MM32F0020B1NV,MM32F0270,MM32G0121C4QV,MM32F031F6U6,MM32F0273D7P,MM32F003TW,MM32G0144C1TV,MM32F3273E7P,MM32F0141B1T,MM32G0163D6PV,MM32F0163D7PM,MM32SPIN030C,MM32F0163D7PV,MM32F0131C7P,MM32F103,MM32SPIN05TW,MM32F031Y6Y6,MM32F0141C4P,MM32F0141C4Q,MM32F0141C4N,MM32F031K6T6,MM32F0010A1NV,MM32F0131C6P,MM32SPIN422C,MM32F0020B1T,MM32SPIN0280D4PV,MM32L0020B1T,MM32F103RBT6,MM32L0020B1N,MM32F031F6P6,MM32F0121C1N,MM32F0050C1NV,MM32F5333D4QM,MM32G0001A6TV,MM32SPIN360C,MM32,MM32F5333D4QV,MM32F0141C6P,MM32F0141B4P,MM32F0131C4P,MM32F0131C4Q,MM32SPIN0230B3TV,MM32SPIN533A,MM32SPIN033A,MM32F5287L7PV,MM32F5233D4QV
【IC】灵动发布全新入门级32位MCU MM32G0001系列,内置时钟全温度范围内偏差不超过±2%
灵动股份推出全新超值型MM32G0001系列MCU。2023年初,灵动首次发布了其主打高性价比的MM32G系列,目前已陆续推出了G0140,G0160和G5330系列产品。为进一步丰富MM32G系列产品组合,灵动和上下游合作伙伴通力合作,打造出全新入门级超值型MM32G0001系列MCU。
新产品 发布时间 : 2023-07-01
【IC】灵动微电子高性能通用32位MCU产品MM32H5480荣获“2024年度硬核MCU芯片奖”
近日,第六届硬核芯生态大会在深圳举办,同期2024年度硬核中国芯获奖榜单正式发布。作为国内领先的本土通用32位MCU产品及解决方案供应商,MindMotion灵动微电子凭借高性能产品MM32H5480斩获“2024年度硬核MCU芯片奖”。
原厂动态 发布时间 : 2024-10-18
【经验】基于MM32F0163D7P的USB Audio Class(UAC)音频设备移植教程
在上一节我们在MM32F0163D7P 平台上成功的移植了TinyUSB,基于这个平台,今天我们来实现一个 uac2_headset 音频设备,这个设备支持基础的录音和放音功能,如果要支持音量调节/静音功能,还需要再添加一个 HID 变成复合设备。
设计经验 发布时间 : 2023-08-05
【IC】灵动新推出32位MCU MM32F0120,主频高达72MHz,提供64KB Flash和8KB RAM
灵动微电子发布的MM32F0120,搭载Cortex M0内核,主频高达72MHz,提供64KB Flash和8KB RAM,最高支持48pin封装。适用于屏显控制、电动玩具、电源管理、马达控制、无线快充等多种应用场合。
产品 发布时间 : 2024-09-11
【IC】灵动微电子发布灵动·天枢子系列新成员:MM32F5260,搭载国产Star-MC1内核,内核动力强劲
在8月底的深圳嵌入式电子展上,灵动微电子正式发布了“灵动星”系列MM32高性能MCU产品,包含4个子系列:MM32F3,灵动·天枢 MM32F5,灵动·玉衡MM32G5,灵动·天玑MM32H5。9月30日灵动微电子正式发布灵动·天枢子系列的重磅成员:MM32F5260。
产品 发布时间 : 2024-10-10
电子商城
现货市场
服务
可定制显示屏的尺寸0.96”~15.6”,分辨率80*160~3840*2160,TN/IPS视角,支持RGB、MCU、SPI、MIPI、LVDS、HDMI接口,配套定制玻璃、背光、FPCA/PCBA。
最小起订量: 1000 提交需求>
可烧录IC封装SOP/MSOP/SSOP/TSOP/TSSOP/PLCC/QFP/QFN/MLP/MLF/BGA/CSP/SOT/DFN;IC包装Tray/Tube/Tape;IC厂商不限,交期1-3天。支持IC测试(FT/SLT),管装、托盘装、卷带装包装转换,IC打印标记加工。
最小起订量: 1pcs 提交需求>
登录 | 立即注册
提交评论