基于FreeRTOS的LWIP移植

news2024/11/30 3:51:19

目录

  • 前言
  • 一、移植准备工作
  • 二、以太网固件库与驱动
    • 2.1 固件库文件添加
    • 2.2 库文件修改
    • 2.3 添加网卡驱动
  • 三、LWIP 数据包和网络接口管理
    • 3.1 添加LWIP源文件
    • 3.2 Lwip文件修改
      • 3.2.1 修改cc.h
      • 3.2.2 修改lwipopts.h
      • 3.2.3 修改icmp.c
      • 3.2.4 修改sys_arch.h和sys_arch.c
      • 3.2.5 修改ethernetif.h和ethernetif.c
  • 四、添加应用程序
    • 4.1 添加Lwip应用
    • 4.2 添加FreeRTOS应用


前言

  介绍一下基于FreeRTOS的LWIP移植,平台为STM32F407标准库的Keil环境。LWIP的相关介绍见以下链接:https://blog.csdn.net/weixin_44567668/article/details/139619797

一、移植准备工作

  移植文件需要LWIP源码FreeRTOS标准库工程,参考以下链接:

  1. FreeRTOS标准库工程
      移植需要一个已经移植好FreeRTOS的STM32标准库工程,大概如下图所示
    在这里插入图片描述
      FreeRTOS的移植参考以下链接:https://blog.csdn.net/weixin_44567668/article/details/135419275

  2. Lwip源码
      Lwip源码可以从官网获取,LWIP官网链接:http://savannah.nongnu.org/projects/lwip/
    在这里插入图片描述
      点击Download Area就可以下载源码,我们选择contrib-1.4.1.ziplwip-1.4.1.zip,不用最新版本是因为标准库缺少对应最新版本的以太网句柄ETH_HandleTypeDef,如果是HAL库版本可以用最新版本的Lwip源码
    在这里插入图片描述
      此时由于一些配置和移植需要,还要下载一下ST官方移植示例,ST官方参考工程链接:https://www.st.com/en/embedded-software/stsw-stm32070.html

二、以太网固件库与驱动

2.1 固件库文件添加

  从ST例程里找到以太网库STM32F4x7_ETH_Driver,并将其复制到我们工程文件里
在这里插入图片描述
  然后在keil工程里添加进来stm32f4x7_eth.cstm32f4xx_syscfg.c
在这里插入图片描述

2.2 库文件修改

  1. stm32f4x7_eth_conf.h的修改
      在STM32F4x7_ETH_Driver\inc很容易找到stm32f4x7_eth_conf_template.h,里面定义了一些关于操作PHY芯片的信息,有关PHY芯片LAN8720的相关介绍见前言链接。此时我们将stm32f4x7_eth_conf_template.h重命名为stm32f4x7_eth_conf.h,然后将里面改成LAN8720对应的配置
#ifndef __STM32F4x7_ETH_CONF_H
#define __STM32F4x7_ETH_CONF_H
#include "stm32f4xx.h"

#define USE_ENHANCED_DMA_DESCRIPTORS

//如果使用自己定义的延时函数的话就注销掉下面一行代码,否则使用
//默认的低精度延时函数

//#define USE_Delay    	//使用默认延时函数,因此注销掉
#ifdef USE_Delay
	#include "main.h"               
	#define _eth_delay_    Delay     //Delay为用户自己提供的高精度延时函数
                                    
#else
	#define _eth_delay_    ETH_Delay //默认的_eth_delay功能函数延时精度差
#endif

#ifdef  CUSTOM_DRIVER_BUFFERS_CONFIG
	//重新定义以太网接收和发送缓冲区的大小和数量
	#define ETH_RX_BUF_SIZE    ETH_MAX_PACKET_SIZE //接收缓冲区的大小
	#define ETH_TX_BUF_SIZE    ETH_MAX_PACKET_SIZE //发送缓冲区的大小
	#define ETH_RXBUFNB        20                  //接收缓冲区数量
	#define ETH_TXBUFNB        5                   //发送缓冲区数量
#endif

//*******************PHY配置块*******************
#ifdef USE_Delay
	#define PHY_RESET_DELAY    ((uint32_t)0x000000FF)  	//PHY复位延时
	#define PHY_CONFIG_DELAY   ((uint32_t)0x00000FFF) 	//PHY配置延时
	#define ETH_REG_WRITE_DELAY ((uint32_t)0x00000001)	//向以太网寄存器写数据时的延时
#else
	#define PHY_RESET_DELAY    ((uint32_t)0x000FFFFF)	//PHY复位延时
	#define PHY_CONFIG_DELAY   ((uint32_t)0x00FFFFFF)	//PHY配置延时
	#define ETH_REG_WRITE_DELAY ((uint32_t)0x0000FFFF)	//向以太网寄存器写数据时的延时
#endif

//LAN8720 PHY芯片的状态寄存器
#define PHY_SR				((uint16_t)31) 		//LAN8720的PHY状态寄存器地址
#define PHY_SPEED_STATUS    ((uint16_t)0x0004) 	//LAN8720 PHY速度值掩码
#define PHY_DUPLEX_STATUS   ((uint16_t)0x00010) //LAN8720 PHY连接状态值掩码  
#endif 
  1. stm32f4x7_eth.c的修改
      在 stm32f4x7_eth.c 文件中针对不同的平台定义了四个数组:Rx_Buff[]、Tx_Buff[]、DMARxDscrTab[]和 DMATxDscrTab[],这四个数组占用了大量的 RAM。我们在这里将这四个变量屏蔽掉,如图所示
    在这里插入图片描述

2.3 添加网卡驱动

  网卡驱动主要是对以太网的一些配置,主要是一些初始化工作,这里新建两个文件为ethernet.hethernet.c
在这里插入图片描述

  1. ethernet.h头文件
#ifndef __LAN8720_H
#define __LAN8720_H

#include "delay.h"
#include "stm32f4x7_eth.h"
			
#define LAN8720_PHY_ADDRESS  	0x00				//LAN8720 PHY芯片地址.
#define LAN8720_RST 		   	PDout(3) 			//LAN8720复位引脚	 


/* MAC ADDRESS*/
#define MAC_ADDR0   02
#define MAC_ADDR1   00
#define MAC_ADDR2   00
#define MAC_ADDR3   00
#define MAC_ADDR4   00
#define MAC_ADDR5   00
 
/*Static IP ADDRESS*/
#define IP_ADDR0   192
#define IP_ADDR1   168
#define IP_ADDR2   0
#define IP_ADDR3   10
   
/*NETMASK*/
#define NETMASK_ADDR0   255
#define NETMASK_ADDR1   255
#define NETMASK_ADDR2   255
#define NETMASK_ADDR3   0

/*Gateway Address*/
#define GW_ADDR0   192
#define GW_ADDR1   168
#define GW_ADDR2   0
#define GW_ADDR3   1 


extern ETH_DMADESCTypeDef *DMARxDscrTab;			//以太网DMA接收描述符数据结构体指针
extern ETH_DMADESCTypeDef *DMATxDscrTab;			//以太网DMA发送描述符数据结构体指针 
extern uint8_t *Rx_Buff; 							//以太网底层驱动接收buffers指针 
extern uint8_t *Tx_Buff; 							//以太网底层驱动发送buffers指针
extern ETH_DMADESCTypeDef  *DMATxDescToSet;			//DMA发送描述符追踪指针
extern ETH_DMADESCTypeDef  *DMARxDescToGet; 		//DMA接收描述符追踪指针 
extern ETH_DMA_Rx_Frame_infos *DMA_RX_FRAME_infos;	//DMA最后接收到的帧信息指针
 

u8 LAN8720_Init(void);
u8 ethernet_chip_get_speed(void);
u8 ETH_MACDMA_Config(void);
FrameTypeDef ETH_Rx_Packet(void);
u8 ETH_Tx_Packet(u16 FrameLength);

uint8_t ethernet_mem_malloc(void);
void ETH_Mem_Free(void);
#endif 
  1. ethernet.c
      在ethernet.c中下面几个函数,如表所示。
