目录

一.目标

二.实现(基于上章节的socket封装下)

三.注意事项

四.Flash的封装

一.目标

1.把STM32F407当作webserver,PC端可以使用浏览器访问它(这里要注意的是:开发板要上传html给浏览器,而不是在PC端自己创建html文件)

2.点击网页上显示的按键,可以控制STM32上的LED

3.同时也可以自己配置一些信息,比如ip,port,led引脚,状态,闪烁频率等等,将收到的这些信息存入Flash最后一个分区

二.实现(基于上章节的socket封装下)

1.Web服务器任务创建流程:

硬件初始化:首先初始化ESP8266模块的AT指令接口(通常使用UART2),确保模块处于正常工作状态

Wi-Fi连接:通过AT指令集,使ESP8266连接到指定的Wi-Fi热点(AP模式或STA模式),确保网络链路就绪

Socket创建:调用socket()系统调用,创建一个TCP套接字(SOCK_STREAM类型)

地址绑定:配置并绑定服务器地址结构体(struct sockaddr_in),将本地IP地址和端口号(如8080)与套接字关联

监听设置:调用listen()函数,使服务器套接字进入被动监听状态,并设置最大等待连接数(backlog)

连接接受:进入主循环,通过accept()函数阻塞等待客户端的连接请求。当有客户端(如网页浏览器)请求连接时,accept()返回一个新的套接字描述符,用于后续与该客户端的数据通信

void webserver_test_task(void *parameter)
{
	PAT_Device ptDev = get_esp8266_device();
	
	int iSocketServer;
	struct sockaddr_in tSocketServerAddr;
	int iSocketClient;
	struct sockaddr_in tSocketClientAddr;

	uint32_t iAddrLen;
	char ip[20];
	char ucRecvBuf[64];

	// 先假定使用UART2连接ESP8266
	at_init("uart2"); 
	
	while(1)
	{
		if(0 == at_connect_ap(WIFI_NAME, WIFI_PASSWD))
		{
			break;
		}
		vTaskDelay(1000);
	}

	//分配socket
	iSocketServer = (int)socket(AF_INET, SOCK_STREAM, 0);
	
	//绑定ip和port
	tSocketServerAddr.sin_family = AF_INET;
	tSocketServerAddr.sin_port = htons(SERVER_PORT);
	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;  //这里ip地址复制了INADDR_ANY
	memset(tSocketServerAddr.sin_zero, 0, 8);
	bind(iSocketServer, (const struct sockaddr*)&tSocketServerAddr, sizeof(struct sockaddr));
	
	//开始监听
	listen(iSocketServer, BACKLOG);
  	
	while(1)
	{
		iAddrLen = sizeof(tSocketClientAddr);
		iSocketClient = (int)accept(iSocketServer, (struct sockaddr*)&tSocketClientAddr, (socklen_t *)&iAddrLen);
		vTaskDelay(1000);
......
......
......

2.然后就是不同的地方,也是最重要的地方。Web服务器任务的核心处理逻辑。
Web服务器任务的核心在于HTTP请求的解析与响应,这是整个应用层功能的关键所在。具体处理流程如下:

·客户端连接建立:用户在浏览器中输入ESP8266的IP地址和端口号(如 192.168.1.100:8080)

浏览器发起TCP连接请求,服务器通过accept()接收连接,获得用于通信的socket描述符

·HTTP请求解析:服务器通过recv()接收来自socket的数据根据请求内容进行路由分发:

页面请求:若接收到以 "GET / HTTP/1.1" 开头的请求,表示浏览器请求根页面。服务器通过ESP8266将预置的HTML5网页代码发送给浏览器,在客户端渲染出控制界面。

指令请求:若接收到以 "GET /led?s=" 开头的请求,表示浏览器发出了LED控制指令。服务器解析URL参数中的状态值(s=1 表示开灯,s=0 表示关灯),并调用GPIO驱动控制对应的LED引脚。

·HTTP响应回复,关键原则:每次接收到GET请求后,必须向浏览器回复一个HTTP响应(状态码200 OK或相应的错误码),否则浏览器会因超时而显示连接错误。

	while(1)
	{
		iAddrLen = sizeof(tSocketClientAddr);
		iSocketClient = (int)accept(iSocketServer, (struct sockaddr*)&tSocketClientAddr, (socklen_t *)&iAddrLen);
		vTaskDelay(1000);
		
		if(-1 != iSocketClient)
		{	
			inet_ntop(AF_INET, &tSocketClientAddr.sin_addr, ip, 20);
			sprintf(ucRecvBuf, "Get connect from clientsocket:%d, user_data:%d, port:%d", iSocketClient, ptDev->sockets[iSocketClient].user_data ,ntohs(tSocketClientAddr.sin_port));
			printf("%s\r\n", ucRecvBuf);
 
			if(pdPASS != xTaskCreate(client_task, "client_task", AT_CLIENT_TASK_STACK, (void *)iSocketClient, osPriorityNormal, NULL))
				printf("socket:%d create task fail!\r\n", iSocketClient);
		}
	}
}

int GetLinkIDForSocket(int socket);

void client_task(void *parameter)
{
	int socket = (int)parameter;
	printf("The Current socket:%d", socket);
		
	int iRecvLen;
	char ucRecvBuf[500];					
	
	int ret;
	int total_len = 0;
	int remain_len = 0;
	int send_len = 0;
	
	while(1)
	{                                                                            
		iRecvLen = recv(socket, ucRecvBuf, sizeof(ucRecvBuf), 0);										/* 接收 HTTP 请求 */
		printf("socket %d recv data, link id = %d\n", socket, GetLinkIDForSocket(socket));
		if(iRecvLen > 0)
		{
			if(strstr((char *)ucRecvBuf, "GET / HTTP/1.1"))
			{			
				char http_head[256];																										//先发http响应
				int header_len = sprintf(http_head,
																"HTTP/1.1 200 OK\r\n"
																"Content-Type: text/html\r\n"
																"Content-Length: %d\r\n"
																"Connection: keep-alive\r\n"
																"Access-Control-Allow-Origin: *\r\n"
																"\r\n",
																index_html_len); 																//这里的长度就是页面的长度,不算'\0'
				int len = sendto(socket, http_head, header_len, 0, NULL, NULL);	
				printf("socket %d sendto %d html head, %d byte html, link id = %d\n", header_len, index_html_len, socket, GetLinkIDForSocket(socket));

																																								//再发送html代码
				while(total_len < index_html_len)  																			//发送总长度<web页面长度,就循环发送
				{
					remain_len = index_html_len - total_len;   														//剩余长度
					send_len = (remain_len > HTML_SEND_LEN) ? HTML_SEND_LEN : remain_len; //剩余长度大于每次发送的限定长度,下次发送限定长度;剩余长度小于每次发送的限定长度,下次发送剩余长度
					ret = sendto(socket, index_html+total_len, send_len, 0, NULL, NULL);  //每次发送个数都要偏移,+总发送长度 
					if(ret <= 0)
						break;																															//发送失败
					total_len = ret + total_len; 																					//发送成功加上ret发送的个数
				}	
			}
			else if(strstr((char *)ucRecvBuf, "GET /led?s="))
			{
				GPIO_Device *ptLED1 = GPIODeviceFind("LED1");														//处理网页的请求,有参数提取,然后控制led
		
				char *temp = strstr(ucRecvBuf, "s=");
				char *status = temp + 2;
				if(*status == '1')
				{
					ptLED1->Write(ptLED1, 0);
				}		
				else if(*status == '0')
				{
					ptLED1->Write(ptLED1, 1);
				}
				
				char response_buf[128];																									//回应
				int header_len = sprintf(response_buf,
																"HTTP/1.1 200 OK\r\n"
																"Content-Type: text/plain\r\n"
																"Content-Length: 2\r\n"
																"Connection: keep-alive\r\n"
																"Access-Control-Allow-Origin: *\r\n"
																"\r\n"
																"OK");
				sendto(socket, response_buf, header_len, 0, NULL, NULL);		
			}
		}
		else if(strstr(ucRecvBuf, "Connection: close"))	
		{
			printf("Client requests close\n");
			break;
		}
		else
		{
			break;
		}			
	}
		//退出任务,关闭socket,然后自杀
		closesocket(socket);
		vTaskDelete(NULL);
}

三.注意事项

问题描述:ESP8266服务器端数据接收时序问题。
在ESP8266 Web服务器实现中,存在一个关键的数据接收时序问题,具体描述如下,当网页浏览器连接到ESP8266服务器时,浏览器会立即发送一个HTTP GET请求(如 GET / HTTP/1.1)。然而,此时服务器端的任务处理流程尚未完成对新连接的识别与socket映射,导致该数据包丢失。

·浏览器发送的 GET / HTTP/1.1 请求数据,在socket映射完成前已经到达
·由于没有对应的socket接收缓存,该数据包未被正确存储
·后续 recv() 函数从socket读取时,无法获取到 "GET / HTTP/1.1" 开头的请求数据
·最终导致服务器无法识别页面请求,无法构造并返回HTML页面

解决方法:

1. 首先,我们定义了一个专用的数据结构(环形缓冲区),用于临时存放来自客户端的原始HTTP请求数据。该结构体同时保存了以下关键信息:

接收数据:完整的HTTP请求报文内容

硬件socket ID(hw_socket):ESP8266通过 AT+CIPSTATUS 命令获取的连接标识符

这样,即使 accept() 后续处理尚未完成,到达的数据也不会丢失,而是被安全地缓存起来等待处理。

2. 映射查找与数据转发
在 accept() 处理流程中,服务器通过 AT+CIPSTATUS 查询命令获取当前连接的硬件socket ID(hw_socket),然后执行以下操作:

查找匹配:在缓存结构体中查找与当前 hw_socket 相匹配的缓存条目

数据转发:一旦找到匹配项,将该缓存中的HTTP请求数据写入对应的消息队列(或直接写入socket的接收缓冲区)

3. 数据读取
完成数据转发后,后续的 recv() 函数调用便能够从socket中正确读取到完整的HTTP请求数据(包括 "GET / HTTP/1.1"),从而确保网页解析与响应功能的正常执行。

//解析网络数据包
static void esp8266_recv_packet(PAT_Device ptDev)
{
	Uart_Device *ptUARTDev = ptDev->ptUARTDev;
	uint8_t data;
	int hw_socket = 0;
	int len = 0;
	int state = 0; // 0:等待hw_socket, 1:等待len, 2:读取数据
	int received = 0;
	PAT_Socket ptSocket = NULL;
	int new_socket_buf;
	// +IPD,<link id>,<len>:<data>
	int time1 = HAL_GetTick();
	int time2;
	while(1)
	{
		//读取串口数据
		if(0 != ptUARTDev->Read(ptUARTDev, (uint8_t *)&data, 100))
		{
			time2 = HAL_GetTick();
			printf("recv packet timeout, use time %d ms, want ot get %d bytes, received %d\n", time2 - time1, len, received);
			return;
		}

		switch(state)
		{
			case 0: // 解析hw_socket
			{
				if(data == ',')
				{
					state = 1; // 切换到解析len状态, 根据hw_socket找到对应的PAT_Socket
					ptSocket = get_socket_for_hw_socket(hw_socket);
					if(!ptSocket)
					{
						//如果找不到对应的socket,但是数据已经发过来接收到"+IPD"了,就先创建一个socket_buf
						new_socket_buf = esp8266_socket_buf(0);
						// +IPD,<link id>,<len>:<data>
						ptDev->socket_bufs[new_socket_buf].user_data = hw_socket;
					}
					continue;
				}
				if(data >= '0' && data <= '9')
				{
					hw_socket = hw_socket * 10 + (data - '0');
				}
				break;
			}
			case 1: // 解析len
			{
				if(data == ':')
				{
					state = 2; // 切换到读取数据状态
					ptDev->socket_bufs[new_socket_buf].data_len = len;  //记录长度					
					continue;
				}
				if(data >= '0' && data <= '9')
				{
					len = len * 10 + (data - '0');
				}
				break;
			}
			case 2: // 读取数据
      {
				if(received < len)
				{
					if(ptSocket)
					{
						xQueueSend(ptSocket->recv_queue, &data, 0);		//存在socket,就往socket的队列写数据
					}
					else
					{
						ptDev->socket_bufs[new_socket_buf].data[received] = data;;	//不存在socket,就往buf里面写数据
					}
				}
				received++;
				if(received >= len)
				{
					time2 = HAL_GetTick();
					printf("get IPD packet for link_id %d, len = %d, use %d ms\n", hw_socket, len, time2 - time1);
					if(ptSocket)
					{
						// 释放信号量通知有新数据
						xSemaphoreGive(ptSocket->at_packet_sem);
						return;
					}
					else
					{
						return;
					}
				}
				break;
			}
			default:
			{
				break;
			}
    }
	}
}



/*-------------------------------------------------------------------------------------*/
int esp8266_accept(int socket, struct sockaddr *name, socklen_t *namelen)
{
	PAT_Device ptDev = get_esp8266_device();
	if(NULL == ptDev)
		return -1;
	if(socket < 0 || socket > AT_MAX_SOCKETS_NUM)
		return -1;
	if(NULL == name)
		return -1;
	
	PAT_Socket ptSocket = &ptDev->sockets[socket]; 
	//获得锁
	xSemaphoreTake(ptSocket->at_send_lock, portMAX_DELAY);
	
	//执行: AT+CIPSTATUS 查询网络连接信息
  char *cmd = "AT+CIPSTATUS\r\n";
	if(at_exec_cmd(ptDev, (int8_t *)cmd, NULL, 0, NULL, AT_TIMEOUT))
	{
		//释放锁
		xSemaphoreGive(ptSocket->at_send_lock);
		return -1;
	}
	
	struct sockaddr_in *ptAddr = (struct sockaddr_in *)&ptSocket->local;
	uint16_t server_port = ntohs(ptAddr->sin_port);
	
	//解析接收到的数据AT结构里的resp
	//STATUS:<stat>
	//+CIPSTATUS:<link ID>,<type>,<remote	IP>,<remote	port>,<local port>,<tetype>	
	if(ptDev->resp_line_counts >= 2) //跳过第1行
	{
		for(int i=1; i<ptDev->resp_line_counts && i <AT_RESP_MAX_LINE; i++)
		{
			int flag = 0;																//定义一个标记位
			uint8_t *line = ptDev->resp[i];							//指向当前行(每行以'\0'结尾)
			if(strstr((char *)line, "+CIPSTATUS:"))			//做对比
			{
				int hw_socket;
				uint16_t recv_socket=0;                				
				char type[10] = {0};
				uint8_t remote_ip[32] = {0};
				uint16_t  remote_ip_len=0;
				uint16_t remote_port;
				uint16_t local_port;
				int tetype;
				
				int parsed = sscanf((const char *)line, "+CIPSTATUS:%hu,%9[^,],\"%31[^,],%hu,%hu,%d",
													(uint16_t *)&recv_socket, type, remote_ip, &remote_port, &local_port, &tetype);
				if(parsed == 6)
				{
					hw_socket = recv_socket;
					if(get_socket_for_hw_socket(hw_socket) != NULL)			//找到一个used为1,但是uaser_data不为hw_socket的socket。
					{																										//没找到的话,创建一个新的硬件socket,并填充			
						continue;
					}
          if(local_port != server_port)
					{
						continue;
					} 
					
					//新分配设置socket结构体,来填充返回的hw_socket结构	
					int sw_socket = esp8266_socket(AF_INET, SOCK_STREAM, 0);
					if(sw_socket < 0 || sw_socket > AT_DEVICE_SOCKETS_NUM)
					{
						return -1;
					}
					PAT_Socket ptSocket2 = &ptDev->sockets[sw_socket];
					ptSocket2->user_data = hw_socket;          
					ptSocket2->at_socket_open_flag = 1;                    
													
					struct sockaddr_in *addr = (struct sockaddr_in *)name;
					addr->sin_family = AF_INET;																	//协议
					addr->sin_port = htons(remote_port);												//端口
					remote_ip_len = strlen((char *)remote_ip);
					if(remote_ip_len <=32)
          {
						remote_ip[remote_ip_len-1] =0;
						remote_ip[remote_ip_len] =0;
					}          
          inet_pton(AF_INET, (char *)remote_ip, &addr->sin_addr);			//ip
					if(namelen)
					{
						*namelen = sizeof(struct sockaddr_in);
					}
					
					ptSocket2->remote =(*((struct sockaddr*)addr));
					addr = (struct sockaddr_in *)&ptSocket2->local;						//指向local,修改server端port
					addr->sin_family = AF_INET;  
					addr->sin_port = htons(local_port);
			
					// 看看有没有提前收到数据	
					for(int x=0; x<AT_MAX_SOCKETS_NUM; x++)
					{
						if(ptDev->socket_bufs[x].used == 1 && ptSocket2->user_data == ptDev->socket_bufs[x].user_data)
						{
							for(int y=0; y<ptDev->socket_bufs[x].data_len; y++)
							{
								xQueueSend(ptSocket2->recv_queue, &ptDev->socket_bufs[x].data[y], 0);
							}
							// 释放信号量通知有新数据
							xSemaphoreGive(ptSocket2->at_packet_sem);
							ptDev->socket_bufs[x].used = 0;
							flag = 1;
							break;
						}
					}
					
					//释放锁
					xSemaphoreGive(ptSocket->at_send_lock);
					at_reset_resp(ptDev);
					printf("accept get connect: socket = %d, linkid = %d\n", sw_socket, hw_socket);
					return sw_socket;  // 返回链接ID作为socket描述符						
				}
			}
		}
	}
	//释放锁
	xSemaphoreGive(ptSocket->at_send_lock);
	return -1;  // 解析失败或无连接
}




还存在的一个问题是:

在ESP8266服务器开启多连接模式(AT+CIPMUX=1)的情况下,网页浏览器会通过多个不同的端口同时向服务器发起连接请求。因此,服务器端将通过accept()接收到多个不同的socket。

针对多连接场景,我们采用“一连接一任务”的设计架构:主循环中的accept()持续监听新连接

每接收到一个新socket,立即创建一个独立的客户端任务(client_task),每个任务负责处理对应socket上的数据收发。

由于accept()处于循环中,3个连接将分别创建3个独立的任务实例,针对多连接场景,我们采用“一连接一任务”的设计架构:

主循环中的accept()持续监听新连接,每接收到一个新socket,立即创建一个独立的客户端任务(client_task)每个任务负责处理对应socket上的数据收发由于accept()处于循环中,3个连接将分别创建3个独立的任务实例。

任务类型 数量 优先级 功能描述
客户端处理任务 3个(动态) 相同(osPriorityNormal 各自阻塞在recv(),等待对应socket的HTTP请求
Web服务器主任务 1个 普通(osPriorityNormal 仅负责创建和管理,完成后自杀释放资源
后台数据接收解析任务 1个 最高osPriorityNormal+1 处理ESP8266底层数据的接收与解析

四.Flash的封装

typedef struct
{
  __IO uint32_t ACR;
  __IO uint32_t KEYR;
  __IO uint32_t OPTKEYR;
  __IO uint32_t SR;
  __IO uint32_t CR;
  __IO uint32_t AR;
  __IO uint32_t RESERVED;
  __IO uint32_t OBR;
  __IO uint32_t WRPR;
} FLASH_TypeDef;

#define FLASH_TYPEPROGRAM_HALFWORD             0x01U  /*!<Program a half-word (16-bit) at a specified address.*/
#define FLASH_TYPEPROGRAM_WORD                 0x02U  /*!<Program a word (32-bit) at a specified address.*/
#define FLASH_TYPEPROGRAM_DOUBLEWORD           0x03U  /*!<Program a double word (64-bit) at a specified address*/

#define ROM_START              ((uint32_t)0x08000000)
#define ROM_SIZE               (512 * 1024)
#define ROM_END                ((uint32_t)(ROM_START + ROM_SIZE))
#define STM32_FLASH_END_ADDRESS        ROM_END
#define FLASH_PAGE_SIZE          0x800U

#define PERIPH_BASE           0x40000000UL /*!< Peripheral base address in the alias region */
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x00020000UL)
#define FLASH_R_BASE          (AHBPERIPH_BASE + 0x00002000UL) /*!< Flash registers base address */

#define FLASH_TIMEOUT_VALUE              50000U /* 50 s */

#define FLASH               ((FLASH_TypeDef *)FLASH_R_BASE)



static void delay_us(uint32_t udelay)
{
    __IO uint32_t Delay = udelay * 72 / 8;//(SystemCoreClock / 8U / 1000000U)
    //见stm32f1xx_hal_rcc.c -- static void RCC_Delay(uint32_t mdelay)
    do
    {
        asm("nop");
    } while (Delay --);
}


static void stm32_flash_unlock(void)
{
    if((FLASH->CR & FLASH_CR_LOCK) != 0)
    {
        /* Authorize the FLASH Registers access */
        FLASH->KEYR = FLASH_KEY1;
        FLASH->KEYR = FLASH_KEY2;
    }
}

/**
  * @brief  Locks the FLASH control register access
  * @retval HAL Status
  */
static void stm32_flash_lock(void)
{
  /* Set the LOCK Bit to lock the FLASH Registers access */
  FLASH->CR |= FLASH_CR_LOCK;  
}


static int FLASH_WaitForLastOperation(uint32_t Timeout)
{    
  uint32_t status;

    while ((FLASH->SR & FLASH_FLAG_BSY) && Timeout--)
    {
        delay_us(1);
    }

    if (0 == Timeout)
        return -1;  // timeout

    /* Check FLASH End of Operation flag  */
    if (FLASH->SR & FLASH_FLAG_EOP)
    {
        /* Clear FLASH End of Operation pending bit */
        FLASH->SR  = FLASH_FLAG_EOP;
    }
    

    status = FLASH->SR;
    if((status & FLASH_FLAG_WRPERR)  || 
         (status & FLASH_FLAG_OPTVERR) || 
         (status & FLASH_FLAG_PGERR))
    {
        return -1;
    }

    /* There is no error flag set */
    return 0;
}


static void FLASH_Program_HalfWord(uint32_t Address, uint16_t Data)
{
    /* Proceed to program the new data */
    FLASH->CR |= FLASH_CR_PG;

    /* Write data in the address */
    *(__IO uint16_t*)Address = Data;
}


static void FLASH_PageErase(uint32_t PageAddress)
{

    /* Proceed to erase the page */
    FLASH->CR |= FLASH_CR_PER;
    FLASH->AR = PageAddress;
    FLASH->CR |= FLASH_CR_STRT;
}


static int HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data)
{
    int status = -1;
    uint8_t index = 0;
    uint8_t nbiterations = 0;

    /* Process Locked */
    //__HAL_LOCK(&pFlash);


    /* Wait for last operation to be completed */
    status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);

    if(status == 0)
    {
        if(TypeProgram == FLASH_TYPEPROGRAM_HALFWORD)
        {
            /* Program halfword (16-bit) at a specified address. */
            nbiterations = 1U;
        }
        else if(TypeProgram == FLASH_TYPEPROGRAM_WORD)
        {
            /* Program word (32-bit = 2*16-bit) at a specified address. */
            nbiterations = 2U;
        }
        else
        {
            /* Program double word (64-bit = 4*16-bit) at a specified address. */
            nbiterations = 4U;
        }

        for (index = 0U; index < nbiterations; index++)
        {
            FLASH_Program_HalfWord((Address + (2U*index)), (uint16_t)(Data >> (16U*index)));

            /* Wait for last operation to be completed */
            status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);

            /* If the program operation is completed, disable the PG Bit */
            FLASH->CR &= ~FLASH_CR_PG;
            /* In case of error, stop programation procedure */
            if (status != 0)
            {
                break;
            }
        }
    }

    /* Process Unlocked */
    //__HAL_UNLOCK(&pFlash);

    return status;
}


