网络通信---MCU移植LWIP

news2025/2/23 18:28:04

使用的MCU型号为STM32F429IGT6,PHY为LAN7820A
目标是通过MCU的ETH给LWIP提供输入输出从而实现基本的Ping应答
在这里插入图片描述
OK废话不多说我们直接开始

下载源码

  1. LWIP包源码:lwip源码
    -在这里下载
    在这里插入图片描述
  2. ST官方支持的ETH包:ST-ETH支持包
    这里下载
    在这里插入图片描述

创建工程

这里我使用我的STM32外扩RAM工程,若是手里无有外扩内存的板卡也可以直接使用点灯工程
在这里插入图片描述

加入ETH支持包

将刚刚下载的ETH支持包里

STM32F4x7_ETH_LwIP_V1.1.1\Libraries\STM32F4x7_ETH_Driver

目录下有/inc /src 两个文件夹,分别存放着ETH驱动的源文件和头文件
把他们对应的加入源码工程中

\Libraries\STM32F4xx_StdPeriph_Driver

的 /inc 和 /src中
然后将stm32f4x7_eth_conf_temp.h重命名为stm32f4x7_eth_conf.h
在keil工程中加入他们!

修改stm32f4x7_eth_conf.h

直接编译会报错,因为没ETH使用的delay函数,这里直接不使用ETH的delay,直接注释掉USE_Delay
在这里插入图片描述

修改stm32f4x7_eth.c

在这个文件的一开始会发现在这里插入图片描述
搜索这里的宏定义是发现这些描述符和Buffer占用了大量的空间,描述符占用了320byte,因为后面用DMA搬运所以使用片内RAM,而这里的Buffer一共占用了大约38Kbyte,这非常大,所以一般放在外部RAM,这里我使用的片外SRAM是IS42S16400J 拥有8M内存,所以可以放在片外SRAM,所以这里先注释掉,稍后使用malloc分配内存给它们,如果移植的板卡无片外扩展SRAM则不用管这里,直接放在内部RAM

在这里插入图片描述
然后注释的后面添加指针

ETH_DMADESCTypeDef *DMARxDscrTab;
ETH_DMADESCTypeDef *DMATxDscrTab;
uint8_t *Tx_Buff;
uint8_t *Rx_Buff; 

这里需要使用malloc.c和malloc.h
malloc.c

#include "malloc.h"
#include "stdio.h"

//	 

//内存池(4字节对齐)
#pragma pack(4)
	u8 mem1base[MEM1_MAX_SIZE];
	u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0xD0000000))); //外部SRAM内存池
#pragma pack()

//内存管理表
u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE];													//内部SRAM内存池MAP
u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0xD0000000+MEM2_MAX_SIZE)));	//外部SRAM内存池MAP
//内存管理参数	   
const u32 memtblsize[SRAMBANK]={MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE};	//内存表大小
const u32 memblksize[SRAMBANK]={MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE};					//内存分块大小
const u32 memsize[SRAMBANK]={MEM1_MAX_SIZE,MEM2_MAX_SIZE};							//内存总大小

//内存管理控制器
struct _m_mallco_dev mallco_dev=
{
	mymem_init,							//内存初始化
	mem_perused,						//内存使用率
	mem1base,mem2base,			//内存池
	mem1mapbase,mem2mapbase,//内存管理状态表
	0,0,  		 					//内存管理未就绪
};

//复制内存
//*des:目的地址
//*src:源地址
//n:需要复制的内存长度(字节为单位)
void mymemcpy(void *des,void *src,u32 n)
{
	u8 *xdes = des;
	u8 *xsrc = src;
	while(n--) *xdes++ = *xsrc++;
}

//设置内存
//*s:内存首地址
//c :要设置的值
//count:需要设置的内存大小(字节为单位)
void mymemset(void*s,u8 c,u32 count)
{
	u8 *xs = s;
	while(count--) *xs++=c;
}

//内存管理初始化  
//memx:所属内存块
void mymem_init(u8 memx)
{
	mymemset(mallco_dev.memmap[memx],0,memtblsize[memx]*2); //内存状态表清零
	mymemset(mallco_dev.membase[memx], 0,memsize[memx]);	//内存池所有数据清零  
	mallco_dev.memrdy[memx]=1;								//内存管理初始化OK  
}

//获取内存使用率
//memx:所属内存块
//返回值:使用率(0~100)
u8 mem_perused(u8 memx)  
{  
    u32 used=0;  
    u32 i;  
    for(i=0;i<memtblsize[memx];i++)  
    {  
        if(mallco_dev.memmap[memx][i])used++; 
    } 
    return (used*100)/(memtblsize[memx]);  
} 