函数说明
LAN8720_Init()LAN8720 初始化函数
ETHERNET_NVICConfiguration()以太网 DMA 中断优先级配置
ethernet_chip_get_speed()获取当前连接速度和双工状态
ETH_MACDMA_Config()以太网 MAC 和 DMA 配置函数
ETH_IRQHandler()以太网 DMA 接收中断服务函数
ETH_Rx_Packet()从网卡中接收数据包
ETH_Tx_Packet()从网卡发送数据包
ETH_Mem_Malloc()Rx_Buff[]、Tx_Buff[]、DMARxDscrTab[]和DMATxDscrTab[]这四个数组申请内存
ETH_Mem_Free()释 放 Rx_Buff[] 、Tx_Buff[] 、DMARxDscrTab[]和DMATxDscrTab[]这四个数组的内存

  其中ETH_Rx_Packet()ETH_Tx_Packet()主要替代库函数ETH_Prepare_Transmit_Descriptors()ETH_Get_Received_Frame_interrupt(),同样是为了减小内存开销。全部代码如下

#include "ethernet.h"
#include "stm32f4x7_eth.h"
#include "usart.h" 
#include "delay.h"
#include "malloc.h" 
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"

ETH_DMADESCTypeDef *DMARxDscrTab;	//以太网DMA接收描述符数据结构体指针
ETH_DMADESCTypeDef *DMATxDscrTab;	//以太网DMA发送描述符数据结构体指针 
uint8_t *Rx_Buff; 					//以太网底层驱动接收buffers指针 
uint8_t *Tx_Buff; 					//以太网底层驱动发送buffers指针


extern xSemaphoreHandle s_xSemaphore;
  
static void ETHERNET_NVICConfiguration(void);

//------------------------------- BSP -------------------------------------//
//LAN8720初始化
//返回值:0,成功;
//    其他,失败
u8 LAN8720_Init(void)
{
	u8 rval=0;
	GPIO_InitTypeDef GPIO_InitStructure;
  
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOG|RCC_AHB1Periph_GPIOD, ENABLE);//使能GPIO时钟 RMII接口
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);   //使能SYSCFG时钟
  
	SYSCFG_ETH_MediaInterfaceConfig(SYSCFG_ETH_MediaInterface_RMII); //MAC和PHY之间使用RMII接口

	/*网络引脚设置 RMII接口
	  ETH_MDIO -------------------------> PA2
	  ETH_MDC --------------------------> PC1
	  ETH_RMII_REF_CLK------------------> PA1
	  ETH_RMII_CRS_DV ------------------> PA7
	  ETH_RMII_RXD0 --------------------> PC4
	  ETH_RMII_RXD1 --------------------> PC5
	  ETH_RMII_TX_EN -------------------> PG11
	  ETH_RMII_TXD0 --------------------> PG13
	  ETH_RMII_TXD1 --------------------> PG14
	  ETH_RESET-------------------------> PD3*/
					
	  //配置PA1 PA2 PA7
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;  
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_ETH); //引脚复用到网络接口上
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_ETH);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_ETH);

	//配置PC1,PC4 and PC5
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	GPIO_PinAFConfig(GPIOC, GPIO_PinSource1, GPIO_AF_ETH); //引脚复用到网络接口上
	GPIO_PinAFConfig(GPIOC, GPIO_PinSource4, GPIO_AF_ETH);
	GPIO_PinAFConfig(GPIOC, GPIO_PinSource5, GPIO_AF_ETH);
                                
	//配置PG11, PG14 and PG13 
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_11 | GPIO_Pin_13 | GPIO_Pin_14;
	GPIO_Init(GPIOG, &GPIO_InitStructure);
	GPIO_PinAFConfig(GPIOG, GPIO_PinSource11, GPIO_AF_ETH);
	GPIO_PinAFConfig(GPIOG, GPIO_PinSource13, GPIO_AF_ETH);
	GPIO_PinAFConfig(GPIOG, GPIO_PinSource14, GPIO_AF_ETH);
	
	//配置PD3为推完输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;	//推完输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;  
	GPIO_Init(GPIOD, &GPIO_InitStructure);
	
	LAN8720_RST=0;					//硬件复位LAN8720
	delay_ms(50);	
	LAN8720_RST=1;				 	//复位结束 
	ETHERNET_NVICConfiguration();	//设置中断优先级
	rval=ETH_MACDMA_Config();		//配置MAC及DMA
	return !rval;					//ETH的规则为:0,失败;1,成功;所以要取反一下 
}

//以太网中断分组配置
void ETHERNET_NVICConfiguration(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	
	NVIC_InitStructure.NVIC_IRQChannel = ETH_IRQn;  //以太网中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0X06;  //中断寄存器组2最高优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0X00;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}


//得到8720的速度模式
//返回值:
//001:10M半双工
//101:10M全双工
//010:100M半双工
//110:100M全双工
//其他:错误.
u8 ethernet_chip_get_speed(void)
{
	u8 speed;
	speed=((ETH_ReadPHYRegister(0x00,31)&0x1C)>>2); //从LAN8720的31号寄存器中读取网络速度和双工模式
	return speed;
}
/
//以下部分为STM32F407网卡配置/接口函数.

//初始化ETH MAC层及DMA配置
//返回值:ETH_ERROR,发送失败(0)
//		ETH_SUCCESS,发送成功(1)
u8 ETH_MACDMA_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;	//对单播地址使用完美地址过滤 
#ifdef CHECKSUM_BY_HARDWARE
	ETH_InitStructure.ETH_ChecksumOffload = ETH_ChecksumOffload_Enable; 			//开启ipv4和TCP/UDP/ICMP的帧校验和卸载   
#endif
	//当我们使用帧校验和卸载功能的时候,一定要使能存储转发模式,存储转发模式中要保证整个帧存储在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_2_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);  	//使能以太网接收中断	
	}
	return rval;
}


//以太网中断服务函数
void ETH_IRQHandler(void)
{
	portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
	
//	if ( ETH_GetDMAFlagStatus(ETH_DMA_FLAG_R) == SET) 
	if(ETH_GetRxPktSize(DMARxDescToGet)!=0) 	//检测是否收到数据包
	{ 
		xSemaphoreGiveFromISR( s_xSemaphore, &xHigherPriorityTaskWoken );   	
	}
	
	ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
	ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
	
	/* Switch tasks if necessary. */	
  if( xHigherPriorityTaskWoken != pdFALSE )
  {
    portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
  }
}  

//----------------------------- 库函数重定义 ----------------------------------//
//接收一个网卡数据包
//返回值:网络数据包帧结构体
FrameTypeDef ETH_Rx_Packet(void)
{ 
	u32 framelength=0;
	FrameTypeDef frame={0,0};   
	//检查当前描述符,是否属于ETHERNET DMA(设置的时候)/CPU(复位的时候)
	if((DMARxDescToGet->Status&ETH_DMARxDesc_OWN)!=(u32)RESET)
	{	
		frame.length=ETH_ERROR; 
		if ((ETH->DMASR&ETH_DMASR_RBUS)!=(u32)RESET)  
		{ 
			ETH->DMASR = ETH_DMASR_RBUS;//清除ETH DMA的RBUS位 
			ETH->DMARPDR=0;//恢复DMA接收
		}
		return frame;//错误,OWN位被设置了
	}  
	if(((DMARxDescToGet->Status&ETH_DMARxDesc_ES)==(u32)RESET)&& 
	((DMARxDescToGet->Status & ETH_DMARxDesc_LS)!=(u32)RESET)&&  
	((DMARxDescToGet->Status & ETH_DMARxDesc_FS)!=(u32)RESET))  
	{       
		framelength=((DMARxDescToGet->Status&ETH_DMARxDesc_FL)>>ETH_DMARxDesc_FrameLengthShift)-4;//得到接收包帧长度(不包含4字节CRC)
 		frame.buffer = DMARxDescToGet->Buffer1Addr;//得到包数据所在的位置
	}else framelength=ETH_ERROR;//错误  
	frame.length=framelength; 
	frame.descriptor=DMARxDescToGet;  
	//更新ETH DMA全局Rx描述符为下一个Rx描述符
	//为下一次buffer读取设置下一个DMA Rx描述符
	DMARxDescToGet=(ETH_DMADESCTypeDef*)(DMARxDescToGet->Buffer2NextDescAddr);   
	return frame;  
}
//发送一个网卡数据包
//FrameLength:数据包长度
//返回值:ETH_ERROR,发送失败(0)
//		ETH_SUCCESS,发送成功(1)
u8 ETH_Tx_Packet(u16 FrameLength)
{   
	//检查当前描述符,是否属于ETHERNET DMA(设置的时候)/CPU(复位的时候)
	if((DMATxDescToSet->Status&ETH_DMATxDesc_OWN)!=(u32)RESET)return ETH_ERROR;//错误,OWN位被设置了 
 	DMATxDescToSet->ControlBufferSize=(FrameLength&ETH_DMATxDesc_TBS1);//设置帧长度,bits[12:0]
	DMATxDescToSet->Status|=ETH_DMATxDesc_LS|ETH_DMATxDesc_FS;//设置最后一个和第一个位段置位(1个描述符传输一帧)
  	DMATxDescToSet->Status|=ETH_DMATxDesc_OWN;//设置Tx描述符的OWN位,buffer重归ETH DMA
	if((ETH->DMASR&ETH_DMASR_TBUS)!=(u32)RESET)//当Tx Buffer不可用位(TBUS)被设置的时候,重置它.恢复传输
	{ 
		ETH->DMASR=ETH_DMASR_TBUS;//重置ETH DMA TBUS位 
		ETH->DMATPDR=0;//恢复DMA发送
	} 
	//更新ETH DMA全局Tx描述符为下一个Tx描述符
	//为下一次buffer发送设置下一个DMA Tx描述符 
	DMATxDescToSet=(ETH_DMADESCTypeDef*)(DMATxDescToSet->Buffer2NextDescAddr);    
	return ETH_SUCCESS;   
}

