在物联网与嵌入式开发中,ESP32 凭借强大的 WiFi 功能与丰富的外设支持,成为实现本地交互与远程控制的理想平台。通过搭建 HTTP 服务,可让 ESP32 具备接收网络数据、响应远程请求的能力。本文将围绕ESP32 HTTP 服务搭建、图片数据接收与实时显示展开实践,实现通过网页或客户端上传图片,由 ESP32 接收并驱动显示屏实时展示。该方案融合了网络通信、数据处理与外设驱动,适用于智能显示、远程监控、交互终端等典型 IoT 场景,兼具实用性与扩展性,可为嵌入式 Web 应用开发提供清晰的实现思路。
一、服务端配置
1 配置HTTP Server库
如下图所示,下载ESPAsyncWebSrv库:


2 配置HTTP Server代码
将以下代码复制到Arduino IDE中:
c
//
// A simple server implementation showing how to:
// * serve static messages
// * read GET and POST parameters
// * handle missing pages / 404s
//
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebSrv.h>
AsyncWebServer server(80);
const char* ssid = "wifi_name";
const char* password = "wifi_password";
const char* PARAM_MESSAGE = "message";
void notFound(AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found");
}
void setup()
{
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED)
{
Serial.printf("WiFi Failed!\n");
return;
}
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{
request->send(200, "text/plain", "Hello, world");
});
// Send a GET request to <IP>/get?message=<message>
server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request)
{
String message;
if (request->hasParam(PARAM_MESSAGE))
{
message = request->getParam(PARAM_MESSAGE)->value();
Serial.printf("Get Value From Get Request: %s\n",message.c_str());
}
else
{
message = "No message sent";
}
request->send(200, "text/plain", "Hello, GET: " + message);
});
// Send a POST request to <IP>/post with a form field message set to <message>
server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request)
{
String message;
if (request->hasParam(PARAM_MESSAGE, true))
{
message = request->getParam(PARAM_MESSAGE, true)->value();
Serial.printf("Get Value From Post Request: %s\n",message.c_str());
}
else
{
message = "No message sent";
}
request->send(200, "text/plain", "Hello, POST: " + message);
});
server.onNotFound(notFound);
server.begin();
}
void loop() {
}
注:这里的ssid和password记得要改成你的wifi名称和wifi密码
二、客户端配置
测试客户端时,注意要与esp32连接同一个wifi,保证两者互通。
1 python客户端测试
python
# esp_http_client.py
import requests
import time
# 请将这个IP地址替换为你的ESP设备实际IP地址
import requests
import time
# 请将这个IP地址替换为你的ESP设备实际IP地址
base_url = f"http://192.168.1.12"
def send_get_request(url, params=None):
"""发送GET请求并处理响应"""
try:
if params:
print(f"请求参数: {params}")
response = requests.get(url, params=params, timeout=5)
# 打印响应信息
print(f"响应状态码: {response.status_code}")
print(f"响应内容: {response.text}")
return response
except requests.exceptions.Timeout:
print("错误: 请求超时,请检查网络连接和ESP IP地址")
except requests.exceptions.ConnectionError:
print("错误: 无法连接到ESP设备,请检查IP地址和设备是否在线")
except Exception as e:
print(f"未知错误: {e}")
return None
def send_post_request(url, data=None):
"""发送POST请求并处理响应"""
try:
if data:
print(f"POST数据: {data}")
response = requests.post(url, data=data, timeout=5)
# 打印响应信息
print(f"响应状态码: {response.status_code}")
print(f"响应内容: {response.text}")
return response
except requests.exceptions.Timeout:
print("错误: 请求超时,请检查网络连接和ESP IP地址")
except requests.exceptions.ConnectionError:
print("错误: 无法连接到ESP设备,请检查IP地址和设备是否在线")
except Exception as e:
print(f"未知错误: {e}")
return None
def main():
# 1. 测试根路径 GET /
print("\n--- 测试1: GET / ---")
send_get_request(f"{base_url}/")
time.sleep(1)
# 2. 测试GET /get 不带参数
print("\n--- 测试2: GET /get (无参数) ---")
send_get_request(f"{base_url}/get")
time.sleep(1)
# 3. 测试GET /get 带参数
print("\n--- 测试3: GET /get (带参数) ---")
get_params = {"message": "Hello from Python GET!"}
send_get_request(f"{base_url}/get", params=get_params)
time.sleep(1)
# 4. 测试POST /post 不带参数
print("\n--- 测试4: POST /post (无参数) ---")
send_post_request(f"{base_url}/post")
time.sleep(1)
# 5. 测试POST /post 带参数
print("\n--- 测试5: POST /post (带参数) ---")
post_data = {"message": "Hello from Python POST!"}
send_post_request(f"{base_url}/post", data=post_data)
# 6. 测试404页面
print("\n--- 测试6: GET /nonexistent (404测试) ---")
send_get_request(f"{base_url}/nonexistent")
if __name__ == "__main__":
main()
可以看到python打印:

然后esp32那边也收到了消息:

2 curl测试
在使用libcurl之前别忘了安装curl库,使用以下命令:
c
# 安装libcurl开发库
sudo apt update
sudo apt install libcurl4-openssl-dev
以下是使用libcurl实现的http客户端:
c
//curl_test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <curl/curl.h>
// 定义ESP设备的基础URL,请替换为实际IP
#define BASE_URL "http://192.168.1.12"
// 用于存储curl响应的结构体
struct MemoryStruct {
char *memory;
size_t size;
};
/**
* 回调函数:将curl响应写入内存缓冲区
*/
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)userp;
char *ptr = realloc(mem->memory, mem->size + realsize + 1);
if (!ptr) {
printf("错误: 内存分配失败\n");
return 0;
}
mem->memory = ptr;
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
/**
* 发送GET请求(复刻Python的send_get_request函数)
* @param url: 请求URL
* @param params: GET参数(key=value格式,多个用&分隔,NULL表示无参数)
*/
CURLcode send_get_request(const char *url, const char *params) {
CURL *curl_handle;
CURLcode res;
struct MemoryStruct chunk;
char full_url[256] = {0};
// 初始化内存缓冲区
chunk.memory = malloc(1);
chunk.size = 0;
// 初始化curl
curl_global_init(CURL_GLOBAL_ALL);
curl_handle = curl_easy_init();
if (curl_handle) {
// 拼接完整URL(含参数)
strcpy(full_url, url);
if (params != NULL) {
printf("请求参数: %s\n", params);
strcat(full_url, "?");
strcat(full_url, params);
}
// 设置curl选项
curl_easy_setopt(curl_handle, CURLOPT_URL, full_url);
curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, 5L); // 总超时5秒
curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 5L); // 连接超时5秒
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1L); // 避免信号问题
// 执行请求
res = curl_easy_perform(curl_handle);
if (res == CURLE_OK) {
// 获取响应状态码
long response_code;
curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &response_code);
printf("响应状态码: %ld\n", response_code);
printf("响应内容: %s\n", chunk.memory ? chunk.memory : "");
} else {
// 错误处理
if (res == CURLE_OPERATION_TIMEDOUT) {
printf("错误: 请求超时,请检查网络连接和ESP IP地址\n");
} else if (res == CURLE_COULDNT_CONNECT || res == CURLE_COULDNT_RESOLVE_HOST) {
printf("错误: 无法连接到ESP设备,请检查IP地址和设备是否在线\n");
} else {
printf("未知错误: %s\n", curl_easy_strerror(res));
}
}
// 清理curl资源
curl_easy_cleanup(curl_handle);
} else {
printf("错误: 初始化curl失败\n");
res = CURLE_FAILED_INIT;
}
// 释放内存
free(chunk.memory);
curl_global_cleanup();
return res;
}
/**
* 发送POST请求(复刻Python的send_post_request函数)
* @param url: 请求URL
* @param data: POST数据(key=value格式,多个用&分隔,NULL表示无参数)
*/
CURLcode send_post_request(const char *url, const char *data) {
CURL *curl_handle;
CURLcode res;
struct MemoryStruct chunk;
// 初始化内存缓冲区
chunk.memory = malloc(1);
chunk.size = 0;
// 初始化curl
curl_global_init(CURL_GLOBAL_ALL);
curl_handle = curl_easy_init();
if (curl_handle) {
// 设置curl选项
curl_easy_setopt(curl_handle, CURLOPT_URL, url);
curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, 5L); // 总超时5秒
curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 5L); // 连接超时5秒
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1L); // 避免信号问题
// 设置POST请求
curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
if (data != NULL) {
printf("POST数据: %s\n", data);
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, data);
}
// 执行请求
res = curl_easy_perform(curl_handle);
if (res == CURLE_OK) {
// 获取响应状态码
long response_code;
curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &response_code);
printf("响应状态码: %ld\n", response_code);
printf("响应内容: %s\n", chunk.memory ? chunk.memory : "");
} else {
// 错误处理
if (res == CURLE_OPERATION_TIMEDOUT) {
printf("错误: 请求超时,请检查网络连接和ESP IP地址\n");
} else if (res == CURLE_COULDNT_CONNECT || res == CURLE_COULDNT_RESOLVE_HOST) {
printf("错误: 无法连接到ESP设备,请检查IP地址和设备是否在线\n");
} else {
printf("未知错误: %s\n", curl_easy_strerror(res));
}
}
// 清理curl资源
curl_easy_cleanup(curl_handle);
} else {
printf("错误: 初始化curl失败\n");
res = CURLE_FAILED_INIT;
}
// 释放内存
free(chunk.memory);
curl_global_cleanup();
return res;
}
/**
* 主函数:复刻原Python脚本的测试流程
*/
int main(void) {
char test_url[256] = {0};
// 1. 测试根路径 GET /
printf("\n--- 测试1: GET / ---\n");
snprintf(test_url, sizeof(test_url), "%s/", BASE_URL);
send_get_request(test_url, NULL);
sleep(1);
// 2. 测试GET /get 不带参数
printf("\n--- 测试2: GET /get (无参数) ---\n");
snprintf(test_url, sizeof(test_url), "%s/get", BASE_URL);
send_get_request(test_url, NULL);
sleep(1);
// 3. 测试GET /get 带参数
printf("\n--- 测试3: GET /get (带参数) ---\n");
snprintf(test_url, sizeof(test_url), "%s/get", BASE_URL);
send_get_request(test_url, "message=Hello from C GET!");
sleep(1);
// 4. 测试POST /post 不带参数
printf("\n--- 测试4: POST /post (无参数) ---\n");
snprintf(test_url, sizeof(test_url), "%s/post", BASE_URL);
send_post_request(test_url, NULL);
sleep(1);
// 5. 测试POST /post 带参数
printf("\n--- 测试5: POST /post (带参数) ---\n");
snprintf(test_url, sizeof(test_url), "%s/post", BASE_URL);
send_post_request(test_url, "message=Hello from C POST!");
sleep(1);
// 6. 测试404页面
printf("\n--- 测试6: GET /nonexistent (404测试) ---\n");
snprintf(test_url, sizeof(test_url), "%s/nonexistent", BASE_URL);
send_get_request(test_url, NULL);
return 0;
}
编译命令:
shell
gcc -o curl_test curl_test.c -lcurl
三、tft库配置及图片显示