//内存分配(内部调用)
//memx:所属内存块
//size:要分配的内存大小(字节)
//返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址 
u32 mymem_malloc(u8 memx,u32 size)  
{  
    signed long offset=0;  
    u16 nmemb;	//需要的内存块数  
		u16 cmemb=0;//连续空内存块数
    u32 i;  
    if(!mallco_dev.memrdy[memx])mallco_dev.init(memx);//未初始化,先执行初始化 
    if(size==0)return 0XFFFFFFFF;//不需要分配
    nmemb=size/memblksize[memx];  	//获取需要分配的连续内存块数
    if(size%memblksize[memx])nmemb++;  
    for(offset=memtblsize[memx]-1;offset>=0;offset--)//搜索整个内存控制区  
    {     
		if(!mallco_dev.memmap[memx][offset])cmemb++;//连续空内存块数增加
		else cmemb=0;								//连续内存块清零
		if(cmemb==nmemb)							//找到了连续nmemb个空内存块
		{
            for(i=0;i<nmemb;i++)  					//标注内存块非空 
            {  
                mallco_dev.memmap[memx][offset+i]=nmemb;  
            }  
            return (offset*memblksize[memx]);//返回偏移地址  
		}
    }  
    return 0XFFFFFFFF;//未找到符合分配条件的内存块  
}  

//释放内存(内部调用) 
//memx:所属内存块
//offset:内存地址偏移
//返回值:0,释放成功;1,释放失败;  
u8 mymem_free(u8 memx,u32 offset)  
{  
	int i;  
  if(!mallco_dev.memrdy[memx])//未初始化,先执行初始化
	{
		mallco_dev.init(memx);    
    return 1;//未初始化  
  }  
  if(offset<memsize[memx])//偏移在内存池内. 
  {  
		int index=offset/memblksize[memx];			//偏移所在内存块号码  
    int nmemb=mallco_dev.memmap[memx][index];	//内存块数量
    for(i=0;i<nmemb;i++)  						//内存块清零
    {  
			mallco_dev.memmap[memx][index+i]=0;  
    }  
    return 0;  
  }else return 2;//偏移超区了.  
}  

//释放内存(外部调用) 
//memx:所属内存块
//ptr:内存首地址 
void myfree(u8 memx,void *ptr)  
{  
	u32 offset;  
	printf("myfree\r\n");	
    if(ptr==NULL)return;//地址为0.  
 	offset=(u32)ptr-(u32)mallco_dev.membase[memx];  
    mymem_free(memx,offset);//释放内存     
}  

//分配内存(外部调用)
//memx:所属内存块
//size:内存大小(字节)
//返回值:分配到的内存首地址.
void *mymalloc(u8 memx,u32 size)  
{  
  u32 offset;  									      
	offset=mymem_malloc(memx,size);  	   			
  if(offset==0XFFFFFFFF)return NULL;  
  else return (void*)((u32)mallco_dev.membase[memx]+offset);  
}  

//重新分配内存(外部调用)
//memx:所属内存块
//*ptr:旧内存首地址
//size:要分配的内存大小(字节)
//返回值:新分配到的内存首地址.
void *myrealloc(u8 memx,void *ptr,u32 size)  
{  
    u32 offset;  
    offset=mymem_malloc(memx,size);  
    if(offset==0XFFFFFFFF)return NULL;     
    else  
    {  									   
	    mymemcpy((void*)((u32)mallco_dev.membase[memx]+offset),ptr,size);	//拷贝旧内存内容到新内存   
        myfree(memx,ptr);  											  		//释放旧内存
        return (void*)((u32)mallco_dev.membase[memx]+offset);  				//返回新内存首地址
    }  
}

malloc.h

#ifndef _MALLOC_H
#define _MALLOC_H
#include "stm32f4xx.h"

#ifndef NULL
#define NULL 0
#endif

//定义三个内存池
#define SRAMIN 	0  //内部内存池
#define SRAMEX 	1  //外部内存池

#define SRAMBANK  2 //定义支持的SRAM块数


//mem1内存参数设定,mem1完全处于内部SRAM里面
#define MEM1_BLOCK_SIZE	32  			//内存块大小为32字节
#define MEM1_MAX_SIZE		30*1024 	//最大管理内存 10k
#define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLOCK_SIZE  //内存表大小

//mem2内存参数设定,mem2处于外部SRAM里面
#define MEM2_BLOCK_SIZE	32  			//内存块大小为32字节
#define MEM2_MAX_SIZE		500*1024 	//最大管理内存 500k
#define MEM2_ALLOC_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLOCK_SIZE  //内存表大小


//内存管理控制器
struct _m_mallco_dev
{
	void (*init)(u8);  		//初始化
	u8 (*perused)(u8); 		//内存使用率
	u8 *membase[SRAMBANK]; //内存池,管理SRAMBANK个区域的内存
	u16 *memmap[SRAMBANK];  //内存状态表
	u8 memrdy[SRAMBANK];   //内存管理是否就绪
};
extern struct _m_mallco_dev mallco_dev;  //在malloc.c里面定义

void mymemset(void *s,u8 c,u32 count);	 //设置内存
void mymemcpy(void *des,void *src,u32 n);//复制内存     
void mymem_init(u8 memx);					 //内存管理初始化函数(外/内部调用)
u32 mymem_malloc(u8 memx,u32 size);		 //内存分配(内部调用)
u8 mymem_free(u8 memx,u32 offset);		 //内存释放(内部调用)
u8 mem_perused(u8 memx);				 //获得内存使用率(外/内部调用) 