//-------------------------------- 内存申请 ----------------------------------//
//释放ETH 底层驱动申请的内存
void ETH_Mem_Free(void)
{ 
	myfree(SRAMIN,DMARxDscrTab);//释放内存
	myfree(SRAMIN,DMATxDscrTab);//释放内存
	myfree(SRAMIN,Rx_Buff);		//释放内存
	myfree(SRAMIN,Tx_Buff);		//释放内存  
}


//为ETH底层驱动申请内存
//返回值:0,正常
//    其他,失败
uint8_t 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;		//申请成功
}

  此时编译会有一个报错,提示未定义变量s_xSemaphore,这是后面Lwip移植ethernetif.c时定义的一个信号量,这时候可以不用管,也可以先屏蔽
在这里插入图片描述

三、LWIP 数据包和网络接口管理

3.1 添加LWIP源文件

  在工程目录下新建文件夹LWIP,然后把下载的lwip-1.4.1\src文件夹复制进去,然后再添加arch文件夹
在这里插入图片描述
  然后我们需要将中间文件添加到arch文件夹,总共5个文件cc.hlwipopts.hperf.hsys_arch.hsys_arch.c。这些都可以在ST参考例程STM32F4x7_ETH_LwIP_V1.1.0里找到,由于都需要修改,建议直接复制附件工程或者在Keil里新建
在这里插入图片描述
  然后将src\api文件夹里所有文件,src\core里的c文件和ipv4里的所有文件,以及src\netif里的ethernetif.cetharp.c添加到Keil工程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  以及sys_arch.c,添加进来如下
在这里插入图片描述

在这里插入图片描述
  最后将头文件加进来
在这里插入图片描述

3.2 Lwip文件修改

3.2.1 修改cc.h

  cc.h 主要完成了协议栈内部使用的数据类型的定义,如果使用操作系统的话还有临界代码区保护等等

#ifndef __CC_H__
#define __CC_H__

//#include "cpu.h"
#include "stdio.h"

/*-------------data type------------------------------------------------------*/

typedef unsigned   char    u8_t;    /* Unsigned 8 bit quantity         */
typedef signed     char    s8_t;    /* Signed    8 bit quantity        */
typedef unsigned   short   u16_t;   /* Unsigned 16 bit quantity        */
typedef signed     short   s16_t;   /* Signed   16 bit quantity        */
typedef unsigned   long    u32_t;   /* Unsigned 32 bit quantity        */
typedef signed     long    s32_t;   /* Signed   32 bit quantity        */
typedef u32_t mem_ptr_t;            /* Unsigned 32 bit quantity        */
typedef int sys_prot_t;

/*-------------critical region protection (depends on uC/OS-II setting)-------*/

#define BYTE_ORDER LITTLE_ENDIAN  //小端模式

#if OS_CRITICAL_METHOD == 1
#define SYS_ARCH_DECL_PROTECT(lev)
#define SYS_ARCH_PROTECT(lev)		CPU_INT_DIS()
#define SYS_ARCH_UNPROTECT(lev)		CPU_INT_EN()
#endif

#if OS_CRITICAL_METHOD == 3  //method 3 is used in this port.
#define SYS_ARCH_DECL_PROTECT(lev)	u32_t lev
#define SYS_ARCH_PROTECT(lev)		lev = OS_CPU_SR_Save()
#define SYS_ARCH_UNPROTECT(lev)		OS_CPU_SR_Restore(lev)
#endif

/*----------------------------------------------------------------------------*/

/* define compiler specific symbols */
#if defined (__ICCARM__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT 
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES

#elif defined (__CC_ARM)

#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT 
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#elif defined (__GNUC__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#elif defined (__TASKING__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#endif

/*---define (sn)printf formatters for these lwip types, for lwip DEBUG/STATS--*/

#define U16_F "4d"
#define S16_F "4d"
#define X16_F "4x"
#define U32_F "8ld"
#define S32_F "8ld"
#define X32_F "8lx"

/*--------------macros--------------------------------------------------------*/
#ifndef LWIP_PLATFORM_ASSERT
#define LWIP_PLATFORM_ASSERT(x) \
    do \
    {   printf("Assertion \"%s\" failed at line %d in %s\r\n", x, __LINE__, __FILE__); \
    } while(0)
#endif

#ifndef LWIP_PLATFORM_DIAG
#define LWIP_PLATFORM_DIAG(x) do {printf x;} while(0)
#endif

#endif /* __CC_H__ */

3.2.2 修改lwipopts.h

  在 LWIP 的源码中有个opt.h的文件,这个文件是裁剪和配置 LWIP 的,不过我们最好不要直接在 opt.h 里面做修改,而是在lwipopts.h里进行来裁剪与配置

#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__

#define SYS_LIGHTWEIGHT_PROT    1

//NO_SYS==1:不使用操作系统
#define NO_SYS                  0  //不使用操作系统

//使用4字节对齐模式
#define MEM_ALIGNMENT           4  

//MEM_SIZE:heap内存的大小,如果在应用中有大量数据发送的话这个值最好设置大一点 
#define MEM_SIZE                16000 //内存堆大小

//MEMP_NUM_PBUF:memp结构的pbuf数量,如果应用从ROM或者静态存储区发送大量数据时,这个值应该设置大一点
#define MEMP_NUM_PBUF           10

//MEMP_NUM_UDP_PCB:UDP协议控制块(PCB)数量.每个活动的UDP"连接"需要一个PCB.
#define MEMP_NUM_UDP_PCB        6

//MEMP_NUM_TCP_PCB:同时建立激活的TCP数量
#define MEMP_NUM_TCP_PCB        10

//MEMP_NUM_TCP_PCB_LISTEN:能够监听的TCP连接数量
#define MEMP_NUM_TCP_PCB_LISTEN 6

//MEMP_NUM_TCP_SEG:最多同时在队列中的TCP段数量
#define MEMP_NUM_TCP_SEG        15

//MEMP_NUM_SYS_TIMEOUT:能够同时激活的timeout个数
#define MEMP_NUM_SYS_TIMEOUT    8


/* ---------- Pbuf选项---------- */
//PBUF_POOL_SIZE:pbuf内存池个数. 
#define PBUF_POOL_SIZE          20

//PBUF_POOL_BUFSIZE:每个pbuf内存池大小. 
#define PBUF_POOL_BUFSIZE       512


/* ---------- TCP选项---------- */
#define LWIP_TCP                1  //为1是使用TCP
#define TCP_TTL                 255//生存时间

/*当TCP的数据段超出队列时的控制位,当设备的内存过小的时候此项应为0*/
#define TCP_QUEUE_OOSEQ         0

//最大TCP分段
#define TCP_MSS                 (1500 - 40)	  //TCP_MSS = (MTU - IP报头大小 - TCP报头大小

//TCP发送缓冲区大小(bytes).
#define TCP_SND_BUF             (4*TCP_MSS)

//TCP_SND_QUEUELEN: TCP发送缓冲区大小(pbuf).这个值最小为(2 * TCP_SND_BUF/TCP_MSS) 
#define TCP_SND_QUEUELEN        (2* TCP_SND_BUF/TCP_MSS)

//TCP发送窗口
#define TCP_WND                 (2*TCP_MSS)


/* ---------- ICMP选项---------- */
#define LWIP_ICMP                 1 //使用ICMP协议

