一文搞定stm32移植LWIP及代码逻辑

一文搞定stm32移植LWIP及代码逻辑

文章目录

一,使用以太网的库二,ST以太网驱动库的移植1,stm32f4x7_eth.c2,lan8720.c

三,移植LWIP协议栈1,lwip_comm.c2,ethernetif.c

四,逻辑梳理1,数据输出2,数据输入

一,使用以太网的库

为了再stm32中使用以太网进行通信,需要使用两个库的代码。 如图是stm32和互联网通信的模型,其中的lwip协议栈和驱动就是我们要移植的代码。 https://wws.lanzous.com/b01i3kcd,密码cmvn。

其中ST提供的以太网库负责处理配置stm32以太网功能,lan8720驱动phy芯片。而LWIP则负责在软件上实现网络层,传输层等上层协议。

二,ST以太网驱动库的移植

首先我使用的是正点原子的stm32f4探索者,开发板使用的phy芯片为LAN8720。下载好以太网驱动库和LAN8720的驱动文件。然后添加到工程中。

以太网驱动库 lan8720驱动

文件说明:

1,stm32f4x7_eth.c

stm32f4x7_eth.c中,主要使用的函数有:

void ETH_DeInit(void);

uint32_t ETH_Init(ETH_InitTypeDef* ETH_InitStruct, uint16_t PHYAddress); //初始化以太网

void ETH_StructInit(ETH_InitTypeDef* ETH_InitStruct); //结构体参数初始化

void ETH_SoftwareReset(void); //重置以太网mac的寄存器

void ETH_Start(void); //开启以太网功能

void ETH_Stop(void);

uint32_t ETH_GetRxPktSize(ETH_DMADESCTypeDef *DMARxDesc); //读取以太网接收到的数据包大小

另外还需稍微了解DMA描述符。

以太网外设接收到数据后会将数据放到DMA描述符的缓存中,描述符是链表的结构。发送数据时,软件将数据放入描述符的缓存区。发送和接收共两条描述符链。其定义如下,但被注释了。

2,lan8720.c

主要函数

u8 LAN8720_Init(void); //phy芯片初始化

FrameTypeDef ETH_Rx_Packet(void); //从以太网接收一个数据包

u8 ETH_Tx_Packet(u16 FrameLength); //发送一个数据包

u32 ETH_GetCurrentTxBuffer(void); //获取当前发送描述符的buff地址

u8 ETH_Mem_Malloc(void); //为以太网描述符分配内存

//以太网dma接收中断服务函数

void ETH_IRQHandler(void)

{

while(ETH_GetRxPktSize(DMARxDescToGet)!=0)

{

lwip_pkt_handle(); //通知lwip处理接收的数据

}

ETH_DMAClearITPendingBit(ETH_DMA_IT_R);

ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);

}

注意:文件中重新声明了以上四个变量,并在函数ETH_Mem_Malloc()中为四个变量申请了内存。原因是这四个变量会占用芯片较多的内存,使用动态内存分配,为变量分配了片外RAM的内存,能提供运行速度。

u8 ETH_Mem_Malloc(void)

{

DMARxDscrTab=mymalloc(SRAMIN,ETH_RXBUFNB*sizeof(ETH_DMADESCTypeDef));

DMATxDscrTab=mymalloc(SRAMIN,ETH_TXBUFNB*sizeof(ETH_DMADESCTypeDef));

Rx_Buff=mymalloc(SRAMIN,ETH_RX_BUF_SIZE*ETH_RXBUFNB);

Tx_Buff=mymalloc(SRAMIN,ETH_TX_BUF_SIZE*ETH_TXBUFNB);

if(!DMARxDscrTab||!DMATxDscrTab||!Rx_Buff||!Tx_Buff)

{

ETH_Mem_Free();

return 1;

}

return 0;

}

三,移植LWIP协议栈

下载并添加lwip源码到工程中。

文件说明

1,lwip_comm.c

主要函数:

//初始化lwip

u8 lwip_comm_init(void)

{

struct netif *Netif_Init_Flag;

struct ip_addr ipaddr;

struct ip_addr netmask;

struct ip_addr gw;

if(ETH_Mem_Malloc())return 1; //为以太网dma描述符分配内存

if(lwip_comm_mem_malloc())return 1; //为lwip分配内存

if(LAN8720_Init())return 2; //phy芯片初始化

lwip_init(); //lwip协议栈初始化

lwip_comm_default_ip_set(&lwipdev); //暂时设置默认ip

//以下代码是根据情况获取ip地址

#if LWIP_DHCP

ipaddr.addr = 0;

netmask.addr = 0;

gw.addr = 0;

#else

IP4_ADDR(&ipaddr,lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);

IP4_ADDR(&netmask,lwipdev.netmask[0],lwipdev.netmask[1] ,lwipdev.netmask[2],lwipdev.netmask[3]);

IP4_ADDR(&gw,lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);

#endif

//到此,获取到ip地址,创建一个netif接口,并为接口添加ip地址等,添加接口初始化函数和输入函数

Netif_Init_Flag=netif_add(&lwip_netif,&ipaddr,&netmask,&gw,NULL,ðernetif_init,ðernet_input);//

#if LWIP_DHCP

lwipdev.dhcpstatus=0;

dhcp_start(&lwip_netif);

#endif

if(Netif_Init_Flag==NULL)

return 3;

{

//完成netif设置

netif_set_default(&lwip_netif);

netif_set_up(&lwip_netif);

}

return 0;

}

