STM32之LWIP网络通讯设计-下(十五)

STM32F407 系列文章 - ETH-LWIP(十五)


目录

前言

一、软件设计

二、CubeMX实现

1.配置前准备

2.CubeMX配置

1.ETH模块配置

2.时钟模块配置

3.中断模块配置

4.RCC及SYS配置

5.LWIP模块配置

3.生成代码

1.main文件

2.用户层源文件

3.用户层头文件

4.效果演示

三、移植实现

总结


前言

一般对于许多嵌入式系统或单片机,在其资源受限的环境下,要想实现网络通讯,并保证资源的高效利用和稳定的网络通信,我们一般采用一种轻量级的网络协议lwIP。TI公司的STM32芯片一般都会自带一路以太网口,用于网络通讯,但因其内存资源受限,所以都用采用一种小型化、轻量级的lwIP网络协议,只需十几KB的RAM和大约40K的ROM即可运行,既可以在无操作系统环境下工作,也可以与各种操作系统配合使用,使其成为资源受限的嵌入式系统的理想选择。一般市场上所卖的板子都带这一功能的,需准备STM32F407开发板一块和网线一根。


一、软件设计

前面上一篇博文STM32之LWIP网络通讯设计-上(十四)-CSDN博客讲述了对STM32实现LWIP网络通讯的前提性要求介绍,包含使用到的网络协议、MAC内核、PHY驱动芯片、通讯连接示意图、以及硬件电路原理设计图,为网络通讯软件开发提供了设计指导。今天将完成STM32网络通讯的软件设计,实现该软件有有两种方式,第一种方法是通过可视化工具STM32CubeMX完成对lwIP通讯的配置,一键化生成工程代码;第二种方法是先下载lwIP,然后完成lwIP移植到STM32工程项目中,在完成其通讯配置。这里会给出这两种实现方法,但博主更推荐第一种方法,简便快捷。

二、CubeMX实现

‌STM32CubeMX是一个图形化配置工具,主要用于配置STM32微控制器和微处理器。它通过直观的图形用户界面,帮助用户选择STM32 MCU型号、配置引脚、设置系统时钟和外设参数,并生成相应的初始化C代码。这里对CubeMX可视化工具不做详细的介绍,改天会专门写篇文章介绍它。

1.配置前准备

在进行可视化工具CubeMX设置前,需要先了解处理器网络通讯的电路图实现,主要是了解到处理器使用到的IO引脚以及网络驱动芯片,根据上一篇文章介绍网络通讯设计-上(十四)(有提供stm32f407手册数据和YT8512C驱动芯片数据),线连接到处理器的IO引脚PC4、PC5、PG13、PG14、PG11、PC1、PA2、PA7、PD3,如下图所示。

2.CubeMX配置

打开CubeMX工具,如下所示。

1.ETH模块配置

上图,在右边Pinout View上面,根据电气原理图设置stm32相关IO引脚,如上图所示,设置完相关引脚后在上图左边ETH项,打开可以看到配置的相关引脚PC4、PC5、PG13、PG14、PG11、PC1、PA2、PA7、PD3如下所示。

在上图上,我们选择网络模式为RMII,根据YT8512C驱动芯片数据手册,MAC地址、PHY地址、PHY状态寄存器地址、PHY速度、PHY双工状态、以及PHY相关参数设置如下。

并使能Eth中断配置,如下。

根据电气设计图,设置处理器IO引脚PD3为ETH复位引脚,如下所示。

2.时钟模块配置

根据图纸参数,外部时钟为8MHz,处理器最高位168MHz,完成相关设置如下。

3.中断模块配置

4.RCC及SYS配置

5.LWIP模块配置

最后,别忘记使能LWIP模块,完成对其配置,打开第三方插件Middleware,勾选LWIP,里面可以看到LWIP使用的版本号,并完成LWIP的地址、掩码、TCP、BUF等参数的设置,如下所示。

提一句,CubeMX这里面用的LWIP跟外部官网上是一样,因为其开源免费的特性,CubeMX直接将其内嵌在软件里面使用。

在第三方插件Middleware界面上,还可以看到FATFS通用文件系统设置、以及freeRTOS线程设置等等,说明使用CubeMX可视化工具,很方便、功能也挺多。

3.生成代码

上面完成配置引脚、设置系统时钟、以及外设参数设置后,点击GENERATE CODE直接生成代码和工程文件,在保存的路径上面查看。

  