//用户调用函数
void myfree(u8 memx,void *ptr);  			//内存释放(外部调用)
void *mymalloc(u8 memx,u32 size);			//内存分配(外部调用)
void *myrealloc(u8 memx,void *ptr,u32 size);//重新分配内存(外部调用)
#endif

然后在main函数中使用
在这里插入图片描述

修改stm32f4x7_eth.h

#include “stm32f4x7_eth.h” 的最后添加 extern 使得外部文件可以使用

在这里插入图片描述
至此 ETH的DMA描述符,缓存,接收帧内存 都可以使用了

加入LWIP包

在工程源目录中加入LWIP文件夹, 并且把lwip包的文件全部复制到源码目录的LWIP文件夹里

添加lwip源码

在这里插入图片描述
在keil中创建相对应的Group并且在keil中加入这些路径

  • lwip/core
    需要单独加入ipv4的内容,不加ipv6的内容
    在这里插入图片描述
    lwip/netif 加入这些中的ethernet.c文件,注意只加ethernet.c
    在这里插入图片描述
  • lwip.api
    加入这些
    在这里插入图片描述- lwip/arch
    这个文件夹是单独创建在User中的arch文件夹,这里存放着lwip与用户的接口
    在我的文件夹中的User/arch 文件夹中,直接复制过去
    在这里插入图片描述

添加lwip头文件路径

在keil工程中加入头文件路径
在这里插入图片描述

添加lwip时钟更新

在这里我使用的是我10ms的定时器驱动的一个任务调度器,没软件定时器的可以直接放入10ms定时器中.
在这里插入图片描述
把上图的函数放入10ms任务中,其中lwip_localtime+=10表示的是10ms更新的时基。

添加以太网底层驱动

以太网初始化

初始化GPIO

初始化GPIO并且选择RMII接口的SYSCFG

RCC->AHB1ENR |= RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOG;
	RCC->APB2ENR |=RCC_APB2Periph_SYSCFG;//使能SYSCFG时钟
	
	SYSCFG->PMC=(uint32_t)(0x800000);//MAC和PHY之间使用RMII接口
	
	GPIOA->MODER|=(uint32_t)(0x8028); 		//PA1 PA2 PA7
	GPIOB->MODER|=(uint32_t)(0x800000);		//PB11
	GPIOC->MODER|=(uint32_t)(0xA08);		//PC1 PC4 PC5
	GPIOG->MODER|=(uint32_t)(0x28000000);	//PG13 PG14

	GPIOA->AFR[0]|=(uint32_t)(0xB0000BB0);//PA1 PA2 PA7
	GPIOB->AFR[1]|=(uint32_t)(0xB000);			//PB11
	GPIOC->AFR[0]|=(uint32_t)(0xBB00B0);		//PC1 PC4 PC5
	GPIOG->AFR[1]|=(uint32_t)(0xBB00000);		//PG13 PG14

	GPIOA->OSPEEDR|=(uint32_t)(0xC03C); 	//PA1 PA2 PA7
	GPIOB->OSPEEDR|=(uint32_t)(0xC00000); 	//PB11
	GPIOC->OSPEEDR|=(uint32_t)(0xF0C); 	//PC1 PC4 PC5
	GPIOG->OSPEEDR|=(uint32_t)(0x3C000000); //PG13 PG14

初始化以太网MAC_DMA

