網絡通信---MCU移植LWIP

使用的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 PA7GPIOB->MODER|=(uint32_t)(0x800000);		//PB11GPIOC->MODER|=(uint32_t)(0xA08);		//PC1 PC4 PC5GPIOG->MODER|=(uint32_t)(0x28000000);	//PG13 PG14GPIOA->AFR[0]|=(uint32_t)(0xB0000BB0);//PA1 PA2 PA7GPIOB->AFR[1]|=(uint32_t)(0xB000);			//PB11GPIOC->AFR[0]|=(uint32_t)(0xBB00B0);		//PC1 PC4 PC5GPIOG->AFR[1]|=(uint32_t)(0xBB00000);		//PG13 PG14GPIOA->OSPEEDR|=(uint32_t)(0xC03C); 	//PA1 PA2 PA7GPIOB->OSPEEDR|=(uint32_t)(0xC00000); 	//PB11GPIOC->OSPEEDR|=(uint32_t)(0xF0C); 	//PC1 PC4 PC5GPIOG->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);		//配置ETHif(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.pswp.cn/web/66665.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/66665.shtml
英文地址,請注明出處:http://en.pswp.cn/web/66665.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【MySQL】存儲引擎有哪些?區別是什么?

頻率難度60%???? 這個問題其實難度并不是很大&#xff0c;只是涉及到的相關知識比較繁雜&#xff0c;比如事務、鎖機制等等&#xff0c;都和存儲引擎有關系。有時還會根據場景選擇不同的存儲引擎。 下面筆者將會根據幾個部分盡可能地講清楚 MySQL 中的存儲引擎&#xff0…

【系統環境丟失恢復】如何恢復和重建 Ubuntu 中的 .bashrc 文件

r如果你遇到這種情況&#xff0c;說明系統環境的.bashrc 文件丟失恢復&#xff1a; 要恢復 ~/.bashrc 文件&#xff0c;可以按照以下幾種方式操作&#xff1a; 恢復默認的 ~/.bashrc 文件 如果 ~/.bashrc 文件被刪除或修改&#xff0c;你可以恢復到默認的版本。可以參考以下…

人工智能丨智能化測試解決方案全面解析

智能化測試解決方案通過整合前沿的人工智能技術與自動化測試技術&#xff0c;為軟件產品的測試工作帶來了前所未有的高效與智能。 智能化測試解決方案主要內容 大語言模型私有部署&#xff1a;確保文檔理解、代碼分析和測試生成過程中的安全與隱私&#xff0c;讓你無后顧之憂…

Element修改表格結構樣式集合(后續實時更新)

場景 修改前端Element組件el-table樣式 實現 線表格 <div class"tablepro"><el-table:data"tableData":header-cell-style"{ textAlign:center}"class"tablepro-table"borderstyle"width: 100%;height:100%"&g…

C++語言的語法糖

C語言的語法糖 在現代編程語言的設計中&#xff0c;語法糖&#xff08;Syntactic Sugar&#xff09;是一個非常重要的概念。它指的是一種編程語言所提供的語法特性&#xff0c;使得代碼更加簡潔易讀&#xff0c;編寫更加方便&#xff0c;而不是增加語言的功能。C作為一種強大的…

基于Redis實現短信驗證碼登錄

目錄 1 基于Session實現短信驗證碼登錄 2 配置登錄攔截器 3 配置完攔截器還需將自定義攔截器添加到SpringMVC的攔截器列表中 才能生效 4 Session集群共享問題 5 基于Redis實現短信驗證碼登錄 6 Hash 結構與 String 結構類型的比較 7 Redis替代Session需要考慮的問題 8 …

c++入門----模板深入探究與仿函數

1.模板參數加入一個變量 一般只能是int&#xff0c;double要看c的版本&#xff0c;在最新的版本下是支持double模板參數的。 2.適配器的使用 template <class T,class containerdeque<T>> class stack { public:void push_back(const T& x) {_con.push_back…

【Vim Masterclass 筆記22】S09L40 + L41:同步練習11:Vim 的配置與 vimrc 文件的相關操作(含點評課內容)

文章目錄 S09L40 Exercise 11 - Vim Settings and the Vimrc File1 訓練目標2 操作指令2.1. 打開 vimrc-sample 文件2.2. 嘗試各種選項與設置2.3. 將更改內容保存到 vimrc-sample 文件2.4. 將文件 vimrc-sample 的內容復制到寄存器2.5. 創建專屬 vimrc 文件2.6. 對于 Mac、Linu…

kafka學習筆記5 PLAIN認證——筑夢之路

