网络通信---MCU移植LWIP
使用的MCU型号为STM32F429IGT6,PHY为LAN7820A
目标是通过MCU的ETH给LWIP提供输入输出从而实现基本的Ping应答
OK废话不多说我们直接开始
下载源码
- LWIP包源码:lwip源码
-在这里下载
- 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(Ð_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(Ð_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;
}
- 这里 设置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中断,改用频率更高的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了好多次发现都可以,实验成功
总结一下,这里有很重要但是也很有可能会疏漏的几点:
- 设置为MCU设置MAC地址,有专门的一个函数用来为MCU设置自己ETH的MAC地址,否则无法接收到自己IP地址包。
- 配置DMA的描述符和缓存的关系,这也是有专门的函数用来初始化对应的关系,否则描述符无法和缓存对应起来,接收到的是乱码或者直接进入硬件错误中断
- 如果是外部SRAM的板子,ETH的TX/RX的30K缓存可以放到外部SRAM中,而占用300字节的设备描述符不可以放入外部SRAM,因为这些描述符是直接与内部ETH的DMA交互的,将这些描述符的内存指向外部RAM会导致读取不到描述符出现直接无法读取的现象。
- 开启每个TX缓存的和校验,有专门的函数,如果不设置TX缓存和校验会导致TX发送的所有数据的和校验都是0x00 在抓包软件中出现的是错误。
- 如果使用片内RAM存储缓存,可以调节ETH_TXBUFNB 或者 ETH_RXBUFNB 调节有多少个缓存区从而调节缓存区大小
- 经过我的实测,将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
相关文章:
网络通信---MCU移植LWIP
使用的MCU型号为STM32F429IGT6,PHY为LAN7820A 目标是通过MCU的ETH给LWIP提供输入输出从而实现基本的Ping应答 OK废话不多说我们直接开始 下载源码 LWIP包源码:lwip源码 -在这里下载 ST官方支持的ETH包:ST-ETH支持包 这里下载 创建工程 …...
Redis源码-redisObject
解释 redis中,所有的数据类型最终都转换成了redisObject,该结构体的定义,在文件server.h中。 参数说明 参数名说明unsigned type:4对象对应的数据类型unsigned encoding:4对象的编码方式unsigned lru:LRU_BITSLRU算法清空对象,…...
YOLOv10-1.1部分代码阅读笔记-tuner.py
tuner.py ultralytics\engine\tuner.py 目录 tuner.py 1.所需的库和模块 2.class Tuner: 1.所需的库和模块 # Ultralytics YOLO 🚀, AGPL-3.0 license# 此模块提供用于对象检测、实例分割、图像分类、姿势估计和多对象跟踪的 Ultralytics YOLO 模型的超参数调…...
【数据结构】二分查找
🚩 WRITE IN FRONT 🚩 🔎 介绍:"謓泽"正在路上朝着"攻城狮"方向"前进四" 🔎🏅 荣誉:2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评…...
iOS-支付相关
支付宝支付 #import <AlipaySDK/AlipaySDK.h> //orderStrAliPay为服务端传的订单信息 //fromScheme为应用配置的schemeUrl标识,用户支付包支付成功后跳转会本应用内 //callback回调需要在- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url 中调…...
ubuntu16.04 VSCode下cmake+clang+lldb调试c++
VSCode下cmakeclanglldb调试c Ubuntu16.04 安装OpenCV4.5.4 文章目录 VSCode下cmakeclanglldb调试c1.安装clangclangdcmake2、打开VSCode,安装扩展插件3、编译4、Debug4.1 创建launch.json。4.2 配置setting.json 5. vscode安装配置clang-format插件5.1 Linux系统安…...
学Python的人…
学Python的人… 一、Python能干什么? 1.爬虫:前几年,深度学习还没发展起来的时候,书店里Python就和爬虫挂钩,因为Python写爬虫确实方便。 2.数据分析:Python有各种的数据分析库可以方便使用࿰…...
GDB相比IDE有什么优点
GDB(GNU Debugger)相比于集成开发环境(IDE)具有一些独特的优点,主要体现在其灵活性、可定制性和低级控制能力。具体来说,GDB有以下几个优点: 1. 轻量级且无依赖 GDB是一个命令行工具,不依赖于任何复杂的图形界面或大型库,这使得它非常适合在资源受限的环境中使用,比…...
Docker 镜像加速的配置
解决拉取镜像报错:Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while 在使用 Docker 过程中,拉取镜像的速度常常会受到网络状况的影响,尤其是在国内网络环境下,…...
分布式多卡训练(DDP)踩坑
多卡训练最近在跑yolov10版本的RT-DETR,用来进行目标检测。 单卡训练语句(正常运行): python main.py多卡训练语句: 需要通过torch.distributed.launch来启动,一般是单节点,其中CUDA_VISIBLE…...
MFC程序设计(一)MFC入门
本MFC教程使用VS2022实现 MFC基本概念 微软基础类库(英语:Microsoft Foundation Classes,简称MFC)是一个微软公司提供的类库(class libraries),以C类的形式封装了Windows API,并且…...
swift Actor并发处理
preconcurrency 使用 /*在 Swift 5.5 引入并发模型后,编译器会对潜在的数据竞争和不安全的并发代码发出警告或错误。然而,某些旧代码或第三方库可能尚未完全适配这些新规则。preconcurrency 提供了一种临时解决方案,允许你在不修改代码的情况…...
网络编程 | UDP套接字通信及编程实现经验教程
1、UDP基础 传输层主要应用的协议模型有两种,一种是TCP协议,另外一种则是UDP协议。在上一篇博客文章中,已经对TCP协议及如何编程实现进行了详细的梳理讲解,在本文中,主要讲解与TCP一样广泛使用了另一种协议:…...
Hadoop•搭建完全分布式集群
听说这里是目录哦 一、安装Hadoop🥕二、配置Hadoop系统环境变量🥮三、验证Hadoop系统环境变量是否配置成功🧁四、修改Hadoop配置文件🍭五、分发Hadoop安装目录🧋六、分发系统环境变量文件🍨七、格式化HDFS文…...
代码中使用 Iterable<T> 作为方法参数的解释
/*** 根据课程 id 集合查询课程简单信息* param ids id 集合* return 课程简单信息的列表*/ GetMapping("/courses/simpleInfo/list") List<CourseSimpleInfoDTO> getSimpleInfoList(RequestParam("ids") Iterable<Long> ids); 一、代码解释&…...
web前端1--基础
(时隔数月我又来写笔记啦~) 1、下载vscode 1、官网下载:Visual Studio Code - Code Editing. Redefined 2、步骤: 1、点击同意 一直下一步 勾一个创建桌面快捷方式 在一直下一步 2、在桌面新建文件夹 拖到vscode图标上 打开v…...
关于opensips的帮助命令的解释
opensips -help以下是 opensips 命令及其选项的中文解释(基于 3.6.0-dev 版本): 命令用法 opensips -l 地址 [-l 地址 ...] [选项]选项说明 选项功能-f 文件指定配置文件(默认为 /usr/local//etc/opensips/opensips.cfg&#x…...
你还在用idea吗
从VIM、Emacs,到eclipse、Jetbrains, 再到VSCode,过去的三十年时间,出现了这三代IDE产品。现在属于AI的时代来了,最新一代的产品像Cursor、Windsurf,就在昨天,字节跳动发布了最新的IDE,就叫Trae…...
安装wxFormBuilder
1. 网址:GitHub - wxFormBuilder/wxFormBuilder: A wxWidgets GUI Builder 2. 安装MSYS2 MSYS2可以在GitHub的内容中找到,这个版本是32位64位的 3. 在程序中打开MINGW64 shell 4. 在MSYS2 MINGW64 shell中输入 pacman -Syu pacman -S ${MINGW_PACKAGE…...
【大数据2025】Hadoop 万字讲解
文章目录 一、大数据通识大数据诞生背景与基本概念大数据技术定义与特征大数据生态架构概述数据存储数据计算与易用性框架分布式协调服务和任务调度组件数仓架构流处理架构 二、HDFSHDFS 原理总结一、系统架构二、存储机制三、数据写入流程四、心跳机制与集群管理 安全模式&…...
HTML语言的计算机基础
HTML语言的计算机基础 引言 在当今信息技术迅猛发展的时代,网页设计和开发已成为计算机科学中不可或缺的一部分。而HTML(超文本标记语言)作为构建网页的基础语言,承载着网页上所有内容的结构,帮助开发者创建和展示信…...
Cannot resolve symbol ‘XXX‘ Maven 依赖问题的解决过程
一、问题描述 在使用 Maven 管理项目依赖时,遇到了一个棘手的问题。具体表现为:在 pom.xml 文件中导入了所需的依赖,并且在 IDE 中导入语句没有显示为红色(表示 IDE 没有提示依赖缺失),但是在实际使用这些依…...
Swift语言的函数实现
Swift语言函数实现详解 引言 Swift是一种强类型、泛型编程的现代编程语言,广泛应用于iOS和macOS开发。函数是Swift编程中的基本构建块之一,通过函数可以将代码进行模块化,实现重用性和可读性。本篇文章将系统地介绍Swift中的函数࿰…...
除了基本的事件绑定,鸿蒙的ArkUI
鸿蒙操作系统(HarmonyOS)是由华为技术有限公司开发的分布式操作系统,旨在为多种智能设备提供一个统一的操作平台。它不仅适用于智能手机,还适用于平板电脑、智能手表、智能电视等物联网设备。为了使开发者能够更加便捷地创建跨设备…...
数据分析及应用:经营分析中的综合指标解析与应用
目录 1. 市场份额(Market Share) 2. 客户获取成本(Customer Acquisition Cost, CAC) 3. 客户生命周期价值(Customer Lifetime Value, CLV) 4. 客户留存率(Customer Retention Rate, CRR) 5. 净推荐值(Net Promoter Score, NPS) 6. 转化率(Conversion Rate) …...
Mac开启任何来源安装配置环境变量
目录 开启任何来源配置环境变量退出保存时如果没有权限修改文件权限拓展——.bash_profile和.zshrc 开启任何来源 sudo spctl --master-disable#打开软件时提示文件损坏 sudo xattr -r -d com.apple.quarantine 进入访达应用程序拖拽应用到终端配置环境变量 cd ~ vi ~/.bash…...
UI自动化设计模式--POM
在自动化测试中,Page Object Model(POM)设计方式是一种常用的设计模式,它将页面元素和操作封装成独立的类,提高了测试代码的可读性、可维护性和可复用性。以下是关于POM设计方式的详细介绍: 基本概念 POM…...
(三)线性代数之二阶和三阶行列式详解
在前端开发中,尤其是在WebGL、图形渲染、或是与地图、模型计算相关的应用场景里,行列式的概念常常在计算变换矩阵、进行坐标变换或进行图形学算法时被使用。理解二阶和三阶行列式对于理解矩阵运算、旋转、平移等操作至关重要。下面,我将结合具…...
postman接口测试工具详解
一、前言 Postman是一款广泛使用的API测试工具,适用于开发人员和测试人员。它提供了直观的用户界面,能够方便地进行API请求、响应验证、自动化测试等操作。本文将详细介绍Postman的功能和用法,帮助用户高效地进行API测试。 二、Postman的主…...
Golang 中强大的重试机制,解决瞬态错误
文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…...
【C语言系列】深入理解指针(2)
一、数组名的理解 上一篇文章中我们写过一个这样的代码: int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *p &arr[0];这里使用&arr[0] 的方式拿到了数组第⼀个元素的地址,但是其实数组名本来就是地址,而且是数组首元素的地址ÿ…...
【网络原理】万字详解 HTTP 协议
🥰🥰🥰来都来了,不妨点个关注叭! 👉博客主页:欢迎各位大佬!👈 文章目录 1. HTTP 前置知识1.1 HTTP 是什么1.2 HTPP 协议应用场景1.3 HTTP 协议工作过程 2. HTTP 协议格式2.1 fiddler…...
WIFI连接与通信
ESP32-S3 支持 2.4 GHz 的 Wi-Fi 4(802.11n)标准,提供高达 150 Mbps 的数据传输速率。它支持 STA(Station)模式、AP(Access Point)模式和 Wi-Fi 直连(Wi-Fi Direct)模式&…...
Elixir语言的数据库编程
Elixir语言的数据库编程 介绍 Elixir是一种基于Erlang虚拟机(BEAM)的函数式编程语言,特别适用于构建可扩展和可维护的系统。它结合了Erlang的卓越并发特性和Ruby的易用性,因此在Web开发和实时应用中越来越受到欢迎。伴随着Elixi…...
python中Mako用法
Mako 是一个轻量级的 Python 模板库,它以其高效的代码生成和灵活的表达能力著称,常用于 Web 开发和静态文件生成。以下是对 Mako 的核心 API介绍。 1. 安装 Mako 首先安装 Mako: pip install mako2. 基本用法 Mako 的核心在于 Template 类…...
SMS4J - 一个聚合各种短信API商的工具
众所周知,在我们日常的项目开发中,短信验证码发送是一个必不可少的环节。 特别是如今手机互联网时代,基本所有东西都跟手机强绑定,所有的安全验证都离不开验证码这一环节。 所以对于一个系统来说,发送短信验证码成为…...
BEVFusion论文阅读
1. 简介 融合激光雷达和相机的信息已经变成了3D目标检测的一个标准,当前的方法依赖于激光雷达传感器的点云作为查询,以利用图像空间的特征。然而,人们发现,这种基本假设使得当前的融合框架无法在发生 LiDAR 故障时做出任何预测&a…...
【总结盘点类】2024,一场关于海量数据治理以及合理建模的系列写作
目录 1.今年的创作路线 2.先说第一条线 2.1.由日志引出的海量文本数据存储和分析问题 2.2.监控以及监控的可视化 2.3.数据量级再往上走牵扯出了大数据 2.4.由大数据牵扯出的JAVA线程高级内容 3.第二条线,也是2025要继续的主线 1.今年的创作路线 今年的写作内…...
【25考研】考清华的软件工程专业的研究生需要准备什么?
清华软件复试竞争一样很激烈!建议同学认真复习! 关于项目的注意事项先来一些总结: 千万别照抄开源项目 开源项目是一个很好的参考,但直接搬过来就没啥意义啦。我们可以根据开源项目学习它的技术架构和关键点,然后结…...
网络编程-UDP套接字
文章目录 UDP/TCP协议简介两种协议的联系与区别Socket是什么 UDP的SocketAPIDatagramSocketDatagramPacket 使用UDP模拟通信服务器端客户端测试 完整测试代码 UDP/TCP协议简介 两种协议的联系与区别 TCP和UDP其实是传输层的两个协议的内容, 差别非常大, 对于我们的Java来说, …...
EXCEL的一些用法记录
按某个分隔符进行拆分多列 【数据】- 【分列】 多列调整成多行 复制 - 粘贴 - 选择【转置】 部分内容替换 SUBSTITUTE()函数 ,固定内容 加“”...
不使用 JS 纯 CSS 获取屏幕宽高
前言 在现代前端开发中,获取屏幕的宽度和高度通常依赖于 JavaScript。然而现代 CSS 也可以获取到屏幕的宽高,通过自定义属性(CSS Variables)和一些数学函数来实现这一目标。本文将详细解析如何使用 CSS 的 property 规则和一些数…...
Node.js 完全教程:从入门到精通
Node.js 完全教程:从入门到精通 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,允许开发者在服务器端使用 JavaScript。它的非阻塞 I/O 和事件驱动架构使得 Node.js 非常适合于构建高性能的网络应用。本文将详细介绍 Node.js 的安装、基本语…...
可替代CentOS 7的Linux操作系统选型
可替代CentOS 7的其他Linux操作系统选型 一、背景介绍二、主流操作系统调研2.1 企业级产品:Red Hat Enterprise Linux/CentOS Stream2.1.1 Red Hat Enterprise Linux2.1.2 CentOS Stream2.2 其他发行版:Debian/Ubuntu2.3 开源产品:AlmaLinux / RockyLinux2.3.1 AlmaLinux2.3…...
【ESP32】ESP32连接JY61P并通过WIFI发送给电脑
前言 手头上有个ESP32,发现有wifi功能,希望连接JY61P并通过WIFI把姿态数据发送给电脑 1.采用Arduino IDE编译器;需要安装ESP32的开发板管理器; 2.电脑接受数据是基于python的; 1. ESP32 连接手机WIFI #include <…...
【JSqlParser】Java使用JSqlParser解析SQL语句总结
简述 Java解析SQL语句有很多工具都可以做到,比如Mybatis、Druid、目前用来用去最全面的仍然是Jsqlparser,它是一个Github上的开源项目,JSqlParser是一个用于解析SQL语句的Java库,它可以帮助开发者分析和操作SQL语句的结构。无论是…...
TCP断开通信前的四次挥手(为啥不是三次?)
1.四次握手的过程 客户端A发送 FIN(终止连接请求) A:我要断开连接了(FIN)。A进入FIN_WAIT_1状态,表示请求断开,等待对方确认。 服务器B回复 ACK(确认断开请求,但还未准备…...
解决用 rm 报bash: /usr/bin/rm: Argument list too long错
但目录里面文件过多用 rm 报bash: /usr/bin/rm: Argument list too long错时怎么办: 看看以下操作记录 rootmcu:/# cd /tmp rootmcu:/tmp# rm -f /tmp/chunk* bash: /usr/bin/rm: Argument list too long rootmcu:/tmp# rm -rf /tmp/chunk* bash: /usr/bin/rm: Arg…...
AI News(1/21/2025):OpenAI 安全疏忽:ChatGPT漏洞引发DDoS风险/OpenAI 代理工具即将发布
1、OpenAI 的安全疏忽:ChatGPT API 漏洞引发DDoS风险 德国安全研究员 Benjamin Flesch 发现了一个严重的安全漏洞:攻击者可以通过向 ChatGPT API 发送一个 HTTP 请求,利用 ChatGPT 的爬虫对目标网站发起 DDoS 攻击。该漏洞源于 OpenAI 在处理…...
从零到上线:Node.js 项目的完整部署流程(包含 Docker 和 CICD)
从零到上线:Node.js 项目的完整部署流程(包含 Docker 和 CI/CD) 目录 项目初始化:构建一个简单的 Node.js 应用设置 Docker 环境:容器化你的应用配置 CI/CD:自动化构建与部署上线前的最后检查:…...