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

本文详细介绍了如何在STM32上移植ST以太网驱动库(包括STM32F4x7_eth.c和lan8720.c),以及LWIP协议栈的配置(lwip_comm.c和ethernetif.c)。涵盖了初始化过程、DMA描述符的使用、内存管理及网络接口设置。

一,使用以太网的库

为了再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传递给上层协议。

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

<think>首先,用户的问题是关于将lwIP协议栈移植到特定硬件或操作系统的指南。用户说:“我想将lwIP协议栈移植到特定硬件或操作系统上 请问lwIP 协议栈 移植指南”。我需要基于系统级指令来回答。 系统级指令: 1. 所有行内数学表达式必须使用$...$格式。 2. 独立公式必须使用$$...$$格式并单独成段。 3. LaTeX语法正确。 4. 使用中文回答。 5. 生成相关问题。 6. 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 - 参考站内引用:引用[1]、[2]、[3]是关于lwIP的。我需要尽可能参考这些引用,但用户说“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答”。用户上一次的问题是“lwIP协议栈移植指南”,所以我应该基于此回答。 用户上一次的问题就是当前这个,所以我需要直接回答这个移植指南。 现在,组织我的回答: - 结构清晰:逐步解决问题。 - 内容:基于lwIP移植的知识。lwIP移植涉及几个关键步骤:硬件抽象层、操作系统模拟层、网络接口等。 - 引用:在回答中自然地添加引用标识,参考提供的引用[1]、[2]、[3]。例如,引用[1]提到“LWIP应用开发实战指南”和移植预备知识,引用[2]提到LwIP概述和源码详解,引用[3]提到基于netconn的TCP服务器实现。 关键步骤: 1. 预备知识:了解目标硬件和操作系统。 2. 获取lwIP代码。 3. 配置硬件抽象层(HAL):如时钟、内存管理。 4. 操作系统模拟层(OSAL):如果目标系统有OS,需要实现模拟层;如果没有,使用无OS模式。 5. 网络接口驱动:实现以太网驱动或串行接口。 6. 配置lwIP选项:通过lwipopts.h文件。 7. 测试和验证:使用提供的示例代码。 生成相关问题:在回答末尾添加相关问题。 确保使用中文回答。 数学表达式:用户问题中没有明显的数学内容,但如果有,比如提到性能公式,我应该使用$...$。但在移植指南中,可能不涉及数学,所以可能不需要。 现在,构建回答: - 开头:介绍lwIP移植的基本概念。 - 步骤:逐步列出移植步骤。 - 结尾:添加相关问题。 - 引用:在适当的地方添加引用标识。例如,提到预备知识时引用[1],提到源码时引用[2],提到示例代码时引用[3]。 相关问题示例: 1. 如何优化lwIP在资源受限设备上的性能? 2. lwIP移植常见问题及解决方法? 3. lwIP在不同操作系统上的移植差异? 完整回答结构: 1. 标题或开头:lwIP协议栈移植指南 2. 步骤: a. 预备工作 b. 硬件抽象层实现 c. 操作系统模拟层实现 d. 网络接口驱动开发 e. 配置和编译 f. 测试 3. 结尾:相关问题</think>### lwIP协议栈移植指南 将lwIP(轻量级TCP/IP协议栈)移植到特定硬件或操作系统需要系统化的步骤,以确保网络通信功能正常运行。以下是基于官方文档和实践经验的逐步指南(以嵌入式系统为例)。移植的核心是**实现硬件抽象层(HAL)、操作系统模拟层(OSAL)和网络接口驱动**,同时配置lwIP选项文件。参考lwIP官方文档和开源社区最佳实践[^1][^2]。 #### 步骤1: 预备工作与源码准备 - **了解目标环境**:确认硬件(如MCU型号、以太网控制器)和操作系统(如FreeRTOS、裸机无OS)。lwIP支持无OS模式或与RTOS集成[^1]。 - **获取源码**:从[官方GitHub仓库](https://github.com/lwip-tcpip/lwip)下载最新版本(如v2.1.3),包含核心协议栈和示例。 - **阅读文档**:重点研究《LwIP移植预备知识》和《LwIP协议栈源码详解》,理解内存管理(`mem.c`)和线程模型[^1][^2]。 #### 步骤2: 实现硬件抽象层(HAL) 硬件相关代码需适配目标平台: - **时钟初始化**:配置系统时钟(单位:毫秒),用于TCP超时处理。例如,在STM32中实现`sys_now()`函数: ```c uint32_t sys_now(void) { return HAL_GetTick(); // 返回系统tick值 } ``` - **内存管理**:修改`mem.c`以使用硬件内存池(如静态数组或堆分配),避免动态内存碎片。 - **调试输出**:重定向`printf`到串口(例如UART),便于日志跟踪。 #### 步骤3: 实现操作系统模拟层(OSAL) 若目标系统有OS(如FreeRTOS),需模拟lwIP所需的线程和同步机制: - **创建任务**:实现`sys_thread_new()`函数,启动lwIP核心线程(如`tcpip_thread`)。 - **信号量和邮箱**:提供`sys_sem_new()`、`sys_mbox_new()`等函数,使用OS原语(如FreeRTOS队列)。 - **无OS模式**:如果裸机运行,需在循环中调用`sys_check_timeouts()`处理超时事件[^2]。 #### 步骤4: 开发网络接口驱动 实现以太网或串行接口的数据收发: - **以太网驱动**: - 初始化PHY芯片(如通过SPI或RMII)。 - 实现`low_level_output()`和`low_level_input()`函数,处理数据包发送/接收。 - 注册接口:调用`netif_add()`,例如: ```c struct netif my_netif; netif_add(&my_netif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input); ``` - **其他接口**:如Wi-Fi或PPP(串行),需适配`ppp.c`或自定义驱动。 #### 步骤5: 配置lwIP选项 通过`lwipopts.h`文件定制协议栈行为(覆盖默认`opt.h`): - **关键配置项**: ```c #define LWIP_DHCP 1 // 启用DHCP客户端 #define MEM_SIZE 16000 // 内存池大小(根据硬件调整) #define LWIP_TCP 1 // 启用TCP协议 #define LWIP_UDP 1 // 启用UDP协议 #define PBUF_POOL_SIZE 16 // 数据包缓冲区数量 ``` - **优化建议**:减少`MEM_SIZE`以节省资源,或启用`LWIP_STATS`监控性能[^2]。 #### 步骤6: 编译、测试与验证 -
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值