打开工程,查看代码如下。

1.main文件

这里将完成相关GPIO引脚、LWIP、定时器初始化配置,首先重置所有外围设备,初始化Flash接口和Systick,配置系统时钟,初始化所有已配置的外围设备;然后初始化用户UDP网络设置、和用户参数,并启动1毫秒定时器;最后进入主循环,处理网络上的数据,主要为MX_LWIP_Process()函数和UDP_Data_Process()函数完成,其中MX_LWIP_Process函数为CubeMX工具生成的函数,主要作用为处理网络上接收到的数据,传递给注册的接收回调函数UDP_Receive_Callback解析处理。代码示例如下。

/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "lwip.h"
#include "tim.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
#include "eth_user.h"
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* MCU Configuration--------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM6_Init();
  MX_LWIP_Init();
  MX_TIM7_Init();
	User_UDP_Init();
	HAL_TIM_Base_Start_IT(&htim6); // 启动1ms定时器
  /* Infinite loop */
  while (1)
  {
	  Delay_us(100);
	  MX_LWIP_Process();
	  UDP_Data_Process();
  }
}
/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 8;
  RCC_OscInitStruct.PLL.PLLN = 320;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
  HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_PLLCLK, RCC_MCODIV_4);
}

2.用户层源文件

这里将完成网络用户层设置,主要为网络IP初始化设置、以及网络收发数控制,代码示例如下。

#include "eth_user.h"
#include "stm32f4xx_hal.h"
#include "lwip.h"
#include "lwip/igmp.h"

#define UDP_PORT     8888 		  // 网络端口
static struct udp_pcb *upcb;    // UDP通讯的控制块对象
static DeviceStatus SendUDPSt;	// UDP通道的通讯状态
UDP_Message RecvUDPMes;
UDP_Message AckUDPMes;
UDP_Message SendUDPMes;
/******************************************************************************
 * 描述  : 初始化UDP所用的相关数据对象
 * 参数  : 无
 * 返回  : 无
******************************************************************************/
void UDP_Data_Init(void)
{
	memset(&RecvUDPMes, 0, sizeof(UDP_Message));
	memset(&AckUDPMes, 0, sizeof(UDP_Message));
	memset(&SendUDPMes, 0, sizeof(UDP_Message));
	memset(&SendUDPSt, 0, sizeof(DeviceStatus));
	SendUDPSt.ID = 0x10;
}
/******************************************************************************
 * 描述  : 接收回调函数编写
 * 参数  : upcb UDP协议控制块
 *         p 收到的数据包缓冲区
 *         addr 接收数据包的远程IP地址 
 *         port 接收数据包的远程端口
 * 返回  : 无
******************************************************************************/
static void UDP_Receive_Callback(void *arg, struct udp_pcb *upcb,
    struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
    uint8_t udprecvbuf[MAX_UDP_FRAME_LEN] = {0};
    struct pbuf *ptmp = p;
    uint32_t data_len = 0;
    
    if(upcb != NULL && p != NULL)
    {
			ip_addr_t remote_source_ip;
			IP4_ADDR(&remote_source_ip, 192,168,1,113);		
			if((port == UDP_PORT) && ((addr->addr) == remote_source_ip.addr)) //判断地址和端口从哪里来的
			{
				for (ptmp = p; ptmp != NULL; ptmp = ptmp->next)	 //遍历完整个buf链表
				{				
					//判断要拷贝到buf中的数据是否大于UDP_DEMO_RX_BUFSIZE的剩余空间,
					//如果大于的话就只拷贝剩余长度的数据,否则的话就拷贝所有的数据
					if (ptmp->len <= (MAX_UDP_FRAME_LEN - data_len)){					
						memcpy(udprecvbuf + data_len, ptmp->payload, ptmp->len);
						data_len += ptmp->len;
					}
					else{					
						memcpy(udprecvbuf + data_len, ptmp->payload, (MAX_UDP_FRAME_LEN - data_len)); //拷贝数据
						data_len = MAX_UDP_FRAME_LEN;
					}
					//超出UDP最大数据包,则跳出循环
					if (data_len > MAX_UDP_FRAME_LEN){				
						break; 
					}
				}

				// 初步处理
				memcpy(&AckUDPMes, udprecvbuf, data_len);
				SendUDPSt.LastRecvTime = g_1msTick;
				SendUDPSt.ComSt = COM_ST_PENDING;
				if(*((uint32_t*)udprecvbuf) != UDP_FRAME_HEAD) { // 同步头校验
					AckUDPMes.AnsResult = ACK_HEAD_ERROR;
				}
				else if(AckUDPMes.Len != (data_len-4)) { // 帧长度校验
					AckUDPMes.AnsResult = ACK_LEN_ERROR;
				}
				else if(RecvUDPMes.CRC16 != CRC16_Calculate((uint8_t*)&RecvUDPMes.SN, RecvUDPMes.Len-4)) { // CRC校验
					AckUDPMes.AnsResult = ACK_CRC_ERROR;
				}
				else 
					SendUDPSt.ComSt = COM_ST_INIT;
		  }
    }
    pbuf_free(p); // 释放内存
}