该函数主要是调用其他初始化函数,完成硬件和软件的初始化配置。重点是netif_add创建了网络接口lwip_netif,并为该网络接口设置ip和以太网输入函数ethernet_input()。

在netif_add()中,调用ethernetif_init()函数对lwip_netif进行初始化

err_t ethernetif_init(struct netif *netif)

{

LWIP_ASSERT("netif!=NULL",(netif!=NULL));

#if LWIP_NETIF_HOSTNAME //LWIP_NETIF_HOSTNAME

netif->hostname="lwip"; //初始化名称

#endif

netif->name[0]=IFNAME0; //初始化变量netif的name字段

netif->name[1]=IFNAME1; //在文件外定义这里不用关心具体值

netif->output=etharp_output;//IP层发送数据包函数,由lwip提供,功能如下

netif->linkoutput=low_level_output;//ARP模块发送数据包函数

low_level_init(netif); //底层硬件初始化函数

return ERR_OK;

}

该函数为lwip_netif 设置了以太网输出函数etharp_output(),和ARP发送函数low_level_output(),这些函数是一个网络接口功能实现的逻辑。如下图:

在以太网dma中断中还会调用下面的函数,其实就是调用ethernetif_input(),这个函数待会分析

void lwip_pkt_handle(void)

{

ethernetif_input(&lwip_netif);

}

void lwip_periodic_handle(); //lwip轮询

void lwip_dhcp_process_handle(void) //dhcp处理任务

2,ethernetif.c

上文提到的lwip_netif功能实现的四个重要函数,在此分析

//从dma描述符缓存中读取数据到pbuf并返回pbuf

static struct pbuf * low_level_input(struct netif *netif)

{

struct pbuf *p, *q;

u16_t len;

int l =0;

FrameTypeDef frame;

u8 *buffer;

p = NULL;

frame=ETH_Rx_Packet();//调用以太网接收函数,获取一帧数据包

len=frame.length;//得到包大小

buffer=(u8 *)frame.buffer;//得到包数据地址

p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL);//内存池分配新的pbuf

if(p!=NULL)

{

//将数据包复制到q

for(q=p;q!=NULL;q=q->next)

{

memcpy((u8_t*)q->payload,(u8_t*)&buffer[l], q->len);

l=l+q->len;

}

}

frame.descriptor->Status=ETH_DMARxDesc_OWN;//设置Rx描述符OWN位,buffer重归ETH DMA

if((ETH->DMASRÐ_DMASR_RBUS)!=(u32)RESET)//当Rx Buffer不可用位(RBUS)被设置的时候,重置它.恢复传输

{

ETH->DMASR=ETH_DMASR_RBUS;//重置ETH DMA RBUS位

ETH->DMARPDR=0;//恢复DMA接收

}

return p;

}

err_t ethernetif_input(struct netif *netif)

{

err_t err;

struct pbuf *p;

p=low_level_input(netif); //读取输入的一帧pbuf数据

if(p==NULL) return ERR_MEM;

err=netif->input(p, netif); //调用ethernet_input()将pbuf交给lwip内核

if(err!=ERR_OK)

{

LWIP_DEBUGF(NETIF_DEBUG,("ethernetif_input: IP input error\n"));

pbuf_free(p);

p = NULL;

}

return err;

}

static err_t low_level_output(struct netif *netif, struct pbuf *p)

{

u8 res;

struct pbuf *q;

int l = 0;

u8 *buffer=(u8 *)ETH_GetCurrentTxBuffer(); //获取当前要发送的DMA描述符中的缓冲区地址

//将pbuf中的数据复制到dma描述符

for(q=p;q!=NULL;q=q->next)

{

memcpy((u8_t*)&buffer[l], q->payload, q->len);

l=l+q->len;

}

res=ETH_Tx_Packet(l); //调用以太网外设的发送函数发送数据

if(res==ETH_ERROR)return ERR_MEM;//返回错误状态

return ERR_OK;

}

四,逻辑梳理

1,数据输出

当lwip输出数据时,先由上层调用netif->output(),将pbuf传递给netif,netif调用low_level_output()将数据发送到dma描述符,再由硬件发送出去。

2,数据输入

当有数据经过硬件进入stm32的以太网控制器时,会将数据放入dma描述符,并进入dma接收中断,调用low_level_input()将数据复制到pbuf中,netif再调用netif->input()将pbuf传递给上层协议。

总之还是这图

相关灵感

365365094 一刀下去“倾家荡产”!大众抵制的新疆切糕,明码标价仍无人购买
365365094 探秘东莞常平桑拿产业:享受休闲养生,体验高品质服务
365bet足球网开户 乔迁新居请柬邀请函 29

乔迁新居请柬邀请函 29

📅 07-04 👁️ 2253
365365094 雷神G150P-47108G1TG8808G参数

雷神G150P-47108G1TG8808G参数

📅 07-12 👁️ 6397
正规beat365旧版 贵州银行信用卡申请条件和申请方法 线上申请要注意什么
365365094 手把手教你如何开通Facebook Shop商店!如何开通脸书商店?
365bet足球网开户 质因数分解计算器

质因数分解计算器

📅 07-04 👁️ 9020
365365094 湖北作家匪我思存:写书16年,384万微博粉丝,却如履薄冰
正规beat365旧版 微信聊天文字背景怎么设置

微信聊天文字背景怎么设置

📅 07-07 👁️ 1002