//初始化ETH MAC层及DMA配置
//返回值:ETH_ERROR,发送失败(0)
//		ETH_SUCCESS,发送成功(1)
u8 ETH_MAC_DMA_Config(void)
{
	u8 rval;
	ETH_InitTypeDef ETH_InitStructure; 

	//使能以太网时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_ETH_MAC_Tx |RCC_AHB1Periph_ETH_MAC_Rx, ENABLE);
 
	ETH_DeInit();  								//AHB总线重启以太网
	ETH_SoftwareReset();  						//软件重启网络
	while (ETH_GetSoftwareResetStatus() == SET);//等待软件重启网络完成 
	ETH_StructInit(&ETH_InitStructure); 	 	//初始化网络为默认值  

	///网络MAC参数设置 
	ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Enable;   			//开启网络自适应功能
	ETH_InitStructure.ETH_LoopbackMode = ETH_LoopbackMode_Disable;					//关闭反馈
	ETH_InitStructure.ETH_RetryTransmission = ETH_RetryTransmission_Disable; 		//关闭重传功能
	ETH_InitStructure.ETH_AutomaticPadCRCStrip = ETH_AutomaticPadCRCStrip_Disable; 	//关闭自动去除PDA/CRC功能 
	ETH_InitStructure.ETH_ReceiveAll = ETH_ReceiveAll_Disable;						//关闭接收所有的帧
	ETH_InitStructure.ETH_BroadcastFramesReception = ETH_BroadcastFramesReception_Enable;//允许接收所有广播帧
	ETH_InitStructure.ETH_PromiscuousMode = ETH_PromiscuousMode_Disable;			//关闭混合模式的地址过滤  
	ETH_InitStructure.ETH_MulticastFramesFilter = ETH_MulticastFramesFilter_Perfect;//对于组播地址使用完美地址过滤   
	ETH_InitStructure.ETH_UnicastFramesFilter = ETH_UnicastFramesFilter_Perfect;	//对单播地址使用完美地址过滤 
	ETH_InitStructure.ETH_ChecksumOffload = ETH_ChecksumOffload_Enable; 			//开启ipv4和TCP/UDP/ICMP的帧校验和卸载   
	//当我们使用帧校验和卸载功能的时候,一定要使能存储转发模式,存储转发模式中要保证整个帧存储在FIFO中,
	//这样MAC能插入/识别出帧校验值,当真校验正确的时候DMA就可以处理帧,否则就丢弃掉该帧
	ETH_InitStructure.ETH_DropTCPIPChecksumErrorFrame = ETH_DropTCPIPChecksumErrorFrame_Enable; //开启丢弃TCP/IP错误帧
	ETH_InitStructure.ETH_ReceiveStoreForward = ETH_ReceiveStoreForward_Enable;     //开启接收数据的存储转发模式    
	ETH_InitStructure.ETH_TransmitStoreForward = ETH_TransmitStoreForward_Enable;   //开启发送数据的存储转发模式  

	ETH_InitStructure.ETH_ForwardErrorFrames = ETH_ForwardErrorFrames_Disable;     	//禁止转发错误帧  
	ETH_InitStructure.ETH_ForwardUndersizedGoodFrames = ETH_ForwardUndersizedGoodFrames_Disable;	//不转发过小的好帧 
	ETH_InitStructure.ETH_SecondFrameOperate = ETH_SecondFrameOperate_Enable;  		//打开处理第二帧功能
	ETH_InitStructure.ETH_AddressAlignedBeats = ETH_AddressAlignedBeats_Enable;  	//开启DMA传输的地址对齐功能
	ETH_InitStructure.ETH_FixedBurst = ETH_FixedBurst_Enable;            			//开启固定突发功能    
	ETH_InitStructure.ETH_RxDMABurstLength = ETH_RxDMABurstLength_32Beat;     		//DMA发送的最大突发长度为32个节拍   
	ETH_InitStructure.ETH_TxDMABurstLength = ETH_TxDMABurstLength_32Beat;			//DMA接收的最大突发长度为32个节拍
	ETH_InitStructure.ETH_DMAArbitration = ETH_DMAArbitration_RoundRobin_RxTx_1_1;

	rval=ETH_Init(&ETH_InitStructure,LAN8720_PHY_ADDRESS);		//配置ETH

	if(rval==ETH_SUCCESS)//配置成功
	{
		ETH_DMAITConfig(ETH_DMA_IT_NIS|ETH_DMA_IT_R,ENABLE);  	//使能以太网接收中断	
		NVIC_InitTypeDef NVIC_InitStructure;
		NVIC_InitStructure.NVIC_IRQChannel = ETH_IRQn;  //以太网中断
		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //中断寄存器组2最高优先级
		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
		NVIC_Init(&NVIC_InitStructure);
		ETH_MACAddressConfig(ETH_MAC_Address0,lwipdev.mac);
		printf("ETH Init Sucess\r\n");
	}
	return rval;
}

  1. 这里 设置MAC地址 很重要,否则以太网无法接收自己的IP地址所对应的包!

ETH_MACAddressConfig(ETH_MAC_Address0,lwipdev.mac);

的是lwipdev.mac
这里的lwipdev.mac在lwip_comm.h中,在main函数中调用lwip_comm_init() 用来初始化lwip的底层设备和lwip内核,MAC地址在这个函数的lwip_comm_default_ip_set(&lwipdev); 中修改。
2. 这里一定要 开启ETH的DMA中断并且使能ETH_IRQn

设置以太网DMA描述符 & DMA缓存的对应关系

	rval=ETH_MAC_DMA_Config();
	if(rval==ETH_SUCCESS){
		printf("ETH init OK  ");
	}
	else{
		printf("ETH init Failed  ");
	}
	ETH_DMATxDescChainInit(DMATxDscrTab,Tx_Buff,ETH_TXBUFNB);//将接收描述符和接收缓存区关联起来 串成链式结构 初始化了发送追踪描述符
	ETH_DMARxDescChainInit(DMARxDscrTab,Rx_Buff,ETH_RXBUFNB);//将发送描述符和发送缓存区关联起来 串成链表	  初始化了接收追踪描述符

	 for(uint8_t i=0; i<ETH_TXBUFNB; i++)
    {
      	ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i], ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
		ETH_DMATxDescCRCCmd(&DMATxDscrTab[i],ENABLE);
    } 
	ETH_Start();

后面

for(uint8_t i=0; i<ETH_TXBUFNB; i++)
{
ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i],ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
ETH_DMATxDescCRCCmd(&DMATxDscrTab[i],ENABLE);
}