/******************************************************************************
 * 描述  : 创建udp客户端
 * 参数  : 无
 * 返回  : 无
******************************************************************************/
void User_UDP_Init(void)
{
	ip_addr_t local_ip;
	ip_addr_t remote_ip;
  err_t err;
	/* 初始化UDP数据消息 */
	UDP_Data_Init();
	// 组播配置
  //IP4_ADDR(&local_ip, 224,1,1,201);   //配置本地接收组播的地址
	//IP4_ADDR(&remote_ip, 224,1,1,113);
	//独播
	IP4_ADDR(&local_ip, 192,168,1,201);
	IP4_ADDR(&remote_ip, 192,168,1,113);
	upcb = udp_new();  // 创建udp控制块
	if (upcb!=NULL)
	{	
		upcb->local_port = UDP_PORT;           // 配置本地端口
		upcb->local_ip.addr = local_ip.addr;   // 配置本地地址
		upcb->remote_port = UDP_PORT;          // 绑定远程端口
		upcb->remote_ip.addr = remote_ip.addr; // 绑定远程地址
		
    //加入组播
		//err= igmp_joingroup(IP_ADDR_ANY,&local_ip); //只需要将接收地址放入igmp组,发送的不需要
		//连接远程服务器IP和端口
		err= udp_connect(upcb, &remote_ip, UDP_PORT);
		if(err == ERR_OK)
		{
			err = udp_bind(upcb,&local_ip,UDP_PORT); //只能收到绑定的本地接收ip地址和端口的数据
			//err = udp_bind(upcb,IP_ADDR_ANY,UDP_LOCAL_PORT); //可以收到固定端口上任意ip地址的数
			// 注册接收回调函数,只要接收到数据,这个回调函数会被lwip内核调用
			udp_recv(upcb, UDP_Receive_Callback, NULL);  // 函数初始化时注册-接收回调函数	
		}
		else
		{
			//离开组播地址
			//igmp_leavegroup(IP_ADDR_ANY,&local_ip);
			// 断开UDP连接
		  udp_remove(upcb);
		}
	}
}

/******************************************************************************
 * 描述  : 发送数据
 * 参数  : (in)pData 发送数据的指针
 * 返回  : 无
******************************************************************************/
int8_t UDP_Send_Data(struct udp_pcb *upcb, uint8_t *pData, uint16_t len)
{
  struct pbuf *p;
	int8_t ret = ERR_BUF;
    
	if (upcb != NULL && pData != NULL && len > 0)
	{
		/* 分配缓冲区空间 */
		p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_POOL);
		if(p != NULL)
		{
			/* 填充缓冲区数据 */
			ret = pbuf_take(p, pData, len);
			if(ret == ERR_OK)
			{
				/* 发送udp数据 */
				ret = udp_send(upcb, p);
			}			
			/* 释放缓冲区空间 */
			pbuf_free(p);
		}
	}
	return ret;
}

// 处理单项控制命令
CMD_Ack UDP_Single_Ctrl(UDP_Message msg)
{
	CMD_Ack ret = ACK_OK;
	
	if((uint8_t*)&msg != NULL)
	{
		switch(msg.Param2)
		{
			case 0:
			  break;
			case 1:
				break;
			case 2:
				break;
			default:
				break;
		}
	}
	else
	{
		ret = ACK_ERROR;
	}
	return ret;	
}

/******************************************************************************
 * 描述  : 将接收到的UDP数据包解析处理
 * 参数  : 无
 * 返回  : 无
******************************************************************************/
void UDP_Msgs_Process(void)
{
	if(SendUDPSt.ComSt == COM_ST_INIT) {
		AckUDPMes.AnsResult = UDP_Single_Ctrl(RecvUDPMes);
		SendUDPSt.ComSt = COM_ST_TRANS;
	}
}

