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

您所在的位置:网站首页 lwip协议栈移植 一文搞定stm32移植LWIP及代码逻辑

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

2023-09-19 17:17| 来源: 网络整理| 查看: 265

文章目录​​一,使用以太网的库​​​​二,ST以太网驱动库的移植​​​​1,stm32f4x7_eth.c​​​​2,lan8720.c​​​​三,移植LWIP协议栈​​​​1,lwip_comm.c​​​​2,ethernetif.c​​​​四,逻辑梳理​​​​1,数据输出​​​​2,数据输入​​

一,使用以太网的库

为了再stm32中使用以太网进行通信,需要使用两个库的代码。

如图是stm32和互联网通信的模型,其中的lwip协议栈和驱动就是我们要移植的代码。

​​点我下载移植所需的文件​​,密码cmvn。

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

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

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

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

以太网驱动库

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

lan8720驱动

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

文件说明:

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描述符的缓存中,描述符是链表的结构。发送数据时,软件将数据放入描述符的缓存区。发送和接收共两条描述符链。其定义如下,但被注释了。

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

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

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

主要函数:

//初始化lwipu8 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(),这些函数是一个网络接口功能实现的逻辑。如下图:

一文搞定stm32移植LWIP及代码逻辑_数据_06

在以太网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并返回pbufstatic 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传递给上层协议。

总之还是这图

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

一文搞定stm32移植LWIP及代码逻辑_数据_08



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3