/* ---------- DHCP选项---------- */
//当使用DHCP时此位应该为1,LwIP 0.5.1版本中没有DHCP服务.
#define LWIP_DHCP               1

/* ---------- UDP选项 ---------- */ 
#define LWIP_UDP                1 //使用UDP服务
#define UDP_TTL                 255 //UDP数据包生存时间


/* ---------- Statistics options ---------- */
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1


/* ---------- 链接回调选项 ---------- */
/* WIP_NETIF_LINK_CALLBACK==1:支持来自接口的回调函数
   每当链接改变(例如,向下链接)
 */
#define LWIP_NETIF_LINK_CALLBACK        0


//STM32F4x7允许通过硬件识别和计算IP,UDP和ICMP的帧校验和
#define CHECKSUM_BY_HARDWARE //定义CHECKSUM_BY_HARDWARE,使用硬件帧校验


#ifdef CHECKSUM_BY_HARDWARE
  //CHECKSUM_GEN_IP==0: 硬件生成IP数据包的帧校验和
  #define CHECKSUM_GEN_IP                 0
  //CHECKSUM_GEN_UDP==0: 硬件生成UDP数据包的帧校验和
  #define CHECKSUM_GEN_UDP                0
  //CHECKSUM_GEN_TCP==0: 硬件生成TCP数据包的帧校验和
  #define CHECKSUM_GEN_TCP                0 
  //CHECKSUM_CHECK_IP==0: 硬件检查输入的IP数据包帧校验和
  #define CHECKSUM_CHECK_IP               0
  //CHECKSUM_CHECK_UDP==0: 硬件检查输入的UDP数据包帧校验和
  #define CHECKSUM_CHECK_UDP              0
  //CHECKSUM_CHECK_TCP==0: 硬件检查输入的TCP数据包帧校验和
  #define CHECKSUM_CHECK_TCP              0
#else
  //CHECKSUM_GEN_IP==1: 软件生成IP数据包帧校验和
  #define CHECKSUM_GEN_IP                 1
  // CHECKSUM_GEN_UDP==1: 软件生成UDOP数据包帧校验和
  #define CHECKSUM_GEN_UDP                1
  //CHECKSUM_GEN_TCP==1: 软件生成TCP数据包帧校验和
  #define CHECKSUM_GEN_TCP                1
  // CHECKSUM_CHECK_IP==1: 软件检查输入的IP数据包帧校验和
  #define CHECKSUM_CHECK_IP               1
  // CHECKSUM_CHECK_UDP==1: 软件检查输入的UDP数据包帧校验和
  #define CHECKSUM_CHECK_UDP              1
  //CHECKSUM_CHECK_TCP==1: 软件检查输入的TCP数据包帧校验和
  #define CHECKSUM_CHECK_TCP              1
#endif


/*
   ----------------------------------------------
   ---------- SequentialAPI选项----------
   ----------------------------------------------
*/

//LWIP_NETCONN==1:使能NETCON函数(要求使用api_lib.c)
#define LWIP_NETCONN                    1

/*
   ------------------------------------
   ---------- Socket API选项----------
   ------------------------------------
*/
//LWIP_SOCKET==1:使能Socket API(要求使用sockets.c)
#define LWIP_SOCKET                     1

#define LWIP_COMPAT_MUTEX               1

#define LWIP_SO_RCVTIMEO                1 //通过定义LWIP_SO_RCVTIMEO使能netconn结构体中recv_timeout,使用recv_timeout可以避免阻塞线程


/*
   ----------------------------------------
   ---------- Lwip调试选项----------
   ----------------------------------------
*/
//#define LWIP_DEBUG                     1 //开启DEBUG选项

#define ICMP_DEBUG                      LWIP_DBG_OFF //开启/关闭ICMPdebug

/*
   ------------------------------------
   ---------- httpd options ----------
   ------------------------------------
*/
/** Set this to 1 to include "fsdata_custom.c" instead of "fsdata.c" for the
 * file system (to prevent changing the file included in CVS) */
#define HTTPD_USE_CUSTOM_FSDATA         1

#endif /* __LWIPOPTS_H__ */

3.2.3 修改icmp.c

  我们需要修改 icmp.c 文档使其支持硬件帧校验,修改部分如图所示。图中红框部分为 icmp.c 的源码,我们将其注销掉,蓝框部分是我们需要添加进去的代码,这部分代码是由 ST提供的。
在这里插入图片描述

#ifdef CHECKSUM_BY_HARDWARE
    iecho->chksum = 0;
#else
	/* adjust the checksum */
    if (iecho->chksum >= htons(0xffff - (ICMP_ECHO << 8))) {
      iecho->chksum += htons(ICMP_ECHO << 8) + 1;
    } else {
      iecho->chksum += htons(ICMP_ECHO << 8);
    }	
#endif

3.2.4 修改sys_arch.h和sys_arch.c

  这两个文件主要为协议栈提供邮箱、信号量等机制,实际上就是对RTOS的机制进行一次封装

  1. sys_arch.h
#ifndef __ARCH_SYS_ARCH_H__
#define __ARCH_SYS_ARCH_H__ 

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

void sys_arch_msleep(uint32_t delay_ms);
#define sys_msleep(ms) sys_arch_msleep(ms)

#define SYS_MBOX_NULL (xQueueHandle)0
#define SYS_SEM_NULL  (xSemaphoreHandle)0

typedef xSemaphoreHandle sys_sem_t;
typedef xSemaphoreHandle sys_mutex_t;
typedef xQueueHandle sys_mbox_t;
typedef xTaskHandle sys_thread_t;

#endif 
  1. sys_arch.c
#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/sys.h"
#include "lwip/mem.h"
#include "lwip/stats.h"
#include "FreeRTOS.h"
#include "task.h"


//u32_t sys_jiffies(void)
//{
//  return xTaskGetTickCount();
//}

//static u16_t s_nextthread = 0;

/* Initialize this module (see description in sys.h) */
void sys_init(void)
{
	// keep track of how many threads have been created
//  s_nextthread = 0;
}


#if SYS_LIGHTWEIGHT_PROT

sys_prot_t sys_arch_protect(void)
{
	vPortEnterCritical();
	return 1;
}

void sys_arch_unprotect(sys_prot_t pval)
{
	( void ) pval;
	vPortExitCritical();
}

#endif /* SYS_LIGHTWEIGHT_PROT */



void sys_sem_signal(sys_sem_t *sem)
{
  xSemaphoreGive(*sem);
}

err_t sys_sem_new(sys_sem_t *sem, u8_t count)
{
  /* 创建 sem */
  if(count <= 1)
  {    
    *sem = xSemaphoreCreateBinary();
    if(count == 1)
    {
      sys_sem_signal(sem);
    }
		else
		{
			xSemaphoreTake(*sem,1);
		}
  }
  else
    *sem = xSemaphoreCreateCounting(count,count);
	
#if SYS_STATS
	++lwip_stats.sys.sem.used;
 	if (lwip_stats.sys.sem.max < lwip_stats.sys.sem.used) {
		lwip_stats.sys.sem.max = lwip_stats.sys.sem.used;
	}
#endif /* SYS_STATS */
  
  if(*sem != SYS_SEM_NULL)
    return ERR_OK;
  else
  {
#if SYS_STATS
    ++lwip_stats.sys.sem.err;
#endif /* SYS_STATS */
    printf("[sys_arch]:new sem fail!\n");
    return ERR_MEM;
  }
}

u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
{
  BaseType_t ret;
	portTickType StartTime;
	
	StartTime = xTaskGetTickCount();

  if(!timeout) {
    /* wait infinite */
    ret = xSemaphoreTake(*sem, portMAX_DELAY);
  } else {
    ret = xSemaphoreTake(*sem, timeout / portTICK_RATE_MS);
  }
	
	if (ret == pdTRUE)
	{
		return ((xTaskGetTickCount()-StartTime)*portTICK_RATE_MS);
	}
	else
	{
		/* timed out */
		return SYS_ARCH_TIMEOUT;
  }
}

void sys_sem_free(sys_sem_t *sem)
{
#if SYS_STATS
   --lwip_stats.sys.sem.used;
#endif /* SYS_STATS */
  /* 删除 sem */
  vSemaphoreDelete(*sem);
  *sem = SYS_SEM_NULL;
}

int sys_sem_valid(sys_sem_t *sem)                                               
{
  return (*sem != SYS_SEM_NULL);                                    
}

void sys_sem_set_invalid(sys_sem_t *sem)
{
  *sem = SYS_SEM_NULL;
}