/******************************************************************************
* 描述  : 向以太网上发送UDP数据包
 * 参数  : 无
 * 返回  : 无
******************************************************************************/
void UDP_Send_Msgs(void)
{
	static uint32_t lastime = 0;
	/* deal ack data */
	if(SendUDPSt.ComSt != COM_ST_STOP) {
		AckUDPMes.Head = UDP_FRAME_HEAD;
		AckUDPMes.Len = UDP_MSG_LEN;
		AckUDPMes.CRC16 = CRC16_Calculate((uint8_t*)&AckUDPMes.SN, AckUDPMes.Len-4);
		UDP_Send_Data(upcb, (uint8_t*)&AckUDPMes, UDP_FULL_FRAM_LEN(AckUDPMes.Len));
		SendUDPSt.ComSt = COM_ST_STOP;
		memset(&AckUDPMes, 0, sizeof(UDP_Message));
	}
	/*d eal heartbeat data */
	if(g_1msTick - lastime > 1000) {
		lastime = g_1msTick;
		SendUDPMes.Head = UDP_FRAME_HEAD;
		SendUDPMes.Len = UDP_MSG_LEN + 2;
		SendUDPMes.SN += 1;
		SendUDPMes.Param1 = SUB_CMD_KEEPALIVE;
		SendUDPMes.Param2 = RUN_IDLE;
		SendUDPMes.AnsResult = ACK_PENDING;
		SendUDPMes.Data[0] = 0xff;
		SendUDPMes.Data[1] = 0xff;
		SendUDPMes.CRC16 = CRC16_Calculate((uint8_t*)&SendUDPMes.SN, SendUDPMes.Len-4);
		UDP_Send_Data(upcb, (uint8_t*)&SendUDPMes, UDP_FULL_FRAM_LEN(SendUDPMes.Len));
	}
}

/******************************************************************************
 * 描述  : UDP处理流程(处理现有消息,调度发送队列)
 * 参数  : 无
 * 返回  : 无
******************************************************************************/
void UDP_Data_Process(void)
{
	UDP_Msgs_Process();
	UDP_Send_Msgs();
}

3.用户层头文件

#ifndef __UDP_USER_H
#define __UDP_USER_H

#ifdef __cplusplus
 extern "C" {
#endif

#include "stdint.h"
#include "string.h"
#include "crc.h"
#include "udp.h"
#include "timer.h"
#define MAX_UDP_BUF_SIZE 1000
typedef struct 
{
	uint32_t Head;      // 同步头
	uint16_t Len;       // 整帧长度(UDP_MSG_LEN+Data长度)
	uint16_t CRC16;     // CRC校验结果(UDP_MSG_LEN-4+Data长度的数据)
	uint16_t SN;        // 帧序号
	uint32_t Param1;    // 参数1
	uint32_t Param2;    // 参数2
	uint32_t AnsResult; // 应答结果
  uint8_t Data[MAX_UDP_BUF_SIZE]; // 数据内容(不能超过1024字节)
}__attribute__((packed)) UDP_Message;

#define UDP_FRAME_HEAD 0xC3A53C5A				// 帧头
#define UDP_MSG_LEN 22				          // 帧头长度
#define UDP_FULL_FRAM_LEN(msgLen) ((msgLen)+4)	// 帧头+整帧长度
#define MAX_UDP_FRAME_LEN (4+UDP_MSG_LEN+MAX_UDP_BUF_SIZE)//sizeof(UDP_Message) UDP最大帧长度

typedef enum
{
	ACK_PENDING = 0,        // 等待反馈
	ACK_OK = 0x10,          // 命令执行结束,且结果正常
	ACK_ERROR = 0x11,       // 命令执行过程发生异常
	ACK_WARNING = 0x12,     // 命令执行结束,但结果异常
	ACK_HEAD_ERROR = 0x20,   // Head校验错误
	ACK_CRC_ERROR  = 0x21,   // CRC校验错误
	ACK_LEN_ERROR  = 0x22,   // 长度校验错误
	ACK_CMD_INVALID = 0x23,  // 命令参数错误,无法执行
	ACK_RUNNING = 0x22,     // 有其他命令正在执行
}CMD_Ack; // 回应类型

}Sub_CMD_Type;

