了解如何使用 ESP32-CAM 开发板构建 Web 服务器,该板允许您发送命令以拍摄照片,并在保存在 SPIFFS 中的浏览器中可视化最新拍摄的照片。我们还添加了在必要时旋转图像的选项。
当您访问 Web 服务器时,您将看到三个按钮:
- 旋转(ROTATE):根据您的 ESP32-CAM 方向,您可能需要旋转照片。
- 拍摄照片(CAPTURE PHOTO):当您单击此按钮时,ESP32-CAM 会拍摄一张新照片并将其保存在 ESP32SPIFFS 中。
请至少等待 5 秒钟,然后刷新网页,以确保 ESP32-CAM 拍摄并存储照片;
- 刷新页面(REFRESH PAGE):单击此按钮时,网页将刷新,并使用最新照片进行更新。
注意:如前所述,拍摄的最新照片存储在 ESP32 SPIFFS 中,因此即使您重新启动开发板,也始终可以访问上次保存的照片。
我们将使用 Arduino IDE 对 ESP32 开发板进行编程。因此,您需要安装 Arduino IDE 以及 ESP32 附加组件:
打开 Arduino>文件>首选项 在附加开发板管理器网址填写以下网址
https://git.oschina.net/dfrobot/FireBeetle-ESP32/raw/master/package_esp32_index.json
然后打开 Arduino >工具>开发板>开发板管理器 搜索ESP32并下载
如果下载失败 参考这个 :
Arduino–Arduino IDE上安装ESP32开发环境(两种方法)
要构建 Web 服务器,我们将使用 ESPAsyncWebServer 库。此库还需要异步 TCP 库才能正常工作。请按照以下步骤安装这些库。
按照以下步骤安装 ESPAsyncWebServer 库:
(下载不成功的话我在下面提供了网盘连接)
或者,下载库后,可以转到 Arduino >项目 >加载库>添加.ZIP 库…,然后选择您刚刚下载的库文件。
ESPAsyncWebServer 库需要 AsyncTCP 库才能工作。请按照以下步骤安装该库:
(下载不成功的话我在下面提供了网盘连接)
或者,下载库后,可以转到 Arduino >项目 >加载库>添加.ZIP 库…,然后选择您刚刚下载的库文件。
将以下代码复制到您的 Arduino IDE。此代码构建一个 Web 服务器,允许您使用 ESP32-CAM 拍摄照片并显示最后拍摄的照片。根据 ESP32-CAM 的方向,您可能需要旋转图片,因此我们还包含了该功能。
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-cam-take-photo-display-web-server/
IMPORTANT!!!
- Select Board "AI Thinker ESP32-CAM"
- GPIO 0 must be connected to GND to upload a sketch
- After connecting GPIO 0 to GND, press the ESP32-CAM on-board RESET button to put your board in flashing mode
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*********/
#include "WiFi.h"
#include "esp_camera.h"
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "soc/soc.h" // Disable brownour problems
#include "soc/rtc_cntl_reg.h" // Disable brownour problems
#include "driver/rtc_io.h"
#include
#include
#include
#include
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
boolean takeNewPhoto = false;
// Photo File Name to save in SPIFFS
#define FILE_PHOTO "/photo.jpg"
// OV2640 camera module pins (CAMERA_MODEL_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
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<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";
void setup() {
// Serial port for debugging purposes
Serial.begin(115200);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
if (!SPIFFS.begin(true)) {
Serial.println("An Error has occurred while mounting SPIFFS");
ESP.restart();
}
else {
delay(500);
Serial.println("SPIFFS mounted successfully");
}
// Print ESP32 Local IP Address
Serial.print("IP Address: http://");
Serial.println(WiFi.localIP());
// Turn-off the 'brownout detector'
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
// OV2640 camera module
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
if (psramFound()) {
config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = 10;
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
// Camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
ESP.restart();
}
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send_P(200, "text/html", index_html);
});
server.on("/capture", HTTP_GET, [](AsyncWebServerRequest * request) {
takeNewPhoto = true;
request->send_P(200, "text/plain", "Taking Photo");
});
server.on("/saved-photo", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(SPIFFS, FILE_PHOTO, "image/jpg", false);
});
// Start server
server.begin();
}
void loop() {
if (takeNewPhoto) {
capturePhotoSaveSpiffs();
takeNewPhoto = false;
}
delay(1);
}
// Check if photo capture was successful
bool checkPhoto( fs::FS &fs ) {
File f_pic = fs.open( FILE_PHOTO );
unsigned int pic_sz = f_pic.size();
return ( pic_sz > 100 );
}
// Capture Photo and Save it to 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: %s\n", 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 );
}
首先,包括与摄像机配合使用、构建 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
#include
#include
#include
接下来,在以下变量中写入您的网络凭证,以便 ESP32-CAM 可以连接到您的本地网络。
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
创建一个异步网络服务器端口 80 上的对象。
AsyncWebServer server(80);
这拍摄新照片布尔变量指示何时需要拍摄新照片。
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 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 Photoh2>
<p>It might take more than 5 seconds to capture a photo.p>
<p>
<button onclick="rotatePhoto();">ROTATEbutton>
<button onclick="capturePhoto()">CAPTURE PHOTObutton>
<button onclick="location.reload();">REFRESH PAGEbutton>
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 的工作原理。我们只是快速概述一下。
基本上,创建三个按钮:旋转; 拍摄照片和刷新页面。每张照片调用不同的 JavaScript 函数:旋转照片(),捕获照片()和重新加载().
<button onclick="rotatePhoto();">ROTATEbutton>
<button onclick="capturePhoto()">CAPTURE PHOTObutton>
<button onclick="location.reload();">REFRESH PAGEbutton>
这 捕获照片() 函数在/捕获指向 ESP32 的 URL,因此它会拍摄一张新照片。
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; }
我们不确定使用JavaScript旋转照片的“最佳”方法是什么。此方法非常有效,但可能有更好的方法可以做到这一点。如果您有任何建议,请与我们分享。
最后,以下部分显示照片。
<div><img src="saved-photo" id="photo" width="70%"></div>
当,您单击 刷新 按钮,它将加载最新的图像。
在 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());
后面的部分,使用正确的设置配置和初始化相机。
接下来,我们需要处理当 ESP32-CAM 在 URL 上收到请求时发生的情况。
当 ESP32-CAM 在根 / URL 上收到请求时,我们会发送 HTML 文本来构建网页。
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send_P(200, "text/html", index_html);
});
当我们按下”捕获“按钮,我们向 ESP32 /capture URL 发送请求。当这种情况发生时,我们设置拍摄新照片变量到真,以便我们知道是时候拍摄新照片了。
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(),如果 拍摄新照片 变量为 True,我们调用 capturePhotoSaveSpiffs() 拍摄新照片并将其保存到 SPIFFS。然后,设置 拍摄新照片 变量到 假 .
void loop() {
if (takeNewPhoto) {
capturePhotoSaveSpiffs();
takeNewPhoto = false;
}
delay(1);
}
草图中还有另外两个函数: 检查照片() 和 capturePhotoSaveSpiffs().
这 检查照片() 函数检查照片是否已成功保存到 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: %s\n", 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 );
}
实际上应该CH340G的5V接ESP32-CAM的5V,如图只用3.3V供电电压不稳会报错
要上传代码,请按照以下步骤操作:
打开浏览器并输入 ESP32-CAM IP 地址。然后,单击“捕获照片”(CAPTURE PHOTO)以拍摄新照片,并等待几秒钟,以便将照片保存在SPIFFS中。
此时在Arduino IDE Serial Monitor窗口中,您应该看到类似的消息:
希望对你有用
我没有撕掉ESP32-CAM 摄像头上的膜,所以照片不是那么清晰。
第一次写文章请多多指教。
库文件下载 提取码:csdn
来源出处