设置了以太网TX发送描述缓存帧的和校验,这步是我在前面测试的时候发现的问题若没这段程序,以太网只可以发送ARP请求,TCP/UDP/ICMP等都发送出去的帧都是0和校验,形成错误帧,所以一定要开启TX缓存的和校验!
另一个需要注意的是一定要开启ETH!

ETH_Start();

在这里我贴出的这段程序,可以获得PHY芯片和外部协商的结果,可以验证设置的以太网是是否和外部PHY芯片通讯上。

//得到8720的速度模式
//返回值:
//001:10M半双工
//101:10M全双工
//010:100M半双工
//110:100M全双工
//其他:错误.
void LAN8720_Get_Speed(void)
{
	u8 speed;
	speed=((ETH_ReadPHYRegister(PHY_BCR,PHY_SR)&0x1C)>>2); //从LAN8720的31号寄存器中读取网络速度和双工模式
	switch(speed){
		case 1: printf("10M半双工\r\n"); break;
		case 5: printf("10M全双工\r\n"); break;
		case 2: printf("100M半双工\r\n"); break;
		case 6: printf("100M全双工\r\n"); break;
		default: printf("ETH 初始化失败 %d\r\n",speed); break;
	}
}

以太网接收中断函数

在前初始化了以太网中断,这里编写以太网中断函数

 
//以太网中断服务函数
void ETH_IRQHandler(void){
	if(ETH_CheckFrameReceived()){
		ETH_flag=1;
	}

	ETH_DMAClearITPendingBit(ETH_DMA_IT_R); 	//清除DMA中断标志位
	ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);	//清除DMA接收中断标志位
}  

LWIP初始化

在LWIP底层硬件初始化

在这个函数中加入ETH初始化

static void low_level_init(struct netif *netif)

在这里插入图片描述

设置LWIP底层输入/ 输出函数

底层输入函数:

static struct pbuf * low_level_input(struct netif *netif)

底层输出函数:

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

修改这两个函数即可使用LWIP适配以太网(从这里看LWIP其实可以通过任何通讯方式运行,不只局限于ETH,只要是输入的是以太网帧格式的数据就可以,但是需要修改的部分较多,需要重新定义DMA描述符、追踪描述符、缓存的数据方向)。

LWIP输入处理

在while(1)中检测ETH中断函数的flg并且做出反应

if(ETH_flag){
			ETH_flag = 0;
			ethernetif_input(&lwip_netif);//调用网卡接收函数
		}

有两种ETH输入数据分解的方法, ETH中断是其中一种,但是实测发现了一个问题,当我把我的以太网线插入开发板<—>电脑 之间后,因为电脑一边从WIFI中获取数据一边会从以太网中尝试获取数据,于是会发生ETH超级频繁的进入ETH中断导致ETH这次的任务还没有处理完,下一次的任务标志位又被置为了导致出现了原子操作,也就是会出现数据量大的时候可能漏掉网络请求的情况,如图:
 使用ETH中断导致请求被漏
于是发现可以不使用ETH中断,禁用掉ETH中断,改用频率更高的while(1)大循环中循环检测,如图
在这里插入图片描述
在这里每一次大循环都会检测ETH输入帧是否收到,大大提高了实时响应性,ping命令不会出现遗漏的现象。

测试程序

ETH是否正常接收可以查看debug,这里贴出两个测试程序用来测试ETH是否可以正常发送

 void ethernet_sendtest1(void)
  {
      uint8_t frame_data[] =
      {
          /* 以太网帧格式 */
          0x50,0xFA,0x84,0x15,0x3C,0x3C,                            /* 远端MAC */
          0x0,0x80,0xE1,0x0,0x0,0x0,                                /* 本地MAC */
          0x8,0x0,                                                  /* ip类型 */
          0x45,0x0,0x0,0x26/*l*/,0x0,0x0,0x0,0x0,0xFF,0x11,0x0,0x0, /* UDP报头 */
          0xC0,0xA8,0x2,0x8,                                        /* 本地IP */
          0xC0,0xA8,0x2,0xC2,                                       /* 远端IP */
          0x22,0xB0,                                                /* 本地端口 */
          0x22,0xB1,                                                /* 远端端口 */
          0x0,0x12,                                                 /* UDP长度 */
          0x0,0x0,                                                  /* UDP校验和 */
          0x68,0x65,0x6C,0x6C,0x6F,0x20,0x7A,0x6F,0x72,0x62         /* 数据 */
      };
          
      struct pbuf *p;
      
      /* 分配缓冲区空间 */
      p = pbuf_alloc(PBUF_TRANSPORT, 0x26 + 14, PBUF_POOL);
      
      if (p != NULL)
      {
          /* 填充缓冲区数据 */
         pbuf_take(p, frame_data, 0x26 + 14);
  
          /* 把数据直接通过底层发送 */
          lwip_netif.linkoutput(&lwip_netif, p);
  
          /* 释放缓冲区空间 */
          pbuf_free(p);
      }
  }
 
  
  
  
 void ethernet_sendtest2(void)
  {
      uint8_t dstAddr[6] = {0x50,0xFA,0x84,0x15,0x3C,0x3C};         /* 远端MAC */
      
      uint8_t frame_data[] =
      {
          /* UDP帧格式 */
          0x45,0x0,0x0,0x26/*l*/,0x0,0x0,0x0,0x0,0xFF,0x11,0x0,0x0, /* UDP报头 */
          192,168,1,68,                                        /* 本地IP */
          192,168,1,11,                                       /* 远端IP */
          0x22,0xB0,                                                /* 本地端口 */
          0x22,0xB1,                                                /* 远端端口 */
          0x0,0x12,                                                 /* UDP长度 */
          0x0,0x0,                                                  /* UDP校验和 */
          1,2,3,4,5,6,6,6,6,6         /* 数据 */
      };
          
      struct pbuf *p;
      
      /* 分配缓冲区空间 */
      p = pbuf_alloc(PBUF_TRANSPORT, 0x26, PBUF_POOL);
      
      if (p != NULL)
      {
          /* 填充缓冲区数据 */
          pbuf_take(p, frame_data, 0x26);
          
          /* 把数据进行以太网封装,再通过底层发送 */
          ethernet_output(&lwip_netif, p, (const struct eth_addr*)lwip_netif.hwaddr,
              (const struct eth_addr*)dstAddr, ETHTYPE_IP);
  
          /* 释放缓冲区空间 */
          pbuf_free(p);
     }
 }
  