typedef enum
{
	RUN_IDLE 		  = 0x01,			// 空闲
	RUN_TEST_CTRL	= 0x02,			// 测试
	RUN_BURN_IN	  = 0x03,		  // 老炼
	RUN_UPGRADE		= 0x04			// 在线升级
}RunningStType;   // 软件运行状态类型

typedef enum
{
	COM_ST_UNKNOWN 	= 0x00,		// 未知
	COM_ST_PENDING 	= 0x01,		// 等待回应
	COM_ST_INIT 	  = 0x02,		// 初步OK
	COM_ST_TRANS 	  = 0x03,		// 可传输
	COM_ST_STOP	 	  = 0x04		// 已停止
}DeviceComStatus; // 设备通讯状态标识

void User_UDP_Init(void);
int8_t UDP_Send_Data(struct udp_pcb *upcb, uint8_t *pData, uint16_t len);
void UDP_Msgs_Process(void);
void UDP_Send_Msgs(void);
void UDP_Data_Process(void);
#ifdef __cplusplus
}
#endif

#endif

4.效果演示

编写完用户代码后,进行编译运行,连接仿真器在线调式、或者直接烧写到板子中,打开网络监控助手,效果如下。

三、下载移植实现

这里将实现第二种方法,通过下载、移植lwIP到STM32工程项目中,实现网络通讯。

1.lwip下载

点击打开LWIP官网lwIP - 轻量级 TCP/IP 堆栈,显示界面如下所示。在上面既可以下载所需版本lwip,也可以获得相应技术支持。

1.下载方式一

在上面找到下载区,点击进入后,选择我们需要下载的版本,如下所示。这里为了与前面的CubeMX工具上lwip版本保持一致,也选择lwip 2.12版本。

点击上面的版本进行下载时,有时无法响应,这时我们可以采取git方式下,即方式二下载。

2.下载方式二

选择上面的git存储区,点击lwIP-轻量级 TCPIP堆栈(lwip.git - lwIP - 轻量级 TCPIP 堆栈),选择对应的版本进行下载。在该选项下面还有一个lwIP Contrib - 为轻量级 TCP/IP 堆栈提供的代码选项(contrib.git - lwIP Contrib - 用户例程代码)即提供的例程代码,这里我们不讲解官方Contrib的demo例程。

 也可以直接点击下载网址进行下载,提供对应版本下载地址如下:

https://git.savannah.nongnu.org/cgit/lwip.git/snapshot/lwip-STABLE-2_1_2_RELEASE.tar.gzicon-default.png?t=O83Ahttps://git.savannah.nongnu.org/cgit/lwip.git/snapshot/lwip-STABLE-2_1_2_RELEASE.tar.gz下载完后,可以看到如下大小的压缩包。

2.lwip文件介绍

对上面下载的压缩包进行解压,得到如下画面。

上图中的lwip2.1.2文件夹包含了许多文件和子文件夹,关于里面的文件我们不需要关心,主要是记录lwIP源码更新、开源软件license、描述lwIP的特点、介绍lwIP源码包的文件目录信息等等,无关紧要。另外还有三个文件夹doc、src、test,其中doc文件夹里面是关于LwIP的一些文档,可以看成是应用和移植LwIP的指南;test文件夹里面是测试LwIP内核性能的源码,将它们和LwIP源码加入到工程中一起编译,调用它们提供的函数,可以获得许多与LwIP内核性能有关的指标;src文件夹是lwIP源码包中最重要的,它是lwIP的内核文件,也是我们移植到工程中的重要文件。

打开src文件夹,如下所示,主要讲解下面5个文件夹。

api文件夹里面装的是NETCONN API和Socket API相关的源文件,只有在操作系统的环境中,才 能被编译。apps文件夹里面装的是应用程序的源文件,包括常见的应用程序,如httpd、mqtt、tftp、sntp、snmp等。core文件夹里面是LwIP的内核源文件,后续会详细讲解。include文件夹里面是LwIP所有模块对应的头文件。netif文件夹里面是与网卡移植有关的文件,这些文件为我们移植网卡提供了模板,我们可以直接 使用。

LwIP内核是由一系列模块组合而成的,这些模块包括:TCP/IP协议栈的各种协议、内存管理模 块、数据包管理模块、网卡管理模块、网卡接口模块、基础功能类模块、API模块。每个模块是由相关的几个源文件和头文件组成的,通过头文件对外声明一些函数、宏、数据类型,使得其它模块可以方便地调用此模块的功能。而构成每个模块的头文件都被组织在了include目录中,而源文件则根据类型被分散地组织在 api、apps、core、netif目录中。下面的子级文件在不作介绍,具体可以查看LwIP的官方说明文档。