1 转化图片数据
这里记得转化图片之前,先将图片resize到你需要的尺寸,之后再使用Img2Lcd去按照下面参数转化图片:

2 图片显示demo
使用以下代码去进行图片显示:
c
// ESP32-S3 TFT屏幕显示自定义图片数组
// 引脚定义:CS:14, DC/RS:47, RESET:21, SDI/MOSI:45, SCK:3, SDO/MISO:46, LED:0
#include <TFT_eSPI.h>
#include <SPI.h>
// 创建TFT对象
TFT_eSPI mylcd = TFT_eSPI();
// ====================== 自定义图片数据 ======================
#define IMAGE_WIDTH 240 // 图片宽度(根据你的图片实际尺寸调整)
#define IMAGE_HEIGHT 320 // 图片高度(根据你的图片实际尺寸调整)
const unsigned char gImage_aaa[153600] = { /* 0X10,0X10,0X00,0XF0,0X01,0X40,0X01,0X1B, */
0X5B,0X4E,0X5B,0X4E,0X5B,0X4E,0X5B,0X6E,0X63,0X6E,0X63,0X6E,0X63,0X8E,0X63,0X8E,
0X6B,0XAF,0X6B,0XCF,0X73,0XEF,0X73,0XEF,0X73,0XEF,0X73,0XEF,0X73,0XEF,0X73,0XEF,
0X73,0XCF,0X6B,0XAE,0X6B,0XAF,0X6B,0XCF,0X8C,0XF4,0XB6,0X19,0XCE,0XDB,0XE7,0X3D,
....};
// 显示图片函数
void displayCustomImage() {
// 设置屏幕旋转(0-3可选,根据你的屏幕和图片方向调整)
mylcd.setRotation(0);
// 清屏(黑色背景)
mylcd.fillScreen(TFT_BLACK);
// 核心:显示自定义图片数组
// 注意:需要将unsigned char数组转换为uint16_t指针(RGB565格式)
mylcd.pushImage(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, (const uint16_t*)gImage_aaa);
}
void setup() {
// 初始化串口(可选,用于调试)
Serial.begin(115200);
// 初始化TFT屏幕
mylcd.init();
// 显示自定义图片
displayCustomImage();
Serial.println("自定义图片显示完成!");
}
void loop() {
// 只显示一次图片,loop函数为空
delay(1000);
}
其中图片数组gImage_aaa为你转化出来的图片,可以看到初步可以使用esp32显示图片:

四、图片发送及服务端显示图片
1 复现图片转化过程
python
import cv2
import numpy as np
import sys
def image_to_c_array(image_path, output_path,target_size, byte_order="big"):
"""
:param image_path: 输入图片路径
:param output_path: 输出.c文件路径
:param byte_order: 字节序:"big"(大端,Big Endian)或 "little"(小端,Little Endian)
"""
try:
# 使用OpenCV读取图片(注意:OpenCV默认读取为BGR格式)
img = cv2.imread(image_path, cv2.IMREAD_COLOR)
if img is None:
raise ValueError(f"无法读取图片文件:{image_path}")
# target_size = (240, 320) # OpenCV resize的参数是 (宽, 高)
img = cv2.resize(img, target_size, interpolation=cv2.INTER_LINEAR)
# 转换为RGB格式 + 关键:将数组类型转为int32,避免移位溢出
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.int32)
height, width, _ = img_rgb.shape # OpenCV的shape是 (高, 宽, 通道数)
total_bytes = width * height * 2
# 提取数组名称(和原逻辑一致)
array_name = f"gImage_{image_path.split('.')[0].split('/')[-1]}"
# 生成C语言数组头(完全匹配软件格式)
c_code = f"const unsigned char {array_name}[{total_bytes}] = {{ \n"
# 遍历像素并转换为RGB565(拆分为单个字节)
count = 0 # 用于控制每行显示16个字节
for y in range(height):
for x in range(width):
# OpenCV按[y, x]访问像素(和PIL的[x, y]相反)
r, g, b = img_rgb[y, x]
# RGB565核心转换公式(修复类型溢出问题)
# R(5bit): (r & 0xF8) G(6bit): (g & 0xFC) B(5bit): (b & 0xF8)
# 关键:所有运算先转为int,避免uint8溢出
r_part = (int(r) & 0xF8) << 8
g_part = (int(g) & 0xFC) << 3
b_part = (int(b) & 0xF8) >> 3
rgb565 = r_part | g_part | b_part
# 拆分高低字节(关键逻辑和原代码一致)
if byte_order == "big":
# 大端模式:高字节在前,低字节在后(软件默认)
high_byte = (rgb565 >> 8) & 0xFF # 高8位
low_byte = rgb565 & 0xFF # 低8位
else:
# 小端模式:低字节在前,高字节在后
high_byte = rgb565 & 0xFF
low_byte = (rgb565 >> 8) & 0xFF
# 添加到数组(格式和软件完全一致)
c_code += f"0X{high_byte:02X},0X{low_byte:02X},"
count += 2
# 每行显示16个字节(8个像素),和软件格式对齐
if count % 16 == 0:
c_code = c_code.rstrip(",") # 移除最后一个逗号
c_code += ",\n" # 换行
# 收尾处理
c_code = c_code.rstrip(",\n") # 移除最后一行的逗号和换行
c_code += "\n};"
# 写入文件
with open(output_path, "w", encoding="utf-8") as f:
f.write(c_code)
print(f"✅ 转换完成!输出文件:{output_path}")
except Exception as e:
print(f"❌ 转换失败:{e}")
if __name__ == "__main__":
# 测试示例
input_img = "beauty.png"
output_c = "cv_data.h"
# 模拟图中配置:水平扫描、RGB565、大端字节序
image_to_c_array(input_img, output_c,(240, 320), byte_order="big")
2 服务端接收图片代码
c
// ESP32-S3 TFT屏幕显示HTTP接收的动态图片
// 引脚定义:CS:14, DC/RS:47, RESET:21, SDI/MOSI:45, SCK:3, SDO/MISO:46, LED:0
#include <TFT_eSPI.h>
#include <SPI.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebSrv.h>
#include <ArduinoJson.h> // 用于解析JSON数组(可选,根据Python发送方式)
// ====================== WiFi配置 ======================
const char* WIFI_SSID = "wifi_name";
const char* WIFI_PWD = "wifi_password";
// ====================== 屏幕配置 ======================
#define IMAGE_WIDTH 240 // 和Python端TARGET_SIZE一致
#define IMAGE_HEIGHT 320 // 和Python端TARGET_SIZE一致
#define IMAGE_BUF_SIZE (IMAGE_WIDTH * IMAGE_HEIGHT * 2) // RGB565每个像素2字节
// ====================== 全局变量 ======================
TFT_eSPI mylcd = TFT_eSPI();
AsyncWebServer server(80); // 创建HTTP服务器,端口80
uint8_t gDynamicImageBuf[IMAGE_BUF_SIZE] = {0}; // 动态接收图片的缓冲区(替换原静态数组)
bool isImageReceived = false; // 标记是否收到新图片
// ====================== WiFi连接函数 ======================
void connectWiFi() {
Serial.print("连接WiFi: ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PWD);
int retry = 0;
while (WiFi.status() != WL_CONNECTED && retry < 20) {
delay(500);
Serial.print(".");
retry++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi连接成功!");
Serial.print("ESP32 IP地址: ");
Serial.println(WiFi.localIP()); // 打印IP,供Python端使用
} else {
Serial.println("\nWiFi连接失败,请检查配置!");
while (1) delay(1000); // 连接失败则卡死
}
}
// ====================== 显示图片函数(兼容动态缓冲区) ======================
void displayCustomImage() {
mylcd.setRotation(0);
mylcd.fillScreen(TFT_BLACK); // 清屏
// 核心:显示动态接收的图片缓冲区(转换为uint16_t指针,RGB565格式)
mylcd.pushImage(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, (const uint16_t*)gDynamicImageBuf);
Serial.println("✅ 新图片已显示在屏幕上!");
}
// ====================== HTTP请求处理函数 ======================
// 处理Python发送的二进制流(推荐方式)
void handlePostBinaryImage(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
// 只接收和缓冲区大小匹配的数据
if (total != IMAGE_BUF_SIZE) {
if (index == 0) {
Serial.printf("❌ 数据长度不匹配!期望:%d,实际:%d\n", IMAGE_BUF_SIZE, total);
request->send(400, "text/plain", "Data size error!");
}
return;
}
// 分段接收数据并拷贝到缓冲区
static size_t receivedLen = 0;
if (index == 0) receivedLen = 0; // 新请求开始,重置接收长度
// 拷贝当前段数据到缓冲区
memcpy(&gDynamicImageBuf[receivedLen], data, len);
receivedLen += len;
// 数据接收完成
if (receivedLen == total) {
isImageReceived = true;
request->send(200, "text/plain", "Image received and displayed!");
Serial.printf("✅ 接收二进制图片数据完成,长度:%d\n", receivedLen);
displayCustomImage(); // 立即显示新图片
}
}
// 可选:处理Python发送的JSON数组(调试用)
void handlePostJsonImage(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (index != 0 || total != len) { // 只处理完整的JSON数据
return;
}
// 解析JSON
DynamicJsonDocument doc(IMAGE_BUF_SIZE * 3); // 足够的缓冲区
DeserializationError error = deserializeJson(doc, data, len);
if (error) {
Serial.printf("❌ JSON解析失败:%s\n", error.c_str());
request->send(400, "text/plain", "JSON parse error!");
return;
}
// 提取图片数组
JsonArray imgArray = doc["imagedata"];
if (imgArray.size() != IMAGE_BUF_SIZE) {
Serial.printf("❌ 数组长度不匹配!期望:%d,实际:%d\n", IMAGE_BUF_SIZE, imgArray.size());
request->send(400, "text/plain", "Array size error!");
return;
}
// 拷贝JSON数组到图片缓冲区
for (int i = 0; i < IMAGE_BUF_SIZE; i++) {
gDynamicImageBuf[i] = imgArray[i];
}
isImageReceived = true;
request->send(200, "text/plain", "JSON image received!");
Serial.println("✅ 接收JSON图片数据完成!");
displayCustomImage(); // 显示新图片
}
// ====================== 初始化函数 ======================
void setup() {
Serial.begin(115200);
delay(100);
// 1. 初始化屏幕
mylcd.init();
mylcd.fillScreen(TFT_BLACK); // 初始清屏
Serial.println("✅ TFT屏幕初始化完成");
// 2. 连接WiFi
connectWiFi();
// 3. 配置HTTP服务器路由
// 路由1:接收二进制流(对应Python方案1,推荐)
server.on("/post_image", HTTP_POST,
[](AsyncWebServerRequest *request) {}, // 无参数处理
NULL,
handlePostBinaryImage // 二进制数据处理函数
);
// 路由2:接收JSON数组(对应Python方案2,可选,调试用)
// server.on("/post_json_image", HTTP_POST, handlePostJsonImage);
// 启动服务器
server.begin();
Serial.println("✅ HTTP服务器已启动,等待接收图片...");
// 初始显示黑色背景(无图片时)
mylcd.fillScreen(TFT_BLACK);
}
// ====================== 主循环 ======================
void loop() {
// 无需循环操作,HTTP请求由异步服务器处理
delay(1000);
}
3 客户端转化代码并发送
python
import cv2
import numpy as np
import requests
ESP32_IP = "192.168.1.12"
BASE_URL = f"http://{ESP32_IP}/post_image"
TARGET_SIZE = (240, 320) # (宽度, 高度)
TXT_FILE_PATH = "rgb565_c_array.txt" # 输出C数组格式的txt文件
def image_to_rgb565_c_array(image_path, target_size, byte_order="big"):
"""
将图片转换为RGB565格式,并生成C语言数组格式的字符串(UTF-8可显示)
:return: (c_array_str, byte_data, width, height)
c_array_str: C数组格式的字符串
byte_data: 原始RGB565字节数据(用于发送)
"""
img = cv2.imread(image_path, cv2.IMREAD_COLOR)
if img is None:
raise ValueError(f"无法读取图片:{image_path}")
# 调整尺寸
img = cv2.resize(img, target_size, interpolation=cv2.INTER_LINEAR)
height, width, _ = img.shape
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.int32)
total_bytes = width * height * 2
# 提取数组名称(和参考代码逻辑一致)
array_name = f"gImage_{image_path.split('.')[0].split('/')[-1]}"
# 初始化C数组字符串和字节数据
c_code = f"const unsigned char {array_name}[{total_bytes}] = {{ \n"
byte_data = bytearray()
count = 0 # 控制每行16个字节
for y in range(height):
for x in range(width):
r, g, b = img_rgb[y, x]
# RGB565转换(和参考代码完全一致)
r_part = (int(r) & 0xF8) << 8
g_part = (int(g) & 0xFC) << 3
b_part = (int(b) & 0xF8) >> 3
rgb565 = r_part | g_part | b_part
# 拆分高低字节(大端模式)
if byte_order == "big":
high_byte = (rgb565 >> 8) & 0xFF
low_byte = rgb565 & 0xFF
else:
high_byte = rgb565 & 0xFF
low_byte = (rgb565 >> 8) & 0xFF
# 保存原始字节数据(用于后续发送)
byte_data.append(high_byte)
byte_data.append(low_byte)
# 按C数组格式拼接字符串(0Xxx,0Xxx,)
c_code += f"0X{high_byte:02X},0X{low_byte:02X},"
count += 2
# 每行显示16个字节(8个像素),换行(和参考代码对齐)
if count % 16 == 0:
c_code = c_code.rstrip(",") # 移除行尾多余逗号
c_code += ",\n" # 换行
# 收尾处理(移除最后一行的逗号和换行,补全数组结尾)
c_code = c_code.rstrip(",\n")
c_code += "\n};"
return c_code, bytes(byte_data), width, height
def save_c_array_to_utf8_txt(c_array_str, txt_path):
"""将C数组格式的字符串以UTF-8编码写入txt文件(无乱码)"""
try:
with open(txt_path, "w", encoding="utf-8") as f:
f.write(c_array_str)
print(f"✅ C数组格式已写入UTF-8 txt文件:{txt_path}")
except Exception as e:
raise ValueError(f"写入txt失败:{e}")
def parse_c_array_from_txt(txt_path):
"""从C数组格式的txt文件解析出原始RGB565字节数据(用于发送)"""
try:
with open(txt_path, "r", encoding="utf-8") as f:
content = f.read()
# 提取数组内容(去掉C语法部分,只保留0Xxx格式的数值)
# 步骤1:截取{}之间的内容
start_idx = content.find("{") + 1
end_idx = content.find("}")
array_content = content[start_idx:end_idx].strip()
# 步骤2:拆分所有0Xxx格式的数值
hex_values = [v.strip() for v in array_content.split(",") if v.strip()]
# 步骤3:转换为字节数据
byte_data = bytearray()
for hex_val in hex_values:
# 去掉0X/0x前缀,转十进制整数
val = int(hex_val.replace("0X", "").replace("0x", ""), 16)
byte_data.append(val)
print(f"✅ 从txt解析出字节数据,长度:{len(byte_data)}")
return bytes(byte_data)
except Exception as e:
raise ValueError(f"解析txt中的C数组失败:{e}")
def send_image_to_esp32(image_path):
try:
# 1. 转换图片为C数组字符串 + 原始字节数据
c_array_str, raw_byte_data, width, height = image_to_rgb565_c_array(
image_path, TARGET_SIZE, byte_order="big"
)
print(f"📌 图片转换完成,尺寸:{width}x{height},总字节数:{len(raw_byte_data)}")
# 2. 将C数组格式写入UTF-8 txt文件(无乱码)
save_c_array_to_utf8_txt(c_array_str, TXT_FILE_PATH)
# 3. 从txt文件解析回原始字节数据
send_data = parse_c_array_from_txt(TXT_FILE_PATH)
# 4. 校验数据一致性
if raw_byte_data == send_data:
print("✅ 数据读写一致性校验通过")
else:
print("❌ 数据读写一致性校验失败!")
return
# 5. 发送给ESP32
print(f"📤 发送解析后的字节流,长度:{len(send_data)}")
response = requests.post(
BASE_URL,
data=send_data,
headers={"Content-Type": "application/octet-stream"},
timeout=10
)
print(f"✅ 响应状态码: {response.status_code}")
print(f"✅ 响应内容: {response.text}")
except Exception as e:
print(f"❌ 发送失败: {e}")
if __name__ == "__main__":
input_image_path = "beauty1.jpg" # 替换为你的图片路径
send_image_to_esp32(input_image_path)
遇到的报错
-
assert failed: tcp_alloc /IDF/components/lwip/lwip/src/core/tcp.c:1851 (required to lock the TCPIP core functionality!)解决办法:重新安装esp32 sdk到3.0.7版本问题解决:
