要在 ESP32-C3 上开发一个类似无线 U 盘的功能,包括:
- ESP32-C3 启动 Wi-Fi 热点模式(AP Mode)。
- 通过手机连接到此热点。
- 手机打开浏览器访问 ESP32-C3 提供的网页。
- 在网页上上传手机中的文件到 ESP32-C3。
下面,我们将详细介绍如何实现这个功能。这个过程包含以下几个步骤:
配置 ESP32-C3 Wi-Fi 为热点模式
首先,我们需要将 ESP32-C3 设置为 Wi-Fi 热点模式(AP Mode),这样手机可以连接到 ESP32-C3。使用 ESP-IDF 提供的 esp_wifi
库,我们可以按照以下步骤配置热点模式。
代码示例:
#include <stdio.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#define WIFI_SSID "ESP32C3_AP"
#define WIFI_PASS "12345678" // 至少8个字符
static void wifi_init_softap(void) {
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
wifi_config_t wifi_config = {
.ap = {
.ssid = WIFI_SSID,
.ssid_len = strlen(WIFI_SSID),
.password = WIFI_PASS,
.max_connection = 4, // 热点可连接的设备数量
.authmode = WIFI_AUTH_WPA_WPA2_PSK,
},
};
if (strlen(WIFI_PASS) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN; // 开放网络,不需要密码
}
esp_wifi_set_mode(WIFI_MODE_AP);
esp_wifi_set_config(WIFI_IF_AP, &wifi_config);
esp_wifi_start();
printf("ESP32-C3 AP started. SSID:%s password:%s\n", WIFI_SSID, WIFI_PASS);
}
设置 HTTP 服务器以提供网页和处理文件上传
ESP32-C3 将启动一个 HTTP 服务器,提供网页给连接的客户端(如手机),并处理文件上传请求。
我们使用 ESP-IDF 中的 esp_http_server
库来实现 HTTP 服务器功能。这个库允许我们创建路由来处理不同的 HTTP 请求。
代码示例:
#include <esp_http_server.h>
// 创建 HTTP 服务器句柄
static httpd_handle_t start_webserver(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
if (httpd_start(&server, &config) == ESP_OK) {
// 注册静态网页的处理函数(GET 请求)
httpd_uri_t webpage_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = webpage_get_handler, // 我们将编写这个函数来提供网页
.user_ctx = NULL
};
httpd_register_uri_handler(server, &webpage_uri);
// 注册文件上传处理函数(POST 请求)
httpd_uri_t upload_uri = {
.uri = "/upload",
.method = HTTP_POST,
.handler = file_upload_post_handler, // 我们将编写这个函数来处理文件上传
.user_ctx = NULL
};
httpd_register_uri_handler(server, &upload_uri);
}
return server;
}
创建用于上传文件的网页
为了让用户能够通过网页上传文件,我们需要提供一个 HTML 表单。这个网页将在访问 ESP32-C3 的 IP 地址时加载。
HTML 网页示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ESP32-C3 File Upload</title>
</head>
<body>
<h1>Upload File to ESP32-C3</h1>
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>
</body>
</html>
代码提供网页(webpage_get_handler
):
static esp_err_t webpage_get_handler(httpd_req_t *req) {
const char* webpage =
"<!DOCTYPE html>"
"<html>"
"<head>"
"<meta charset=\"UTF-8\">"
"<title>ESP32-C3 File Upload</title>"
"</head>"
"<body>"
"<h1>Upload File to ESP32-C3</h1>"
"<form action=\"/upload\" method=\"POST\" enctype=\"multipart/form-data\">"
"<input type=\"file\" name=\"file\" />"
"<input type=\"submit\" value=\"Upload\" />"
"</form>"
"</body>"
"</html>";
httpd_resp_set_type(req, "text/html");
return httpd_resp_send(req, webpage, strlen(webpage));
}
处理上传的文件并存储
当用户通过表单上传文件时,会发送一个 HTTP POST 请求到 /upload
。我们需要解析这个请求并将文件内容存储在 ESP32-C3 上(例如,存储到闪存或 SPIFFS 文件系统)。
配置文件系统(SPIFFS)
在处理上传之前,我们需要初始化文件系统来存储文件。这可以通过启用 SPIFFS 来实现。
配置 partitions.csv:
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x180000,
spiffs, data, spiffs, 0x190000,0x70000,
初始化 SPIFFS:
#include "esp_spiffs.h"
static void init_spiffs(void) {
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = NULL,
.max_files = 5, // 可以同时打开的文件数
.format_if_mount_failed = true
};
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK) {
printf("Failed to initialize SPIFFS (%s)\n", esp_err_to_name(ret));
return;
}
size_t total = 0, used = 0;
ret = esp_spiffs_info(NULL, &total, &used);
if (ret == ESP_OK) {
printf("SPIFFS partition size: total: %d, used: %d\n", total, used);
} else {
printf("Failed to get SPIFFS partition information (%s)\n", esp_err_to_name(ret));
}
}
处理文件上传请求
我们需要在 file_upload_post_handler
函数中处理文件上传请求。HTTP POST 请求中会包含文件数据。我们将使用 httpd_req_recv
函数来读取请求主体数据。
代码示例(file_upload_post_handler
):
static esp_err_t file_upload_post_handler(httpd_req_t *req) {
// 首先,为接收的数据分配足够的缓冲区
const int MAX_FILE_SIZE = 1024 * 1024; // 假设最大文件大小为1MB
char *buf = malloc(MAX_FILE_SIZE);
if (!buf) {
httpd_resp_send_500(req); // 内存分配失败
return ESP_FAIL;
}
int total_len = req->content_len;
int received = 0;
while (received < total_len) {
int ret = httpd_req_recv(req, buf + received, total_len - received);
if (ret <= 0) {
// 收到错误或连接关闭
free(buf);
httpd_resp_send_500(req);
return ESP_FAIL;
}
received += ret;
}
// 现在,buf 中包含了表单数据,包括文件内容
// 我们需要解析多部分表单以获取实际文件内容
// 这里我们假定表单数据简单,并直接处理文件数据
// 实际上,您需要解析表单边界和头信息以准确提取文件数据
// 在此简化实现中,我们假设只有文件数据,并将其保存到 SPIFFS 中
FILE *f = fopen("/spiffs/uploaded_file", "wb");
if (!f) {
free(buf);
httpd_resp_send_500(req);
return ESP_FAIL;
}
fwrite(buf, 1, received, f);
fclose(f);
free(buf);
httpd_resp_sendstr(req, "File uploaded successfully");
return ESP_OK;
}
注意:实际情况下,HTTP POST 请求的表单数据是多部分格式,需要解析其中的边界和头信息来正确提取文件内容。上面的示例简化了这一过程,但在现实应用中,您需要使用 multipart/form-data
解析器或者手动解析 POST 数据以正确获取文件内容。
完整代码示例
下面的完整代码示例集成了上述步骤:
#include <stdio.h>
#include <string.h>
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_spiffs.h"
#include "esp_http_server.h"
#define WIFI_SSID "ESP32C3_AP"
#define WIFI_PASS "12345678"
static const char *TAG = "HTTP_SERVER";
// 初始化 SPIFFS
static void init_spiffs(void) {
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = NULL,
.max_files = 5,
.format_if_mount_failed = true
};
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
return;
}
size_t total = 0, used = 0;
ret = esp_spiffs_info(NULL, &total, &used);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "SPIFFS partition size: total: %d, used: %d", total, used);
} else {
ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret));
}
}
// 提供文件上传网页
static esp_err_t webpage_get_handler(httpd_req_t *req) {
const char* webpage =
"<!DOCTYPE html>"
"<html>"
"<head>"
"<meta charset=\"UTF-8\">"
"<title>ESP32-C3 File Upload</title>"
"</head>"
"<body>"
"<h1>Upload File to ESP32-C3</h1>"
"<form action=\"/upload\" method=\"POST\" enctype=\"multipart/form-data\">"
"<input type=\"file\" name=\"file\" />"
"<input type=\"submit\" value=\"Upload\" />"
"</form>"
"</body>"
"</html>";
httpd_resp_set_type(req, "text/html");
return httpd_resp_send(req, webpage, strlen(webpage));
}
// 处理文件上传
static esp_err_t file_upload_post_handler(httpd_req_t *req) {
// 将整个请求主体读取到内存中(仅适用于较小的文件)
const int MAX_FILE_SIZE = 1024 * 1024;
if (req->content_len > MAX_FILE_SIZE) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "File too large");
return ESP_FAIL;
}
char *buf = malloc(req->content_len);
if (!buf) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Not enough memory");
return ESP_FAIL;
}
int received = httpd_req_recv(req, buf, req->content_len);
if (received <= 0) {
free(buf);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file");
return ESP_FAIL;
}
// 简化实现:假设文件数据完全在 buf 中
// 实际应用中需要解析 multipart/form-data
FILE *f = fopen("/spiffs/uploaded_file", "wb");
if (!f) {
free(buf);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to open file for writing");
return ESP_FAIL;
}
fwrite(buf, 1, received, f);
fclose(f);
free(buf);
httpd_resp_send_str(req, "File uploaded successfully");
return ESP_OK;
}
// 启动 HTTP 服务器
static httpd_handle_t start_webserver(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
if (httpd_start(&server, &config) == ESP_OK) {
// 注册网页处理函数
httpd_uri_t webpage_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = webpage_get_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &webpage_uri);
// 注册文件上传处理函数
httpd_uri_t upload_uri = {
.uri = "/upload",
.method = HTTP_POST,
.handler = file_upload_post_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &upload_uri);
return server;
}
ESP_LOGI(TAG, "Error starting server!");
return NULL;
}
// 初始化 Wi-Fi 热点
static void wifi_init_softap(void) {
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
wifi_config_t wifi_config = {
.ap = {
.ssid = WIFI_SSID,
.ssid_len = strlen(WIFI_SSID),
.password = WIFI_PASS,
.max_connection = 4,
.authmode = WIFI_AUTH_WPA_WPA2_PSK,
},
};
if (strlen(WIFI_PASS) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}
esp_wifi_set_mode(WIFI_MODE_AP);
esp_wifi_set_config(WIFI_IF_AP, &wifi_config);
esp_wifi_start();
ESP_LOGI(TAG, "ESP32-C3 AP started. SSID:%s password:%s", WIFI_SSID, WIFI_PASS);
}
void app_main(void) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_softap();
init_spiffs();
httpd_handle_t server = start_webserver();
if (server == NULL) {
ESP_LOGE(TAG, "Failed to start webserver");
}
}
附加注意事项
-
文件大小限制:ESP32-C3 的存储空间有限,如果文件过大,可能无法存储或处理。我们在代码中设置了
MAX_FILE_SIZE = 1024 * 1024
(1MB),您可以根据需要调整。 -
文件系统选择:本示例使用 SPIFFS 存储上传的文件,您也可以选择 FATFS(如
wl_fs_fat
) 或其他文件系统。不过,必须根据实际需求选择合适的存储方式。 -
multipart/form-data 解析:上面的代码简化了表单解析过程。在实际应用中,您需要正确解析
multipart/form-data
内容以获取文件名、文件内容等信息。您可以使用现有的multipart 解析库或自行编写解析逻辑。 -
安全性:如果只在开发环境中使用,这种实现是可行的。但是,如果您计划在实际环境中使用,请注意安全问题。设置一个合适的密码(如果需要),并且确保网络环境安全。
-
HTTP 服务器性能:此 HTTP 服务器实现是基本的。如果需要更高的性能或安全性,可以使用 HTTPS(TL
S 加密)。对于加密流量,需要配置 TLS 证书。
通过上述步骤,您可以实现 ESP32-C3 创建热点、提供网页、接收文件上传并存储到 SPIFFS。这种功能类似于无线 U 盘,使用户能够通过手机浏览器上传文件到 ESP32-C3。