3.lwip移植

对上


总结

下面提供的代码,基于STM32F407ZGT芯片编写,可直接在原子开发板上运行,也可运行在各工程项目上,但需要注意各接口以及相应的引脚应和原子开发板上保持一致。

相应的代码链接:单片机STM32F407-Case程序代码例程-CSDN文库

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Gateway 网关

1.Spring Cloud Gateway Spring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关&#xff0c;Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式&#xff0c;Spring Cloud Gateway作为Spring Cloud生态…

数据结构:栈(Stack)和队列(Queue)—面试题(二)

1. 用队列实现栈。 习题链接https://leetcode.cn/problems/implement-stack-using-queues/description/描述&#xff1a; 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&a…

在 .NET 9 中使用 Scalar 替代 Swagger

前言 在.NET 9发布以后ASP.NET Core官方团队发布公告已经将Swashbuckle.AspNetCore&#xff08;一个为ASP.NET Core API提供Swagger工具的项目&#xff09;从ASP.NET Core Web API模板中移除&#xff0c;这意味着以后我们创建Web API项目的时候不会再自动生成Swagger API文档了…

双模充电桩发展前景:解锁新能源汽车未来的金钥匙,市场潜力无限

随着全球能源转型的浪潮席卷而来&#xff0c;新能源汽车行业正以前所未有的速度蓬勃发展&#xff0c;而作为其坚实后盾的充电基础设施&#xff0c;特别是双模充电桩&#xff0c;正逐渐成为推动这一变革的关键力量。本文将从多维度深入剖析双模充电桩的市场现状、显著优势、驱动…

Notepad++上NppFTP插件的安装和使用教程

一、NppFTP插件下载 图示是已经安装好了插件。 在搜索框里面搜NppFTP&#xff0c;一般情况下&#xff0c;自带的下载地址容易下载失败。这里准备了一个下载连接&#xff1a;Release v0.29.10 ashkulz/NppFTP GitHub 这里我下载的是x86版本 下载好后在nodepad的插件里面选择打…

Mysql--运维篇--备份和恢复(逻辑备份,mysqldump,物理备份,热备份,温备份,冷备份,二进制文件备份和恢复等)

MySQL 提供了多种备份方式&#xff0c;每种方式适用于不同的场景和需求。根据备份的粒度、速度、恢复时间和对数据库的影响&#xff0c;可以选择合适的备份策略。主要备份方式有三大类&#xff1a;逻辑备份&#xff08;mysqldump&#xff09;&#xff0c;物理备份和二进制文件备…

在 Safari 浏览器中,快速将页面恢复到 100% 缩放(也就是默认尺寸)Command (⌘) + 0 (零)

在 Safari 浏览器中&#xff0c;没有一个专门的快捷键可以将页面恢复到默认的缩放比例。 但是&#xff0c;你可以使用以下两种方法快速将页面恢复到 100% 缩放&#xff08;也就是默认尺寸&#xff09;&#xff1a; 方法一&#xff1a;使用快捷键 (最常用) Command (⌘) 0 (零…

LLMs之RAG:《EdgeRAG: Online-Indexed RAG for Edge Devices》翻译与解读

LLMs之RAG&#xff1a;《EdgeRAG: Online-Indexed RAG for Edge Devices》翻译与解读 导读&#xff1a;这篇论文针对在资源受限的边缘设备上部署检索增强生成 (RAG) 系统的挑战&#xff0c;提出了一种名为 EdgeRAG 的高效方法。EdgeRAG 通过巧妙地结合预计算、在线生成和缓存策…

探索网络安全:浅析文件上传漏洞

前言 在数字化时代&#xff0c;网络安全已成为我们每个人都需要关注的重要议题。无论是个人隐私保护&#xff0c;还是企业数据安全&#xff0c;网络威胁无处不在。了解网络安全的基本知识和防护措施&#xff0c;对我们每个人来说都至关重要。 网络安全 网络安全并非只是对网…

Therabody 与Garmin联手,共同推进运动恢复与健康科技新突破