static unsigned int stm32_flash_read(unsigned char *buf, unsigned int offset, unsigned int size)
{
    memcpy(buf, (const void *)offset, size);
    return size;
}

static unsigned int stm32_flash_write(unsigned char *buf, unsigned int offset, unsigned int size)
{
    unsigned int end_addr   = offset + size;
    unsigned int start_addr  = offset;

    if (offset % 4 != 0)
    {
        putstr("write addr must be 4-byte alignment\r\n");
        return 0;
    }

    if ((end_addr) > STM32_FLASH_END_ADDRESS)
    {
        putstr("write outrange flash size!\r\n");
        return 0;
    }

    stm32_flash_unlock();

    while (offset < end_addr)
    {
        if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, offset, *((rt_uint32_t *)buf)) == 0)
        {
            if (*(uint32_t *)offset != *(uint32_t *)buf)
            {
                break;
            }
            offset += 4;
            buf  += 4;
        }
        else
        {
            break;
        }
    }

    stm32_flash_lock();

    return offset - start_addr;
}


static unsigned int stm32_flash_erase(unsigned int offset, unsigned int size)
{
    unsigned int address = offset;

	stm32_flash_unlock();
	
    /* Wait for last operation to be completed */
    int status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
    
    /* Wait for last operation to be completed */
    if (status == 0)
    {

        /* Erase page by page to be done*/
        for(address = offset;
            address < STM32_FLASH_END_ADDRESS;
            address += FLASH_PAGE_SIZE)
        {   
            FLASH_PageErase(address);

            /* Wait for last operation to be completed */
            status = FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE);

            /* If the erase operation is completed, disable the PER Bit */
            FLASH->CR &= ~FLASH_CR_PER;

            if (status != 0)
            {
                /* In case of error, stop erase procedure and return the faulty address */
                break;
            }
        }
    }

	stm32_flash_lock();

      return address - offset;
}