err_t sys_mbox_new(sys_mbox_t *mbox, int size)
{
	(void ) size;
	
  *mbox = xQueueCreate((UBaseType_t)size, sizeof(void *));
	
  if(*mbox == NULL) {
    return ERR_MEM;
  }

  return ERR_OK;
}



void sys_mbox_post(sys_mbox_t *mbox, void *msg)
{
  while(xQueueSendToBack(*mbox, &msg, portMAX_DELAY) != pdTRUE);
}


//   Try to post the "msg" to the mailbox.
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
{
  if(xQueueSend(*mbox,&msg,0) == pdPASS)  
    return ERR_OK;
  else
    return ERR_MEM;
}


u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
{
  BaseType_t ret;
  void *msg_dummy;
	portTickType StartTime;
	
	StartTime = xTaskGetTickCount();

  if (!msg) {
    msg = &msg_dummy;
  }

  if (!timeout) {
    /* wait infinite */
    ret = xQueueReceive(*mbox, &(*msg), portMAX_DELAY);
  } else {
    ret = xQueueReceive(*mbox, &(*msg), timeout / portTICK_RATE_MS);
  }
	
	if (ret == pdTRUE)
	{
		return ((xTaskGetTickCount() - StartTime)*portTICK_PERIOD_MS);
	}
	else
	{
		/* timed out */
		*msg = NULL;
		return SYS_ARCH_TIMEOUT;
	}
}

u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
{
  void *dummyptr;

	if ( msg == NULL )
	{
		msg = &dummyptr;
	}

	if ( pdTRUE == xQueueReceive( *mbox, &(*msg), 0 ) )
	{
		return ERR_OK;
	}
	else
	{
		return SYS_MBOX_EMPTY;
	}
}

void sys_mbox_free(sys_mbox_t *mbox)
{
  if( uxQueueMessagesWaiting( *mbox ) )
	{
		/* Line for breakpoint.  Should never break here! */
		portNOP();
#if SYS_STATS
	    lwip_stats.sys.mbox.err++;
#endif /* SYS_STATS */
			
		// TODO notify the user of failure.
	}

  vQueueDelete(*mbox);

#if SYS_STATS
     --lwip_stats.sys.mbox.used;
#endif /* SYS_STATS */
}

int sys_mbox_valid(sys_mbox_t *mbox)          
{      
  if (*mbox == SYS_MBOX_NULL) 
    return 0;
  else
    return 1;
}   

void sys_mbox_set_invalid(sys_mbox_t *mbox)
{
  *mbox = SYS_MBOX_NULL; 
}

sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread, void *arg, int stacksize, int prio)
{
  xTaskHandle rtos_task;
  BaseType_t ret;

  ret = xTaskCreate(thread, name, stacksize, arg, prio, &rtos_task);

  if(ret != pdPASS)
	{
		return NULL;
	}
		
  return rtos_task;
}

void sys_arch_msleep(uint32_t delay_ms)
{
  TickType_t delay_ticks = delay_ms / portTICK_RATE_MS;
  vTaskDelay(delay_ticks);
}

//为LWIP提供计时
u32_t sys_now(void){
	return xTaskGetTickCount() * portTICK_PERIOD_MS;
}

  函数功能如下表

函数名称功能
sys_mbox_new创建消息邮箱
sys_mbox_free删除一个邮箱
sys_mbox_post向邮箱投递消息,阻塞
sys_mbox_trypost尝试向邮箱投递消息,不阻塞
sys_arch_mbox_fetch获取消息,阻塞
sys_arch_mbox_tryfetch尝试获取消息,不阻塞
sys_mbox_valid检查一个邮箱是否有效
sys_mbox_set_invalid设置一个邮箱无效
sys_sem_new创建一个信号量
sys_arch_sem_wait等待一个信号量
sys_sem_signal释放一个信号量
sys_sem_free删除一个信号量
sys_sem_valid查询一个信号量是否有效
sys_sem_set_invalid设置一个信号量无效
sys_thread_new创建进程
sys_init初始化操作系统模拟层
sys_msleepLWIP 延时函数
sys_now获取当前系统时间

3.2.5 修改ethernetif.h和ethernetif.c

  1. ethernetif.h
#ifndef __ETHERNETIF_H__
#define __ETHERNETIF_H__

#include "lwip/err.h"
#include "lwip/netif.h"
 
#define NETIF_IN_TASK_STACK_SIZE    ( 1024 )
#define NETIF_IN_TASK_PRIORITY      ( 2 )
 
//网卡的名字
#define IFNAME0 'e'
#define IFNAME1 'n'
 

err_t ethernetif_init(struct netif *netif);

#endif
  1. ethernetif.c
      该文件为网卡驱动文件,用来描述硬件网卡的收发函数及相关信息。该文件定义的函数如下表所示:
函数描述
low_level_init()底层初始化
low_level_output()数据包发送函数
low_level_input()获取数据报函数
ethernetif_input()处理数据包函数
ethernetif_init()网卡初始化
#include "netif/ethernetif.h" 
#include "ethernet.h"  
#include "lwip_app.h" 
#include "netif/etharp.h" 
#include "lwip/sys.h"
#include "string.h"  
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"


/* 定义一个信号量 */
xSemaphoreHandle s_xSemaphore = NULL;


/* Forward declarations. */
void  ethernetif_input(void *pParams);


//由ethernetif_init()调用用于初始化硬件
//netif:网卡结构体指针 
//返回值:ERR_OK,正常
//       其他,失败
static void low_level_init(struct netif *netif)
{
#ifdef CHECKSUM_BY_HARDWARE
	int i; 
#endif 
	netif->hwaddr_len = ETHARP_HWADDR_LEN; //设置MAC地址长度,为6个字节
	//初始化MAC地址,设置什么地址由用户自己设置,但是不能与网络中其他设备MAC地址重复
	netif->hwaddr[0]=MAC_ADDR0; 
	netif->hwaddr[1]=MAC_ADDR1;
	netif->hwaddr[2]=MAC_ADDR2;
	netif->hwaddr[3]=MAC_ADDR3;
	netif->hwaddr[4]=MAC_ADDR4;
	netif->hwaddr[5]=MAC_ADDR5;
	
	netif->mtu=1500; //最大允许传输单元,允许该网卡广播和ARP功能

	netif->flags = NETIF_FLAG_BROADCAST|NETIF_FLAG_ETHARP|NETIF_FLAG_LINK_UP;
	
	ETH_MACAddressConfig(ETH_MAC_Address0, netif->hwaddr); //向STM32F4的MAC地址寄存器中写入MAC地址
	ETH_DMATxDescChainInit(DMATxDscrTab, Tx_Buff, ETH_TXBUFNB);
	ETH_DMARxDescChainInit(DMARxDscrTab, Rx_Buff, ETH_RXBUFNB);
#ifdef CHECKSUM_BY_HARDWARE 	//使用硬件帧校验
	for(i=0;i<ETH_TXBUFNB;i++)	//使能TCP,UDP和ICMP的发送帧校验,TCP,UDP和ICMP的接收帧校验在DMA中配置了
	{
		ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i], ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
	}
#endif
	
	/* 创建一个信号量 */
	if(s_xSemaphore == NULL)
	{
		s_xSemaphore = xSemaphoreCreateBinary();
		xSemaphoreTake( s_xSemaphore, 0);
	}
	
	/* 创建处理ETH_MAC的任务 */
	sys_thread_new("eth_thread",
								 ethernetif_input,        /* 任务入口函数 */
								 netif,                   /* 任务入口函数参数 */
								 NETIF_IN_TASK_STACK_SIZE,/* 任务栈大小 */
								 NETIF_IN_TASK_PRIORITY); /* 任务的优先级 */
	
	ETH_Start(); //开启MAC和DMA				
} 
//用于发送数据包的最底层函数(lwip通过netif->linkoutput指向该函数)
//netif:网卡结构体指针
//p:pbuf数据结构体指针
//返回值:ERR_OK,发送正常
//       ERR_MEM,发送失败
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
	u8 res;
	struct pbuf *q;
	int l = 0;
	
	u8 *buffer=(u8 *)(DMATxDescToSet->Buffer1Addr); //获取当前要发送的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); //调用ETH_Tx_Packet函数发送数据
	if(res==ETH_ERROR)return ERR_MEM;//返回错误状态
	return ERR_OK;
} 