本次合作以数据整合、人工智能驱动的数字教练与科学研究为重点&#xff0c;旨在更好地了解科学恢复对运动表现的影响 &#xff08;2025年1月13日&#xff0c;中国上海&#xff09;全球健康领导者Therabody宣布与智能手表品牌Garmin佳明建立战略合作关系&#xff0c;共同致力于…

Cloudflare中转Gemini API,国内免费爽用,Gemini编程,音视频,多模态能力测试

视频版&#xff1a; Cloudflare中转顶级大模型API&#xff0c;国内免费爽用&#xff0c;Gemini编程&#xff0c;音视频&#xff0c;多模态能力测试 谷歌Gemini是所有一线顶级大模型当中唯一一个API可以免费白嫖的。本期视频&#xff0c;我们借助互联网大善人Cloudflare来中转一…

腾讯云AI代码助手编程挑战赛-算法小助手

作品简介 一个可以帮助学习计算机各种算法的AI小助手&#xff0c;提升工作效率。 技术架构 使用Html语言完成图形化页面的样式&#xff0c;使用JavaScript语言来操作对应的逻辑代码。 实现过程 1、创建一个界面 2、获取数据 3、添加按钮与功能 4、程序优化调试 开发环境…

网络安全实验之钓鱼网站的制作及技巧

在红队攻击中&#xff0c;除漏洞以外最简洁高效的攻击方式无疑是钓鱼攻击&#xff0c;通过不同的钓鱼方式可达到不同的攻击效果&#xff0c;本次我会分享最常见的钓鱼手段之一的网站钓鱼技术&#xff0c;同时对可实现的攻击操作进行演示。 更多网站钓鱼实验科普&#xff0c;可…

用户注册模块用户校验(头条项目-05)

1 用户注册后端逻辑 1.1 接收参数 username request.POST.get(username) password request.POST.get(password) phone request.POST.get(phone) 1.2 校验参数 前端校验过的后端也要校验&#xff0c;后端的校验和前端的校验是⼀致的 # 判断参数是否⻬全 # 判断⽤户名是否…

linux-28 文本管理(一)文本查看,cat,tac,more,less,head,tail

之前提到过linux的几个重要哲学思想&#xff0c;使用纯文本文件保存软件的配置信息是其中之一&#xff0c;所以大多数情况下&#xff0c;我们对整个系统的操作&#xff0c;都是通过编辑它的配置文件来完成&#xff0c;那也就意味着&#xff0c;处理文本文件是我们作为系统管理员…

JVM面试相关

JVM组成 什么是程序计数器 详细介绍Java堆 什么是虚拟机栈 能不能解释一下方法区&#xff1f; 直接内存相关 类加载器 什么是类加载器&#xff0c;类加载器有哪些 什么是双亲委派模型 类加载过程 垃圾回收 对象什么时候可以被垃圾回收器回收 JVM垃圾回收算法有那些 JVM的分代…

【Unity3D】利用IJob、Burst优化处理切割物体

参考文章&#xff1a; 【Unity】切割网格 【Unity3D】ECS入门学习&#xff08;一&#xff09;导入及基础学习_unity ecs教程-CSDN博客 【Unity3D】ECS入门学习&#xff08;十二&#xff09;IJob、IJobFor、IJobParallelFor_unity ijobparallelfor-CSDN博客 工程资源地址&…

Armv8/Armv9架构从入门到精通-介绍

CSDN学院课程连接&#xff1a;https://edu.csdn.net/course/detail/39573 1 讲师介绍 拥有 12 年手机安全、汽车安全、芯片安全开发经验&#xff0c;擅长 Trustzone/TEE/ 安全的设计与开发&#xff0c;对 ARM 架构的安全领域有着深入的研究和丰富的实践经验&#xff0c;能够…

Cesium小知识:pointPrimitive collection 详解

Cesium.PointPrimitiveCollection 是 Cesium 中用于高效管理和渲染大量点(points)的一个类。它允许你创建和管理大量的 PointPrimitive 实例,这些实例可以用来表示地理空间中的点数据,如传感器位置、车辆位置、兴趣点等。与直接使用 Cesium.Entity 相比,PointPrimitiveCol…

Threejs实现 区块链网络效应

大家好&#xff01;我是 [数擎 AI]&#xff0c;一位热爱探索新技术的前端开发者&#xff0c;在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情&#xff0c;欢迎关注我的文章&#xff0c;我们一起成长、进步&#xff01; 开发领域&#xff1a;前端开发 | A…