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

一,使用以太网的库

为了再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,&ethernetif_init,&ethernet_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&ETH_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传递给上层协议。

总之还是这图
在这里插入图片描述
在这里插入图片描述

  • 42
    点赞
  • 118
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
移植STM32平台的LWIP功能之前,需要进行一些准备工作。首先,您需要下载所需的资料,包括lwip-2.1.2、contrib-2.1.0和STM32F4x7_ETH_LwIP_V1.1.1。您可以参考《基于STM32移植LWIP的资料准备》来获取这些资料。另外,您还需要准备好已经在STM32移植好的FreeRTOS的工程。 移植的目标平台是STM32F429,您可以参考STM32官网上提供的STM32F4x7微控制器的LwIP TCP/IP协议栈的演示代码,将其移植到目标平台。 在移植LWIPSTM32平台上时,需要完成以下四个主要部分: 1. RTOS:在STM32上运行FreeRTOS,并为LWIP协议栈提供Mutex、Mailbox和Create Thread等API接口。 2. Network System Config:对LWIP协议栈的系统设置进行配置。 3. LWIP Stack:将LWIP 2.1.2 TCP/IP协议栈的源码添加到工程中。 4. Hardware Driver:主要是设置STM32平台ETH接口的驱动层,例如GPIOs、时钟、MAC和DMA等。 通过完成这些步骤,您就可以在STM32平台上成功移植LWIP功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [【FreeRTOS】基于STM32移植LWIP 2.1.2详细步骤](https://blog.csdn.net/ZHONGCAI0901/article/details/109579940)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值