///用于接收数据包的最底层函数
//neitif:网卡结构体指针
//返回值:pbuf数据结构体指针
static struct pbuf * low_level_input(struct netif *netif)
{  
	struct pbuf *p= NULL, *q;
	u16_t len;
	int l =0;
	FrameTypeDef frame;
	u8 *buffer;
	
	frame=ETH_Rx_Packet();
	len=frame.length;//得到包大小
	buffer=(u8 *)frame.buffer;//得到包数据地址 
	
	if(len > 0)
	{
		p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL);//pbufs内存池分配pbuf
	}
	
	if(p!=NULL)
	{
		/* 将接收描述符中Rx Buffer的数据拷贝到pbuf中 */
		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;
}


void ethernetif_input(void *pParams)
{
	struct netif *netif;
	struct pbuf *p = NULL;
	netif = (struct netif*) pParams;
  LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
	
  while(1)
  {
    if (xSemaphoreTake( s_xSemaphore, portMAX_DELAY)==pdTRUE)
    {
			/* move received packet into a new pbuf */
      taskENTER_CRITICAL();
TRY_GET_NEXT_FRAME:
      p = low_level_input( netif );//调用low_level_input函数接收数据
	taskEXIT_CRITICAL();
			
      if(p != NULL)
      {
		taskENTER_CRITICAL();
				
        if (ERR_OK != netif->input( p, netif))//调用netif结构体中的input字段(一个函数)来处理数据包
        {
          LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
          pbuf_free(p);
          p = NULL;
        }
        else
        {
			xSemaphoreTake( s_xSemaphore, 0);
          goto TRY_GET_NEXT_FRAME;
        }	
		taskEXIT_CRITICAL();
      }
    }
  }
} 
//使用low_level_init()函数来初始化网络
//netif:网卡结构体指针
//返回值:ERR_OK,正常
//       其他,失败
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层发送数据包函数
	netif->linkoutput=low_level_output;//ARP模块发送数据包函数
	low_level_init(netif); 		//底层硬件初始化函数
	return ERR_OK;
}

  这里就能看到2.3小节报错未定义变量xSemaphoreHandle s_xSemaphore = NULL在这里定义了。此时再编译发现两个报警,不用管
在这里插入图片描述

四、添加应用程序

在这里插入图片描述

4.1 添加Lwip应用

  1. lwip_app.h
#ifndef _LWIP_COMM_H
#define _LWIP_COMM_H 
#include "ethernet.h" 


#define LWIP_MAX_DHCP_TRIES		4   //DHCP服务器最大重试次数

typedef void (*display_fn)(uint8_t index);
   
//lwip控制结构体
typedef struct  
{
	uint8_t mac[6];                 /* MAC地址 */
	uint8_t remoteip[4];            /* 远端主机IP地址 */ 
	uint8_t ip[4];                  /* 本机IP地址 */
	uint8_t netmask[4];             /* 子网掩码 */
	uint8_t gateway[4];             /* 默认网关的IP地址 */
	uint8_t dhcpstatus;             /* dhcp状态 */
									/* 0, 未获取DHCP地址;*/
									/* 1, 进入DHCP获取状态*/
									/* 2, 成功获取DHCP地址*/
									/* 0XFF,获取失败 */
	uint8_t link_status;            /* 连接状态 */
	display_fn lwip_display_fn;     /* 显示函数指针 */
}__lwip_dev;
extern __lwip_dev g_lwipdev;	//lwip控制结构体

uint8_t lwip_comm_init(void);

#endif
  1. lwip_app.c
      主要是初始化和开启DHCP
#include "lwip_app.h" 
#include "netif/etharp.h"
#include "lwip/dhcp.h"
#include "lwip/mem.h"
#include "lwip/memp.h"
#include "lwip/init.h"
#include "ethernetif.h" 
#include "lwip/timers.h"
#include "lwip/tcp_impl.h"
#include "lwip/ip_frag.h"
#include "lwip/tcpip.h" 
#include "malloc.h"
#include "delay.h"
#include "usart.h"  
#include <stdio.h>

   
__lwip_dev g_lwipdev;						//lwip控制结构体 
struct netif g_lwip_netif;				//定义一个全局的网络接口


/* DHCP线程配置 */
#define LWIP_DHCP_TASK_PRIO             4                   /* 任务优先级 */
#define LWIP_DHCP_STK_SIZE              128 * 2             /* 任务堆栈大小 */
void lwip_periodic_handle(void *argument);                  /* DHCP线程 */

//extern u32 memp_get_memorysize(void);	//在memp.c里面定义
//extern u8_t *memp_memory;				//在memp.c里面定义.
//extern u8_t *ram_heap;					//在mem.c里面定义.

u32 TCPTimer=0;			//TCP查询计时器
u32 ARPTimer=0;			//ARP查询计时器
u32 lwip_localtime;		//lwip本地时间计数器,单位:ms

#if LWIP_DHCP
u32 DHCPfineTimer=0;	//DHCP精细处理计时器
u32 DHCPcoarseTimer=0;	//DHCP粗糙处理计时器
#endif



//lwip 默认IP设置
//lwipx:lwip控制结构体指针
void lwip_comm_default_ip_set(__lwip_dev *lwipx)
{
	u32 sn0;
	sn0=*(vu32*)(0x1FFF7A10);//获取STM32的唯一ID的前24位作为MAC地址后三字节
	//默认远端IP为:192.168.1.100
	lwipx->remoteip[0]=192;	
	lwipx->remoteip[1]=168;
	lwipx->remoteip[2]=1;
	lwipx->remoteip[3]=104;
	//MAC地址设置(高三字节固定为:2.0.0,低三字节用STM32唯一ID)
	lwipx->mac[0]=2;//高三字节(IEEE称之为组织唯一ID,OUI)地址固定为:2.0.0
	lwipx->mac[1]=0;
	lwipx->mac[2]=0;
	lwipx->mac[3]=(sn0>>16)&0XFF;//低三字节用STM32的唯一ID
	lwipx->mac[4]=(sn0>>8)&0XFFF;;
	lwipx->mac[5]=sn0&0XFF; 
	//默认本地IP为:192.168.1.30
	lwipx->ip[0]=192;	
	lwipx->ip[1]=168;
	lwipx->ip[2]=1;
	lwipx->ip[3]=30;
	//默认子网掩码:255.255.255.0
	lwipx->netmask[0]=255;	
	lwipx->netmask[1]=255;
	lwipx->netmask[2]=255;
	lwipx->netmask[3]=0;
	//默认网关:192.168.1.1
	lwipx->gateway[0]=192;	
	lwipx->gateway[1]=168;
	lwipx->gateway[2]=1;
	lwipx->gateway[3]=1;	
	lwipx->dhcpstatus=0;//没有DHCP	
} 



/* 如果使能DHCP */
#if LWIP_DHCP

/**
 * @breif       DHCP进程
 * @param       argument:传入的形参
 * @retval      无
 */