至此,ETH已经具备了运行LWIP并且可以PING了。
这里我修改IP地址和MAC,防止电脑内部MAC / ARP表影响测试结果
在这里插入图片描述

ping了好多次发现都可以,实验成功
在这里插入图片描述

总结一下,这里有很重要但是也很有可能会疏漏的几点

  1. 设置为MCU设置MAC地址,有专门的一个函数用来为MCU设置自己ETH的MAC地址,否则无法接收到自己IP地址包。
  2. 配置DMA的描述符和缓存的关系,这也是有专门的函数用来初始化对应的关系,否则描述符无法和缓存对应起来,接收到的是乱码或者直接进入硬件错误中断
  3. 如果是外部SRAM的板子,ETH的TX/RX的30K缓存可以放到外部SRAM中,而占用300字节的设备描述符不可以放入外部SRAM,因为这些描述符是直接与内部ETH的DMA交互的,将这些描述符的内存指向外部RAM会导致读取不到描述符出现直接无法读取的现象。
  4. 开启每个TX缓存的和校验,有专门的函数,如果不设置TX缓存和校验会导致TX发送的所有数据的和校验都是0x00 在抓包软件中出现的是错误。
  5. 如果使用片内RAM存储缓存,可以调节ETH_TXBUFNB 或者 ETH_RXBUFNB 调节有多少个缓存区从而调节缓存区大小
  6. 经过我的实测,将RX/TX缓存BUFFER存放在外部SRAM中会有几率ping失败或者超时几个,存放在内部RAM会一直可以使用,怀疑是SRAM的速度影响了DMA搜索地址传输的速度,缓存存放在内部RAM效率高,失误率低,(是否有可能是cache的作用?)

处理以太网数据帧的三种方式对比

续:在写完这篇博客之后我再次重新理解了以太网响应数据的方式,在以太网使用中断检测可用的帧数据&大循环处理 / 直接在大循环中检测可用的帧&处理 / 以太网中断接收到置为标志位&大循环中处理这三种方法我都尝试了一下,

中断检测可使用的帧 & 大循环处理:

这里使用的是在中断中

//以太网中断服务函数
void ETH_IRQHandler(void){
if(ETH_CheckFrameReceived()){
ETH_flag=1;
}
ETH_DMAClearITPendingBit(ETH_DMA_IT_R); //清除DMA中断标志位
ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS); //清除DMA接收中断标志位
}

大循环中

if(ETH_flag==1){
ETH_flag=0;
if(ETH_CheckFrameReceived()){
ethernetif_input(&lwip_netif);//调用网卡接收函数
}
}

这样实际测试发现因为在ETH_CheckFrameReceived( ) 这个函数中有这样一段

/* check if last segment */
if(((DMARxDescToGet->Status & ETH_DMARxDesc_OWN) == (uint32_t)RESET) &&
((DMARxDescToGet->Status & ETH_DMARxDesc_LS) != (uint32_t)RESET))
{
DMA_RX_FRAME_infos->Seg_Count++;
if (DMA_RX_FRAME_infos->Seg_Count == 1)
{
DMA_RX_FRAME_infos->FS_Rx_Desc = DMARxDescToGet;
}
DMA_RX_FRAME_infos->LS_Rx_Desc = DMARxDescToGet;
return 1;
}

这里可以看到它会不断更新是否是最新的缓存区域,如果不是,则++缓存到另一个,但是这样当中断正在检测frame的时候又发生了中断,会直接导致frame检测混乱,count++了之后又++,正在处理上一个事件的时候又被后半部分指到了下一个事件的数据,所以 ETH_CheckFrameReceived( ) 这个函数不可重入!!,实际测发现这样有概率成功ping通几个包,大部分因为网络频繁进中断导致了无法ping通,那么在中断检测帧在大循环处理这个方法PASS