static struct flash_ops stm32_flash = {
    "stm32_flash",
    stm32_flash_read,
    stm32_flash_write,
    stm32_flash_erase,
};

struct flash_ops *get_flash(void)
{
    return &stm32_flash;
}

地址根据这个来

五.总结


通过本次嵌入式Web服务器项目的开发实践,我对FreeRTOS实时操作系统的应用方法有了掌握。项目涉及的主要技术点如下:

一、任务管理的全面实践
项目围绕多连接HTTP服务器的需求,设计了Web服务器主任务、多个客户端处理任务、后台数据解析任务等多个功能模块。实现任务的动态创建与销毁,并在实践中深入理解了:

任务栈空间的合理配置:兼顾运行安全与内存效率

任务优先级的科学设定:根据实时性需求分配合适的优先级

任务生命周期的管理:从创建、运行到自我销毁的完整流程

二、任务调度机制的深入理解
通过配置不同优先级的任务,我深入体验了FreeRTOS的抢占式调度与时间片轮转机制。后台数据解析任务设置为最高优先级以保障底层数据的实时接收;多个客户端处理任务采用相同优先级,由调度器公平分配CPU时间。这种多优先级混合调度的设计让我对实时操作系统的调度策略有了直观而深刻的理解。

三、任务间通信与同步机制的熟练运用
项目广泛使用了FreeRTOS提供的多种任务间通信机制:

队列(Queue):任务之间的数据传递,实现数据的安全流转

信号量(Semaphore):用于任务间的同步与事件触发,实现精确的控制流程

互斥量(Mutex):用于保护共享资源(如全局缓存、硬件状态),防止竞态条件

四、阻塞机制与事件驱动设计的掌握
各客户端任务均采用阻塞式 recv() 等待网络数据,任务在无数据时进入阻塞状态释放CPU资源,有数据到达时由系统唤醒处理。这种事件驱动的设计模式提高了系统的整体效率,也让我深刻理解了任务阻塞与唤醒的底层机制。

同时整个架构的构思,以及各种函数和结构体的封装也让我学习到了很多。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