void lwip_periodic_handle(void *argument)
{
  u32 ip=0,netmask=0,gw=0;
	dhcp_start(&g_lwip_netif);//开启DHCP 
	g_lwipdev.dhcpstatus=0;	//正在DHCP
	printf("正在查找DHCP服务器,请稍等...........\r\n");   
	while(1)
	{ 
		printf("正在获取地址...\r\n");
		ip=g_lwip_netif.ip_addr.addr;		//读取新IP地址
		netmask=g_lwip_netif.netmask.addr;//读取子网掩码
		gw=g_lwip_netif.gw.addr;			//读取默认网关 
		if(ip!=0)   					//当正确读取到IP地址的时候
		{
			g_lwipdev.dhcpstatus=2;	//DHCP成功
 			printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",g_lwipdev.mac[0],g_lwipdev.mac[1],g_lwipdev.mac[2],g_lwipdev.mac[3],g_lwipdev.mac[4],g_lwipdev.mac[5]);
			//解析出通过DHCP获取到的IP地址
			g_lwipdev.ip[3]=(uint8_t)(ip>>24); 
			g_lwipdev.ip[2]=(uint8_t)(ip>>16);
			g_lwipdev.ip[1]=(uint8_t)(ip>>8);
			g_lwipdev.ip[0]=(uint8_t)(ip);
			printf("通过DHCP获取到IP地址..............%d.%d.%d.%d\r\n",g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
			//解析通过DHCP获取到的子网掩码地址
			g_lwipdev.netmask[3]=(uint8_t)(netmask>>24);
			g_lwipdev.netmask[2]=(uint8_t)(netmask>>16);
			g_lwipdev.netmask[1]=(uint8_t)(netmask>>8);
			g_lwipdev.netmask[0]=(uint8_t)(netmask);
			printf("通过DHCP获取到子网掩码............%d.%d.%d.%d\r\n",g_lwipdev.netmask[0],g_lwipdev.netmask[1],g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
			//解析出通过DHCP获取到的默认网关
			g_lwipdev.gateway[3]=(uint8_t)(gw>>24);
			g_lwipdev.gateway[2]=(uint8_t)(gw>>16);
			g_lwipdev.gateway[1]=(uint8_t)(gw>>8);
			g_lwipdev.gateway[0]=(uint8_t)(gw);
			printf("通过DHCP获取到的默认网关..........%d.%d.%d.%d\r\n",g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
			break;
		}else if(g_lwip_netif.dhcp->tries>LWIP_MAX_DHCP_TRIES) //通过DHCP服务获取IP地址失败,且超过最大尝试次数
		{  
			g_lwipdev.dhcpstatus=0XFF;//DHCP失败.
			//使用静态IP地址
			IP4_ADDR(&(g_lwip_netif.ip_addr),g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
			IP4_ADDR(&(g_lwip_netif.netmask),g_lwipdev.netmask[0],g_lwipdev.netmask[1],g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
			IP4_ADDR(&(g_lwip_netif.gw),g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
			printf("DHCP服务超时,使用静态IP地址!\r\n");
			printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",g_lwipdev.mac[0],g_lwipdev.mac[1],g_lwipdev.mac[2],g_lwipdev.mac[3],g_lwipdev.mac[4],g_lwipdev.mac[5]);
			printf("静态IP地址........................%d.%d.%d.%d\r\n",g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
			printf("子网掩码..........................%d.%d.%d.%d\r\n",g_lwipdev.netmask[0],g_lwipdev.netmask[1],g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
			printf("默认网关..........................%d.%d.%d.%d\r\n",g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
			break;
		}  
		delay_ms(250); //延时250ms
	}
	dhcp_stop(&g_lwip_netif); 		//关闭DHCP
}
#endif

//LWIP初始化(LWIP启动的时候使用)
//返回值:0,成功
//      1,内存错误
//      2,LAN8720初始化失败
//      3,网卡添加失败.
u8 lwip_comm_init(void)
{
	struct netif *netif_init_flag;		//调用netif_add()函数时的返回值,用于判断网络初始化是否成功
	struct ip_addr ipaddr;  			//ip地址
	struct ip_addr netmask; 			//子网掩码
	struct ip_addr gw;      			//默认网关 
	
	if(ETH_Mem_Malloc())return 1;		//内存申请失败
	if(LAN8720_Init())return 2;			//初始化LAN8720失败 
	tcpip_init(NULL,NULL);				//初始化tcp ip内核,该函数里面会创建tcpip_thread内核任务
	lwip_comm_default_ip_set(&g_lwipdev);	//设置默认IP等信息

#if LWIP_DHCP		//使用动态IP
	ipaddr.addr = 0;
	netmask.addr = 0;
	gw.addr = 0;
#else				//使用静态IP
	IP4_ADDR(&ipaddr,g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
	IP4_ADDR(&netmask,g_lwipdev.netmask[0],g_lwipdev.netmask[1] ,g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
	IP4_ADDR(&gw,g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
	printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",g_lwipdev.mac[0],g_lwipdev.mac[1],g_lwipdev.mac[2],g_lwipdev.mac[3],g_lwipdev.mac[4],g_lwipdev.mac[5]);
	printf("静态IP地址........................%d.%d.%d.%d\r\n",g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
	printf("子网掩码..........................%d.%d.%d.%d\r\n",g_lwipdev.netmask[0],g_lwipdev.netmask[1],g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
	printf("默认网关..........................%d.%d.%d.%d\r\n",g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
#endif
	netif_init_flag=netif_add(&g_lwip_netif,&ipaddr,&netmask,&gw,NULL,&ethernetif_init,&tcpip_input);//向网卡列表中添加一个网口
	
#if LWIP_DHCP			//如果使用DHCP的话
	g_lwipdev.dhcpstatus=0;	//DHCP标记为0
	dhcp_start(&g_lwip_netif);	//开启DHCP服务
#endif
	
	if(netif_init_flag==NULL)return 3;//网卡添加失败 
	else//网口添加成功后,设置netif为默认值,并且打开netif网口
	{
		netif_set_default(&g_lwip_netif); //设置netif为默认网口
		
		if (netif_is_link_up(&g_lwip_netif))
		{
				netif_set_up(&g_lwip_netif);      /* 打开netif网口 */
		}
		else
		{
				netif_set_down(&g_lwip_netif);
		}
	}
	
#if LWIP_DHCP                                       /* 如果使用DHCP的话 */
    g_lwipdev.dhcpstatus = 0;                       /* DHCP标记为0 */
    /* DHCP轮询任务 */
    sys_thread_new("eth_dhcp",
                   lwip_periodic_handle,            /* 任务入口函数 */
                   &g_lwip_netif,                   /* 任务入口函数参数 */
                   LWIP_DHCP_STK_SIZE,              /* 任务栈大小 */
                   LWIP_DHCP_TASK_PRIO);            /* 任务的优先级 */
#endif	
	
	return 0;//操作OK.
}   

4.2 添加FreeRTOS应用

  在主函数main.c里创建FreeRTOS任务

#include "main.h"
#include "malloc.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "led.h" 
#include "lwip_app.h"

//------------------------ 任务相关定义 ---------------------//
//任务优先级
#define START_TASK_PRIO		5
//任务堆栈大小	
#define START_STK_SIZE 		128  
//任务句柄
TaskHandle_t StartTask_Handler;


//任务优先级
#define KEY_TASK_PRIO		10
//任务堆栈大小	
#define KEY_STK_SIZE 		128  
//任务句柄
TaskHandle_t KeyTask_Handler;


//任务优先级
#define TASK1_TASK_PRIO		11
//任务堆栈大小	
#define TASK1_STK_SIZE 		128  
//任务句柄
TaskHandle_t LedTask_Handler;


//------------------------ 任务函数 ---------------------//

//led任务函数
void led_task(void *pvParameters)
{
	u8 task1_num=0;
	
	while(1)
	{
		task1_num++;	//任务执1行次数加1 注意task1_num1加到255的时候会清零!!
		LED0=!LED0;
		printf("任务1已经执行:%d次\r\n",task1_num);

    vTaskDelay(1000);                           //延时1s,也就是1000个时钟节拍	
	}
}


//key任务函数
void key_task(void *pvParameters)
{
	u8 key;
	while(1)
	{
		key=KEY_Scan(0);
		switch(key)
		{
			case KEY0_PRES:
				vTaskSuspend(LedTask_Handler);//挂起任务1
				printf("挂起任务1的运行!\r\n");
				break;
			case KEY1_PRES:
				vTaskResume(LedTask_Handler);	//恢复任务1
				printf("恢复任务1的运行!\r\n");
				break;
		}
		vTaskDelay(10);			//延时10ms 
	}
}

//开始任务任务函数
void start_task(void *pvParameters)
{
	uint8_t speed;
	
	speed = ethernet_chip_get_speed();      /* 得到网速 */
	printf("speed:%d",speed);
	
	while (lwip_comm_init() != 0)
	{
		delay_ms(500);
	}
	
	taskENTER_CRITICAL();           //进入临界区
	//创建KEY任务
	xTaskCreate((TaskFunction_t )key_task,             
							(const char*    )"key_task",           
							(uint16_t       )KEY_STK_SIZE,        
							(void*          )NULL,                  
							(UBaseType_t    )KEY_TASK_PRIO,        
							(TaskHandle_t*  )&KeyTask_Handler);  
	//创建TASK1任务
	xTaskCreate((TaskFunction_t )led_task,             
							(const char*    )"task1_task",           
							(uint16_t       )TASK1_STK_SIZE,        
							(void*          )NULL,                  
							(UBaseType_t    )TASK1_TASK_PRIO,        
							(TaskHandle_t*  )&LedTask_Handler);   
	vTaskDelete(StartTask_Handler); //删除开始任务
	taskEXIT_CRITICAL();            //退出临界区
}

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
	uart_init(115200);
	delay_init(168);
	KEY_Init();
	LED_Init();
	
	my_mem_init(SRAMIN);  	//初始化内部内存池
	my_mem_init(SRAMCCM); 	//初始化CCM内存池
	
	//创建开始任务
	xTaskCreate((TaskFunction_t )start_task,            //任务函数
							(const char*    )"start_task",          //任务名称
							(uint16_t       )START_STK_SIZE,        //任务堆栈大小
							(void*          )NULL,                  //传递给任务函数的参数
							(UBaseType_t    )START_TASK_PRIO,       //任务优先级
							(TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
	vTaskStartScheduler();          //开启任务调度
}

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

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

相关文章

利用Python filestream实现文件流读

在 Python 中&#xff0c;文件流&#xff08;filestream&#xff09;操作通过内置的 open() 函数实现&#xff0c;它提供了对文件的读取、写入、以及流控制的支持。常见的文件模式包括&#xff1a; r&#xff1a;只读模式&#xff08;默认&#xff09;。w&#xff1a;写入模式…

用Python构建动态折线图:实时展示爬取数据的指南

背景/引言 随着大数据和人工智能的不断发展&#xff0c;实时数据分析变得越来越关键&#xff0c;尤其是在金融市场中。股市数据的实时可视化可以帮助投资者快速做出决策&#xff0c;避免错失良机。Python 凭借其强大的数据处理能力和丰富的可视化库&#xff0c;成为分析和展示…

你不是算法工程师,就可以不了解AI大模型技术吗?

身处人工智能的大浪潮之中&#xff0c;除了算法工程师&#xff0c;其他的角色也都应当对人工智能大模型技术有一定的了解。所以&#xff0c;笔者将针对“什么是人工智能&#xff1f;”“非技术人员对于人工智能大模型的理解存在哪些门槛&#xff1f;”等问题与大家分享自己的见…

项目集成工作流,走审批流程,activiti,springboot,集成工作流,业务审批,驳回,会签,流程设计

前言 activiti工作流引擎项目&#xff0c;企业erp、oa、hr、crm等企事业办公系统轻松落地&#xff0c;一套完整并且实际运用在多套项目中的案例&#xff0c;满足日常业务流程审批需求。 项目源码配套文档获取&#xff1a;本文末个人名片直接获取。 一、项目形式 springboot…

健康补充维生素

在快节奏的现代生活中&#xff0c;健康养生已成为我们不可忽视的重要议题。而提及养生&#xff0c;维生素这一关键词往往跃然纸上&#xff0c;它们作为人体不可或缺的微量营养素&#xff0c;对维持生命活动、促进健康起着至关重要的作用。今天&#xff0c;就让我们深入探讨如何…

中小型医院网站:Spring Boot框架详解

5 系统实现 5.1 用户功能模块的实现 用户进入本系统可查看系统信息&#xff0c;包括首页、门诊信息、药库信息以及系统公告信息等&#xff0c;系统前台主界面展示如图5-1所示。 图5-1系统前台主界面图 5.1.1用户登录界面 用户要想实现预约挂号功能&#xff0c;必须登录系统&a…

修改Linux的IP地址

方法一&#xff08;特点&#xff1a;命令执行后&#xff0c;IP立即修改&#xff0c;但重启后会恢复原来的IP地址&#xff09; 1.含义&#xff1a; inet ip地址 netmask 子网掩码 broadcast 广播地址 inet 192.168.44.129 netmask 255.255.255.0 broadcast 192.168.1.255 …

仅涨粉1.3万、清空橱窗,贾跃亭直播带货这么快就哑火了?

还记得上周&#xff0c;贾跃亭声势浩大的做了个重大决定&#xff0c;也就是几个月前说的要个人IP商业化这盘菜端到了直播带货行业。‍‍ 当时&#xff0c;说他口气大&#xff0c;那真是一点也不小&#xff0c;比如要给中美人民、中美零售业、中美产品、中美品牌&#xff0c;搭一…

LeNet-5(论文复现)

LeNet-5&#xff08;论文复现&#xff09; 本文所涉及所有资源均在传知代码平台可获取 文章目录 LeNet-5&#xff08;论文复现&#xff09;概述LeNet-5网络架构介绍训练过程测试过程使用方式说明 概述 LeNet是最早的卷积神经网络之一。1998年&#xff0c;Yann LeCun第一次将LeN…

站在用户视角审视:以太彩光与PON之争

作者:科技作家-郑凯 园区,是企业数字化转型的“中心战场”。 云计算、大数据、人工智能等数智化技术在园区里“战火交织”;高清视频、协同办公,智慧安防等大量创新应用产生的海量数据在园区内“纵横驰骋”;加上大量的IOT和智能化设备涌入“战场”,让园区网络面对着难以抵御的…

基于YOLOv9的空中飞鸟识别检测系统(附项目源码和数据集下载)

项目完整源码与模型 YOLOv9实现源码&#xff1a;项目完整源码及教程-点我下载YOLOv5实现源码&#xff1a;项目完整源码及教程-点我下载YOLOv7实现源码&#xff1a;项目完整源码及教程-点我下载YOLOv8实现源码&#xff1a;项目完整源码及教程-点我下载数据集&#xff1a;空中飞…

等保测评的技术要求与管理要求详解

等保测评&#xff0c;即网络安全等级保护测评&#xff0c;是根据《中华人民共和国网络安全法》、《信息安全技术网络安全等级保护基本要求》等相关法规和标准&#xff0c;对信息系统的安全性进行评估的过程。等保测评分为技术要求和管理要求两大方面&#xff0c;旨在确保信息系…

外包干了5天,技术明显退步

我是一名本科生&#xff0c;自2019年起&#xff0c;我便在南京某软件公司担任功能测试的工作。这份工作虽然稳定&#xff0c;但日复一日的重复性工作让我逐渐陷入了舒适区&#xff0c;失去了前进的动力。两年的时光匆匆流逝&#xff0c;我却在原地踏步&#xff0c;技术没有丝毫…

PicoQuant GmbH公司Dr. Christian Oelsner到访东隆科技

昨日&#xff0c;德国PicoQuant公司的光谱和显微应用和市场专家Dr.Christian Oelsner莅临武汉东隆科技有限公司。会议上Dr. Christian Oelsner就荧光寿命光谱和显微技术的最新研究和应用进行了深入的交流与探讨。此次访问不仅加强了两家公司在高科技领域的合作关系&#xff0c;…

成都爱尔李晓峰主任讲解“寒”已至,眼需“养”

温度逐渐走低&#xff0c;寒冷空气的到来带走夏季闷热潮湿&#xff0c;也带走了空气中的水分&#xff0c;环境变得干燥&#xff0c;眼睛水分蒸发加快&#xff0c;十分容易造成眼部不适&#xff0c;干眼患者尤其需要注意&#xff01; 有干眼问题的患者&#xff0c;在这样的天气下…

案例实践 | 以长安链为坚实底层,江海链助力南通民政打造慈善应用标杆

案例名称-江海链 ■ 实施单位 中国移动通信集团江苏有限公司南通分公司、中国移动通信集团江苏有限公司 ■ 业主单位 江苏省南通市民政局 ■ 上线时间 2023年12月 ■ 用户群体 南通市民政局、南通慈善总会等慈善组织及全市民众 ■ 用户规模 全市近30家慈善组织&#…

【网络安全】漏洞案例:提升 Self-XSS 危害

未经许可,不得转载。 文章目录 Self-XSS-1Self-XSS-2Self-XSS-1 目标应用程序为某在线商店,在其注册页面的First Name字段中注入XSS Payload: 注册成功,但当我尝试登录我的帐户时,我得到了403 Forbidden,即无法登录我的帐户。 我很好奇为什么我无法登录我的帐户,所以我…

【unity框架开发起步】一些框架开发思维和工具类封装

文章目录 前言一、Editor操作二、快捷导出unity包三、快捷打开存储目录四、封装transform操作1、localPosition赋值简化2、封装修改transform.localPosition X Y Z3、封装transform.localPosition XY、XZ 和YZ4、Transform 重置 五、封装概率函数六、方法过时七、partial 关键字…

STM32_实验2_printf函数重定向输出

掌握串口通信&#xff0c;并将 printf 函数重定向到串口输出。 USART1 global interrupt 的使能与不使能对系统的影响主要体现在如何处理串口通信事件上&#xff0c;如数据接收和发送的方式。这些不同的配置会直接影响系统的效率、响应时间以及资源的使用。 配置printf函数使用…

递归查找子物体+生命周期函数

递归查找子物体 相关代码&#xff1a; Transform FindChild(string childName, Transform parent){if (childName parent.name) {return parent;}if (parent.childCount < 1){return null;}Transform obj null;for(int i 0; i < parent.childCount; i){Transform t …