直接在大循环里检测 & 处理

例如:

if(ETH_CheckFrameReceived()){
ethernetif_input(&lwip_netif);//调用网卡接收函数
}

直接在while(1)中不断检测,这样的好处是可以一直检测,有任何帧被发现都会处理,缺点是这样会占用大量MCU资源,当任务多了之后会发现检测不是很及时,并且会拖累其他任务的响应,直接导致系统响应慢。

中断置位标志位累加 & 大循环处理

这里我使用的是在中断中给需要处理的事件++

//以太网中断服务函数
void ETH_IRQHandler(void){
if(ETH_CheckFrameReceived()){
ETH_flag++;
}
ETH_DMAClearITPendingBit(ETH_DMA_IT_R); //清除DMA中断标志位
ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS); //清除DMA接收中断标志位
}

这样相当于记录了有多少个事件应该被处理
在大循环中处理

if(ETH_flag){
ETH_flag–;
ethernetif_input(&lwip_netif);//调用网卡接收函数
printf(“ETH_flag=%d\r\n”,ETH_flag);
}

这样既不会发生frame检测重入,又可以即使处理所有缓存中的事件!!
实测效果如下:
在debug界面,最多发生一次剩余缓存未处理,并且可以看到后面已经即使处理了
在这里插入图片描述
在ping响应中,往返小于1ms
在这里插入图片描述
之前方法2全部在while中处理的时候的时间是2-3ms:
在这里插入图片描述
所以方法3无论是响应速度或者是处理数据的数量来说都是比较合理的,如果又更好的方法欢迎私信我!


源码获取

文件链接:
通过网盘分享的文件:CSDN_ETH.rar
链接: https://pan.baidu.com/s/15UMS1rLIsaaPfxGsWYXK9Q?pwd=kg2m 提取码: kg2m

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2280447.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSS笔记基础篇02——浮动、标准流、定位、CSS精灵、字体图标

黑马程序员视频地址&#xff1a; 前端Web开发HTML5CSS3移动web视频教程https://www.bilibili.com/video/BV1kM4y127Li?vd_source0a2d366696f87e241adc64419bf12cab&spm_id_from333.788.videopod.episodes&p70https://www.bilibili.com/video/BV1kM4y127Li?vd_source…

Linux:进程(三)

1. 进程创建补充 fork之后父子两个执行流分别执行&#xff0c;fork之后谁谁先执行由调度器来决定。 一般&#xff0c;父子代码共享。当父子不再写入时&#xff0c;数据也是共享的&#xff0c;但是当有一方要写入&#xff0c;就触发写时拷贝。 fork调用失败的原因 1. 系统中有…

2025年1月21日刷题记录

1.leetcode1768题目 链接&#xff1a;1768. 交替合并字符串 - 力扣&#xff08;LeetCode&#xff09; 代码&#xff1a; class Solution { public:string mergeAlternately(string word1, string word2) {string word3;int a word1.size(), b word2.size();int i 0, j 0…

Mysql触发器(学习自用)

一、介绍 二、触发器语法 注意&#xff1a;拿取新的数据时用new&#xff0c;旧数据用old。

wireshark工具简介

目录 1 wireshark介绍 2 wireshark抓包流程 2.1 选择网卡 2.2 停止抓包 2.3 保存数据 3 wireshark过滤器设置 3.1 显示过滤器的设置 3.2 抓包过滤器 4 wireshark的封包列表与封包详情 4.1 封包列表 4.2 封包详情 参考文献 1 wireshark介绍 wireshark是非常流行的网络…

「2024·我的成长之路」:年终反思与展望

文章目录 1. 前言2.创作历程2.1 摆烂期2.2 转变期3. 上升期 2. 个人收获3.经验分享4. 展望未来 1. 前言 2025年1月16日&#xff0c;2024年博客之星入围公布&#xff0c;很荣幸获得了这次入围的机会。2024年对我个人是里程碑的一年&#xff0c;是意义非凡的一年&#xff0c;是充…

【RAG落地利器】向量数据库Chroma入门教程

安装部署 官方有pip安装的方式&#xff0c;为了落地使用&#xff0c;我们还是采用Docker部署的方式&#xff0c;参考链接来自官方部署: https://cookbook.chromadb.dev/running/running-chroma/#docker-compose-cloned-repo 我们在命令终端运行&#xff1a; docker run -d --…

电阻电位器可调电阻信号隔离变送器典型应用

电阻电位器可调电阻信号隔离变送器典型应用 产品描述&#xff1a; 深圳鑫永硕科技的XYS-5587系列是一进一出线性电子尺(电阻/电位计信号及位移)信号隔离变送器&#xff0c;是将输入电阻,线性电子尺,角度位移传感器信号进行采集,隔离,放大并转换成模拟量信号的小型仪表设备,并以…

[创业之路-259]:《向流程设计要效率》-1-让成功成熟业务交给流程进行复制, 把创新产品新业务新客户交给精英和牛人进行探索与创造

标题&#xff1a;成功与创新的双轨并行&#xff1a;以流程复制成熟&#xff0c;以精英驱动新知 在当今这个日新月异的商业环境中&#xff0c;企业要想持续繁荣发展&#xff0c;就必须在稳定与创新之间找到完美的平衡点。一方面&#xff0c;成熟业务的稳定运营是企业生存和发展的…

模拟飞行入坑(五) P3D 多通道视角配置 viewgroup

背景&#xff1a; P3D进行多个屏幕显示的时候&#xff0c;如果使用英伟达自带的屏幕融合成一个屏&#xff0c;或者使用P3D单独拉伸窗口&#xff0c;会使得P3D的画面被整体拉伸&#xff0c;又或者,当使用Multichannel进行多个设备联动时&#xff0c;视角同步组合需要配置&#…

Java中的错误与异常详解

Java中的错误与异常详解 Java提供了一种机制来捕获和处理程序中的异常和错误。异常和错误都继承自 Throwable 类&#xff0c;但它们有着不同的用途和处理方式。 1. Error&#xff08;错误&#xff09; Error 是程序无法处理的严重问题&#xff0c;通常由 JVM&#xff08;Java…

免费开源的三维建模软件Blender

软件介绍 Blender是一款功能强大且免费开源的三维建模、动画制作和渲染软件&#xff0c;广泛应用于影视制作、游戏开发、建筑可视化、教育及艺术创作等多个领域。 核心功能 Blender是一款全能型3D软件&#xff0c;涵盖了从建模、动画到渲染、后期合成的完整工作流程。 1、建…

ElasticSearch DSL查询之排序和分页

一、排序功能 1. 默认排序 在 Elasticsearch 中&#xff0c;默认情况下&#xff0c;查询结果是根据 相关度 评分&#xff08;score&#xff09;进行排序的。我们之前已经了解过&#xff0c;相关度评分是通过 Elasticsearch 根据查询条件与文档内容的匹配程度自动计算得出的。…

iOS 网络请求: Alamofire 结合 ObjectMapper 实现自动解析

引言 在 iOS 开发中&#xff0c;网络请求是常见且致其重要的功能之一。从获取资料到上传数据&#xff0c;出色的网络请求框架能夠大大提升开发效率。 Alamofire 是一个极具人气的 Swift 网络请求框架&#xff0c;提供了便据的 API 以完成网络请求和响应处理。它支持多种请求类…

面向对象编程——对象实例化

在python中&#xff0c;对象实例化是根据类的定义创建具体对象的过程。也就是将类当成模板&#xff0c;从而定义了对象的结构和行为&#xff0c;而实例化则是根据这个模板创建具体的对象实例。每个实例都有自己独立的状态&#xff0c;但是却共享类的结构和方法。 代码&#xff…

阿里云-银行核心系统转型之业务建模与技术建模

业务领域建模包括业务建模和技术建模&#xff0c;整体建模流程图如下&#xff1a; 业务建模包括业务流程建模和业务对象建模 业务流程建模&#xff1a;通过对业务流程现状分析&#xff0c;结合目标核心系统建设能力要求&#xff0c;参考行业建 模成果&#xff0c;形成结构化的…

Unreal Engine 5 C++ Advanced Action RPG 九章笔记

第九章 Hero Special Abilities 2-Challenges Ahead(前方的挑战) 本次章节主要解决三件问题 怒气能力特殊武器能力治疗石怒气能力 对于这个能力我们需要处理它的激活和持械状态,当没有怒气时应该取消该能力当这个能力激活时,我希望角色是进入无敌状态的,不会受到伤害怒气状…

cursor重构谷粒商城05——docker容器化技术快速入门【番外篇】

前言&#xff1a;这个系列将使用最前沿的cursor作为辅助编程工具&#xff0c;来快速开发一些基础的编程项目。目的是为了在真实项目中&#xff0c;帮助初级程序员快速进阶&#xff0c;以最快的速度&#xff0c;效率&#xff0c;快速进阶到中高阶程序员。 本项目将基于谷粒商城…

【FPGA】MIPS 12条整数指令【1】

目录 修改后的仿真结果 修改后的完整代码 实现bgtz、bltz、jalr 仿真结果&#xff08;有问题&#xff09; bltz------并未跳转&#xff0c;jCe&#xff1f; 原因是该条跳转语句判断的寄存器r7&#xff0c;在该时刻并未被赋值 代码&#xff08;InstMem修改前&#xff09; i…

洛谷题目:P2742 [USACO5.1] 圈奶牛Fencing the Cows /【模板】二维凸包 题解 (本题较难)

题目传送门&#xff1a;P2742 [USACO5.1] 圈奶牛Fencing the Cows /【模板】二维凸包 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 另&#xff1a;由于一些文章的疏忽&#xff0c;导致一些错别字&#xff0c;代码错误&#xff0c;公式错误导致大家的理解和误导&#xff0c;…