在Kafka中&#xff0c;SASL&#xff08;Simple Authentication and Security Layer&#xff09;機制包括三種常見的身份驗證方式&#xff1a; SASL/PLAIN認證&#xff1a;含義是簡單身份驗證和授權層應用程序接口&#xff0c;PLAIN認證是其中一種最簡單的用戶名、密碼認證方式&…

深入解析 Spring 框架中的事務傳播行為

目錄 &#xff08;一&#xff09;REQUIRED &#xff08;二&#xff09;SUPPORTS &#xff08;三&#xff09;MANDATORY &#xff08;四&#xff09;REQUIRES_NEW &#xff08;五&#xff09;NOT_SUPPORTED &#xff08;六&#xff09;NEVER &#xff08;七&#xff09;NE…

60,【1】BUUCF web [RCTF2015]EasySQL1

先查看源碼 1&#xff0c;changepwd&#xff08;修改密碼&#xff09; <?php // 開啟會話&#xff0c;以便使用會話變量 session_start();// 設置頁面的內容類型為 HTML 并使用 UTF-8 編碼 header("Content-Type: text/html; charsetUTF-8");// 引入配置文件&…

高并發內存池_CentralCache(中心緩存)和PageCache(頁緩存)申請內存的設計

三、CentralCache&#xff08;中心緩存&#xff09;_內存設計 &#xff08;一&#xff09;Span的創建 // 頁編號類型&#xff0c;32位下是4byte類型&#xff0c;64位下是8byte類型 // #ifdef _WIN64 typedef unsigned long long PageID; #else _WIN32 typedef size_t PageI…

SimpleHelp遠程管理軟件存在任意文件讀取漏洞(CVE-2024-57727)

免責聲明: 本文旨在提供有關特定漏洞的深入信息,幫助用戶充分了解潛在的安全風險。發布此信息的目的在于提升網絡安全意識和推動技術進步,未經授權訪問系統、網絡或應用程序,可能會導致法律責任或嚴重后果。因此,作者不對讀者基于本文內容所采取的任何行為承擔責任。讀者在…

2024年終總結-行到水窮處,坐看云起時

依然是——關于我 我&#xff0c;坐標山東青島&#xff0c;一位無名的Java Coder&#xff0c;你可以叫我Debug.c亦或者種棵代碼技術樹。在此不過多贅述關于我&#xff0c;更多的關于我請移步我的2023年年終總結。 2023年終總結-輕舟已過萬重山 2024年OKR完成情況 2023年年末…

AI編程工具使用技巧:在Visual Studio Code中高效利用阿里云通義靈碼

AI編程工具使用技巧&#xff1a;在Visual Studio Code中高效利用阿里云通義靈碼 前言一、通義靈碼介紹1.1 通義靈碼簡介1.2 主要功能1.3 版本選擇1.4 支持環境 二、Visual Studio Code介紹1.1 VS Code簡介1.2 主要特點 三、安裝VsCode3.1下載VsCode3.2.安裝VsCode3.3 打開VsCod…

代碼隨想錄day14

二叉樹的反轉&#xff0c;采用迭代&#xff0c;只能用前序和后序遍歷 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(i…

1月21日星期二今日早報簡報微語報早讀

1月21日星期二&#xff0c;農歷臘月廿二&#xff0c;早報#微語早讀。 1、多地官宣&#xff1a;2025年可有序、限時或在限定區域燃放煙花爆竹&#xff1b; 2、TikTok恢復在美服務&#xff1b;特朗普提出繼續運營TikTok方案&#xff0c;外交部&#xff1a;若涉及收購中國企業應…

計算機組成原理——數據表示(一)

生活是一道長長的旅程&#xff0c;充滿了挑戰和困難。然而&#xff0c;我們必須堅持下去&#xff0c;努力前進。無論遇到什么困難&#xff0c;我們都要勇敢面對&#xff0c;永不放棄。只有通過不斷的努力和堅持&#xff0c;我們才能夠取得成功。在這個旅程中&#xff0c;我們可…

【數據結構】雙向循環鏈表實現簡易圖書管理系統的增刪改查

圖書管理系統 使用雙向循環鏈表實現一個簡單的圖書管理系統&#xff0c;圖書管理系統有如下功能&#xff1a; 1.添加書籍 2.刪除書籍 3.修改書籍信息 4.查詢書籍信息 5.借書 6.還書 #include <stdio.h> #include <stdlib.h> #include <string.h>// 書籍結構體…

強化學習入門--基本概念

強化學習基本概念 grid-world example 這個指的是一個小機器人&#xff08;agent&#xff09;在一個網格區域&#xff08;存在邊界&#xff09;&#xff0c;網格中存在需要躲避的格子和目標格子&#xff0c;我們的目的就是找到到達目標格子的最短路徑 state 表示智能體相對…