了解如何使用ESP32-CAM开发板构建Web服务器,您可以通过在浏览器可视化最新捕获的照片,并且可以发送命令进行拍照并保存在SPIFFS中。如果需要,我们还添加了旋转图像的选项。

您可能会喜欢我们博客中的其它ESP32-CAM项目。实际上,您可以通过添加一个PIR传感器(在检测到运动时进行拍照),一个物理按钮来拍照或在另一个URL路径中包括视频流功能,来进一步推进该项目。
其它ESP32-CAM项目和教程:
所需零件
要遵循此项目,您需要以下部分:
- 带有OV2640的ESP32-CAM
- 母对母连接线
- FTDI编程器
- 5V电源或移动电源
项目概述
下图显示了我们将在本教程中构建的Web服务器。

当您访问Web服务器时,您将看到三个按钮:
- 旋转(ROTATE): 根据您的ESP32-CAM方向,您可能需要旋转照片;
- 拍摄照片(CAPTURE PHOTO):单击此按钮时,ESP32-CAM会拍摄一张新照片并将其保存在ESP32 SPIFFS中。刷新网页前请等待至少5秒钟,以确保ESP32-CAM拍摄并存储照片。
- 刷新页面(REFRESH PAGE): 当您单击此按钮时,网页将刷新,并使用最新的照片进行更新。
注意:如前所述,最新拍摄的照片存储在ESP32 SPIFFS中,因此即使您重新启动开发板,也始终可以访问最后保存的照片。
安装ESP32附加组件
我们将使用Arduino IDE对ESP32开发板进行编程。因此,您需要安装Arduino IDE以及ESP32附加组件:
安装库
要构建Web服务器,我们将使用ESPAsyncWebServer库。该库还需要AsyncTCP库才能正常工作。请按照以下步骤安装这些库。
安装ESPAsyncWebServer库
请按照以下步骤安装 ESPAsyncWebServer 库:
安装ESP32的异步TCP库
ESPAsyncWebServer库需要 AsyncTCP 库才能工作。请按照以下步骤安装该库:
ESP32-CAM拍摄并显示Photo Web Server程序
将以下代码复制到您的Arduino IDE中。此代码构建了一个Web服务器,可让您使用ESP32-CAM拍摄照片并显示最近拍摄的照片。根据您ESP32-CAM的方向,您可能需要旋转图片,因此我们也包含了该功能。
代码如何工作
首先,包括与相机一起使用,构建Web服务器和使用SPIFFS所需的库。
#include "WiFi.h"
#include "esp_camera.h"
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "soc/soc.h" // Disable brownout problems
#include "soc/rtc_cntl_reg.h" // Disable brownout problems
#include "driver/rtc_io.h"
#include <ESPAsyncWebServer.h>
#include <StringArray.h>
#include <SPIFFS.h>
#include <FS.h>
接下来,在以下变量中编写您的网络凭证,以便ESP32-CAM可以连接到您的本地网络。
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
创建一个 AsyncWebServer 80端口上的对象。
AsyncWebServer server(80);
这 takeNewPhoto 布尔变量指示何时该拍摄新照片。
boolean takeNewPhoto = false;
然后,定义要保存在SPIFFS中的照片的路径和名称。
#define FILE_PHOTO "/photo.jpg"
接下来,为ESP32-CAM AI THINKER模块定义摄像头引脚。
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
建立网页
接下来,我们有HTML来构建网页:
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { text-align:center; }
.vert { margin-bottom: 10%; }
.hori{ margin-bottom: 0%; }
</style>
</head>
<body>
<div id="container">
<h2>ESP32-CAM Last Photo</h2>
<p>It might take more than 5 seconds to capture a photo.</p>
<p>
<button onclick="rotatePhoto();">ROTATE</button>
<button onclick="capturePhoto()">CAPTURE PHOTO</button>
<button onclick="location.reload();">REFRESH PAGE</button>
</p>
</div>
<div><img src="saved-photo" id="photo" width="70%"></div>
</body>
<script>
var deg = 0;
function capturePhoto() {
var xhr = new XMLHttpRequest();
xhr.open('GET', "/capture", true);
xhr.send();
}
function rotatePhoto() {
var img = document.getElementById("photo");
deg += 90;
if(isOdd(deg/90)){ document.getElementById("container").className = "vert"; }
else{ document.getElementById("container").className = "hori"; }
img.style.transform = "rotate(" + deg + "deg)";
}
function isOdd(n) { return Math.abs(n % 2) == 1; }
</script>
</html>)rawliteral";
我们不会详细介绍此HTML的工作方式。我们只是简要介绍一下。
网页中的英文如果改成中文注意,需要在head标签加入这条代码,不然英文会乱码:
<meta charset="UTF-8">
基本上,创建三个按钮: 旋转; 拍摄照片 和 刷新页面。每张照片调用不同的JavaScript函数:rotationPhoto(), capturePhoto() 和 reload():
<button onclick="rotatePhoto();">ROTATE</button>
<button onclick="capturePhoto()">CAPTURE PHOTO</button>
<button onclick="location.reload();">REFRESH PAGE</button>
capturePhoto() 函数在上发送请求 /捕获 ESP32的URL,因此需要拍摄一张新照片。
function capturePhoto() {
var xhr = new XMLHttpRequest();
xhr.open('GET', "/capture", true);
xhr.send();
}
rotationPhoto() 功能旋转照片。
function rotatePhoto() {
var img = document.getElementById("photo");
deg += 90;
if(isOdd(deg/90)){ document.getElementById("container").className = "vert"; }
else{ document.getElementById("container").className = "hori"; }
img.style.transform = "rotate(" + deg + "deg)";
}
function isOdd(n) { return Math.abs(n % 2) == 1; }
我们不确定使用JavaScript旋转照片的“最佳”方法是什么。该方法可以很好地工作,但是可能有更好的方法可以做到这一点。如果您有任何建议,请与我们分享。
最后,以下部分显示了照片。
<div><img src="saved-photo" id="photo" width="70%"></div>
当您单击 刷新 按钮,它将加载最新图像。
setup()
在 setup() 里面 ,初始化串行通讯:
Serial.begin(115200);
将ESP32-CAM连接到您的局域网:
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
初始化SPIFFS:
if (!SPIFFS.begin(true)) {
Serial.println("An Error has occurred while mounting SPIFFS");
ESP.restart();
}
else {
delay(500);
Serial.println("SPIFFS mounted successfully");
}
打印ESP32-CAM的本地IP地址:
Serial.print("IP Address: http://");
Serial.println(WiFi.localIP());
接下来的几行将使用正确的设置对摄像机进行配置和初始化。
处理Web服务器
接下来,我们需要处理ESP32-CAM在URL上收到请求时发生的情况。
当ESP32-CAM在根目录/ URL上收到请求时,我们发送HTML文本来构建网页。
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send_P(200, "text/html", index_html);
});
当我们按“捕获Web服务器上的“”按钮,我们向ESP32 / capture URL发送一个请求。发生这种情况时,我们将takeNewPhoto 可变为 真的,以便我们知道该拍摄新照片了。
server.on("/capture", HTTP_GET, [](AsyncWebServerRequest * request) {
takeNewPhoto = true;
request->send_P(200, "text/plain", "Taking Photo");
});
如果/ saved-photo URL上有请求,请将保存在SPIFFS中的照片发送到连接的客户端:
server.on("/saved-photo", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(SPIFFS, FILE_PHOTO, "image/jpg", false);
});
最后,启动Web服务器。
server.begin();
loop()
在 loop() 里面 ,如果 takeNewPhoto 变量为True,我们称 capturePhotoSaveSpiffs()拍摄新照片并将其保存到SPIFFS。然后,将takeNewPhoto 可变为 错误的。
void loop() {
if (takeNewPhoto) {
capturePhotoSaveSpiffs();
takeNewPhoto = false;
}
delay(1);
}
拍张照片
程序中还有两个其它功能: checkPhoto() 和 capturePhotoSaveSpiffs()。
checkPhoto() 功能检查照片是否已成功保存到SPIFFS。
bool checkPhoto( fs::FS &fs ) {
File f_pic = fs.open( FILE_PHOTO );
unsigned int pic_sz = f_pic.size();
return ( pic_sz > 100 );
}
capturePhotoSaveSpiffs() 功能拍摄照片并将其保存到SPIFFS。
void capturePhotoSaveSpiffs( void ) {
camera_fb_t * fb = NULL; // pointer
bool ok = 0; // Boolean indicating if the picture has been taken correctly
do {
// Take a photo with the camera
Serial.println("Taking a photo...");
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
return;
}
// Photo file name
Serial.printf("Picture file name: %sn", FILE_PHOTO);
File file = SPIFFS.open(FILE_PHOTO, FILE_WRITE);
// Insert the data in the photo file
if (!file) {
Serial.println("Failed to open file in writing mode");
}
else {
file.write(fb->buf, fb->len); // payload (image), payload length
Serial.print("The picture has been saved in ");
Serial.print(FILE_PHOTO);
Serial.print(" - Size: ");
Serial.print(file.size());
Serial.println(" bytes");
}
// Close the file
file.close();
esp_camera_fb_return(fb);
// check if file has been correctly saved in SPIFFS
ok = checkPhoto(SPIFFS);
} while ( !ok );
}
该功能基于dualvim的程序。
ESP32-CAM上传代码
要将代码上传到ESP32-CAM开发板,请使用FTDI编程器将其连接到您的计算机 。请遵循以下原理图:

重要提示: GPIO 0需要连接到GND,以便能够上传代码。
许多FTDI编程器都有一个连接线,可让您选择3.3V或5V。确保连接线在正确的位置以选择5V。
ESP32-CAM | FTDI编程器 |
GND | GND |
5V | VCC(5V) |
U0R | TX |
U0T | 接收 |
GPIO 0 | GND |
要上传代码,请按照以下步骤操作:
1)转到工具>主板,然后选择AI-Thinker ESP32-CAM。
2)进入工具>端口,选择ESP32连接的COM端口。
3)然后,点击上传按钮上传代码。

4)当您开始在调试窗口中看到这些点时,如下所示,请按ESP32-CAM板载RST按钮。

几秒钟后,该代码应成功上传到您的电路板上。
示范
打开浏览器,输入ESP32-CAM IP地址。然后,单击“捕获照片”以拍摄新照片,并等待几秒钟以将照片保存到SPIFFS中。
然后,如果您按下“刷新页面”按钮,页面将使用最新保存的照片进行更新。如果需要调整图像方向,可以始终使用“ ROTATE ”按钮进行调整。

在您的Arduino IDE串行监视器窗口中,您应该看到类似的消息:
