引言
本项目最终目标是实现Firebeetle 2 ESP32-S3 的实时摄像头小车制作,上一个分享内容是:
用网页服务器控制板载LED
接着将继续实现搭建一个简单的摄像头服务器。我将分享:
初始化摄像头,配置 Wi-Fi 连接以及设置一个异步 Web 服务器,以便从浏览器中查看摄像头图像。
所需材料
- Firebeetle2 ESP32-S3
- OV2640 摄像头模块
- 计算机(用于编程)
- Arduino IDE
准备工作
1.环境准备,请仔细跟着5.1操作
2.必要库安装:AXP313A库
注意事项
Wiki已说明CameraWebServer示例用法,可跟随操作实践。
本次分享案例仅显示摄像头画面。
需要安装ESPAsyncWebServer库,可参考上期分享:用网页服务器控制板载LED
完整代码
复制贴上代码后,修改无线网络ID、密码,
并在串口监视器查看IP,即可使用浏览器查看摄像头画面。
查看的设备需与Firebeetle连接同一个无线网络。
#include <WiFi.h> // 包含WiFi库
#include <ESPAsyncWebServer.h> // 包含异步Web服务器库
#include "esp_camera.h" // 包含ESP相机库
#include "DFRobot_AXP313A.h" // 包含DFRobot AXP313A电源管理模块库
DFRobot_AXP313A axp; // 创建一个DFRobot AXP313A对象,用于电源管理
const char *ssid = "eva"; // 你的Wi-Fi网络名称
const char *password = "12345678"; // 你的Wi-Fi密码
// 相机引脚定义
const int XCLK_GPIO_NUM = 45;
const int PWDN_GPIO_NUM = -1;
const int RESET_GPIO_NUM = -1;
const int SIOD_GPIO_NUM = 1;
const int SIOC_GPIO_NUM = 2;
const int Y9_GPIO_NUM = 48;
const int Y8_GPIO_NUM = 46;
const int Y7_GPIO_NUM = 8;
const int Y6_GPIO_NUM = 7;
const int Y5_GPIO_NUM = 4;
const int Y4_GPIO_NUM = 41;
const int Y3_GPIO_NUM = 40;
const int Y2_GPIO_NUM = 39;
const int VSYNC_GPIO_NUM = 6;
const int HREF_GPIO_NUM = 42;
const int PCLK_GPIO_NUM = 5;
AsyncWebServer server(80); // 创建一个异步Web服务器对象,监听端口80
void configInitCamera() {
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_VGA;
config.jpeg_quality = 30;
config.fb_count = 2;
config.grab_mode = CAMERA_GRAB_LATEST;
} else {
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 60;
config.fb_count = 1;
}
esp_err_t err = esp_camera_init(&config); // 初始化相机
if (err != ESP_OK) {
Serial.printf("摄像头初始化失败,错误代码 0x%x", err);
delay(1000);
ESP.restart(); // 重启ESP32
}
}
const char *index_html = R"(
<!DOCTYPE html>
<html>
<head>
<title>ESP32 摄像头</title>
</head>
<body>
<img id="cameraImage" width="640" height="480">
<script>
function updateCameraImage() {
var imageElement = document.getElementById("cameraImage");
imageElement.src = "/image?" + new Date().getTime();
setTimeout(updateCameraImage, 200); // 0.2秒刷新一次
}
updateCameraImage();
</script>
</body>
</html>
)";
void setup() {
Serial.begin(115200); // 启动串口通信
Serial.println("开始");
while (axp.begin() != 0) { // 初始化DFRobot AXP313A电源管理模块
Serial.println("初始化错误。重试中...");
delay(1000);
}
axp.enableCameraPower(axp.eOV2640); // 启用摄像头电源
configInitCamera(); // 初始化摄像头配置
WiFi.mode(WIFI_STA); // 将WiFi设置为站点模式
WiFi.begin(ssid, password); // 连接WiFi网络
WiFi.setSleep(false); //不进入Wi-Fi模块的休眠模式
while (WiFi.status() != WL_CONNECTED) { // 等待WiFi连接
Serial.print(".");
delay(500);
}
Serial.println(WiFi.localIP()); // 打印本地IP地址
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", index_html); // 处理根目录请求并发送HTML页面
});
server.on("/image", HTTP_GET, [](AsyncWebServerRequest *request) {
camera_fb_t *fb = esp_camera_fb_get();
if (fb) {
AsyncWebServerResponse *response = request->beginResponse_P(200, "image/jpeg", fb->buf, fb->len);
request->send(response); // 发送摄像头图像数据
esp_camera_fb_return(fb); // 释放图像缓冲
}
});
server.begin(); // 启动Web服务器
}
void loop() {
// 这里可以放置与摄像头相关的代码
}
代码解析
引入所需的库
这些库的导入是为了提供所需的功能。
WiFi库用于连接到Wi-Fi网络,
ESPAsyncWebServer库用于创建Web服务器,
esp_camera库用于摄像头控制,
DFRobot_AXP313A库用于电源管理。
#include <WiFi.h> // 包含WiFi库
#include <ESPAsyncWebServer.h> // 包含异步Web服务器库
#include "esp_camera.h" // 包含ESP相机库
#include "DFRobot_AXP313A.h" // 包含DFRobot AXP313A电源管理模块库
创建变量与定义
在这一部分,创建了一些变量和对象以便在后续的代码中使用。
axp对象用于电源管理
ssid和password是Wi-Fi连接所需的网络名称和密码,
而后面的一系列常量用于定义摄像头引脚的设置。
DFRobot_AXP313A axp; // 创建一个DFRobot AXP313A对象,用于电源管理
const char *ssid = "eva"; // 你的Wi-Fi网络名称
const char *password = "12345678"; // 你的Wi-Fi密码
// 相机引脚定义
const int XCLK_GPIO_NUM = 45;
const int PWDN_GPIO_NUM = -1;
const int RESET_GPIO_NUM = -1;
const int SIOD_GPIO_NUM = 1;
const int SIOC_GPIO_NUM = 2;
const int Y9_GPIO_NUM = 48;
const int Y8_GPIO_NUM = 46;
const int Y7_GPIO_NUM = 8;
const int Y6_GPIO_NUM = 7;
const int Y5_GPIO_NUM = 4;
const int Y4_GPIO_NUM = 41;
const int Y3_GPIO_NUM = 40;
const int Y2_GPIO_NUM = 39;
const int VSYNC_GPIO_NUM = 6;
const int HREF_GPIO_NUM = 42;
const int PCLK_GPIO_NUM = 5;
AsyncWebServer server(80); // 创建一个异步Web服务器对象,监听端口80
配置摄像头
这个函数configInitCamera用于配置和初始化摄像头的参数。摄像头的各项参数被设置为config对象中,包括像素格式、分辨率、JPEG质量等。
void configInitCamera() {
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_VGA;
config.jpeg_quality = 30;
config.fb_count = 2;
config.grab_mode = CAMERA_GRAB_LATEST;
} else {
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 60;
config.fb_count = 1;
}
esp_err_t err = esp_camera_init(&config); // 初始化相机
if (err != ESP_OK) {
Serial.printf("摄像头初始化失败,错误代码 0x%x", err);
delay(1000);
ESP.restart(); // 重启ESP32
}
}
关于图像质量,可以看这一段代码,
config.jpeg_quality 参数被设置为 30 和 60。这是一个表示 JPEG 图像质量的值,取值范围从 0 到 63,其中 0 表示最低质量,而 63 表示最高质量。较低的质量值会导致更高的压缩,因此图像质量较低,而较高的质量值会导致更低的压缩,从而获得更高的图像质量。
简单说,数字越小,压缩越少,画面质量越高。
而FRAMESIZE可以参考的设置如下:
- FRAMESIZE_UXGA (1600 x 1200)
- FRAMESIZE_QVGA (320 x 240)
- FRAMESIZE_CIF (352 x 288)
- FRAMESIZE_VGA (640 x 480)
- FRAMESIZE_SVGA (800 x 600)
- FRAMESIZE_XGA (1024 x 768)
- FRAMESIZE_SXGA (1280 x 1024)
if (psramFound()) {
config.frame_size = FRAMESIZE_VGA;
config.jpeg_quality = 30; // 这里设置 JPEG 质量
config.fb_count = 2;
config.grab_mode = CAMERA_GRAB_LATEST;
} else {
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 60; // 这里设置 JPEG 质量
config.fb_count = 1;
}
配置HTML页面
这段代码定义了一个包含HTML页面的字符串,该页面包括一个显示摄像头图像的图像元素以及JavaScript代码用于定时刷新图像。
-
<!DOCTYPE html>
:这是HTML文档的声明,它指定文档使用的HTML版本。在这里,我们使用HTML5。
-
<html>
:HTML文档的根元素,包含了整个HTML页面。
-
<head>
:这是头部部分,通常包含了文档的元数据,如标题、字符集设置等。在这里,我们设置了页面的标题为 "ESP32 摄像头"。
-
<body>
:这是HTML文档的主体部分,包含了要在页面上显示的内容。
-
<img id="cameraImage" width="640" height="480">:这是一个图像元素,用于显示摄像头图像。
id="cameraImage"用于给图像元素一个唯一的标识符,以便在JavaScript中引用它。
width和
height` 属性设置了图像元素的宽度和高度。
-
<script>
:这是JavaScript代码的起始标签,用于在页面加载时执行JavaScript代码。
-
function updateCameraImage()
:这是一个JavaScript函数,用于更新摄像头图像。它通过 getElementById
方法获取了 cameraImage
元素,然后设置其 src
属性为 /image
,后跟一个时间戳以确保每次都获取新的图像。最后,它使用 setTimeout
函数每 0.2 秒刷新一次图像。
-
updateCameraImage();
:这行代码调用 updateCameraImage
函数,以便在页面加载后立即开始刷新摄像头图像。
const char *index_html = R"(
<!DOCTYPE html>
<html>
<head>
<title>ESP32 摄像头</title>
</head>
<body>
<img id="cameraImage" width="640" height="480">
<script>
function updateCameraImage() {
var imageElement = document.getElementById("cameraImage");
imageElement.src = "/image?" + new Date().getTime();
setTimeout(updateCameraImage, 200); // 0.2秒刷新一次
}
updateCameraImage();
</script>
</body>
</html>
)";
设置setup()函数
在setup()函数中,串口通信被初始化,DFRobot AXP313A电源管理模块被初始化,摄像头配置被设置,Wi-Fi连接被建立,并Web服务器被启动。
void setup() {
Serial.begin(115200); // 启动串口通信
// ...
while (axp.begin() != 0) {
// 初始化DFRobot AXP313A电源管理模块
// ...
}
axp.enableCameraPower(axp.eOV2640); // 启用摄像头电源
configInitCamera(); // 初始化摄像头配置
WiFi.mode(WIFI_STA); // 将WiFi设置为站点模式
WiFi.begin(ssid, password); // 连接WiFi网络
WiFi.setSleep(false); // 不进入Wi-Fi模块的休眠模式
// ...
Web服务器路由设置
在这部分中,设置了两个Web服务器路由。第一个路由处理根目录请求,向浏览器发送HTML页面。第二个路由用于获取摄像头图像数据,并将其发送到浏览器。
-
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
:这是一个Web服务器路由设置,它定义了当浏览器请求根目录("/")时应该执行的操作。HTTP_GET
表示这是一个HTTP GET请求的处理。在这个示例中,注释 // 处理根目录请求并发送HTML页面
提示这是用于处理根目录请求并发送HTML页面的部分代码。实际的HTML页面发送操作被注释掉,因此在此示例中,请求根目录时并未执行具体操作。
-
server.on("/image", HTTP_GET, [](AsyncWebServerRequest *request) {
:这是另一个Web服务器路由设置,它定义了当浏览器请求 "/image" 路径时应该执行的操作。同样,HTTP_GET
表示这是一个HTTP GET请求的处理。在这个示例中,当浏览器请求 "/image" 时,会执行以下操作:
-
camera_fb_t *fb = esp_camera_fb_get();
:这行代码用于获取摄像头帧缓冲。esp_camera_fb_get()
函数用于从摄像头获取当前帧的图像数据,并将其存储在 fb
变量中。
-
if (fb) {
:这是一个条件语句,检查是否成功获取摄像头图像帧。如果成功获取,就会进入下面的操作块。
-
AsyncWebServerResponse *response = request->beginResponse_P(200, "image/jpeg", fb->buf, fb->len);
:这行代码创建一个Web服务器响应对象,其中包含了图像数据。响应的HTTP状态码为 200
,表示成功。"image/jpeg"
指定了响应的内容类型为JPEG图像。fb->buf
包含了图像数据的指针,而 fb->len
包含了图像数据的长度。
-
request->send(response);
:这行代码用于将创建的响应发送给浏览器,从而向浏览器提供了摄像头图像数据。
-
esp_camera_fb_return(fb);
:这行代码用于释放摄像头图像缓冲,以便将其用于下一帧。
通过这个Web服务器路由设置,当浏览器请求 "/image" 时,摄像头图像数据将被获取并发送给浏览器,从而允许用户查看实时摄像头图像。这对于创建一个实时视频流非常有用。
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
// 处理根目录请求并发送HTML页面
// ...
});
server.on("/image", HTTP_GET, [](AsyncWebServerRequest *request) {
camera_fb_t *fb = esp_camera_fb_get();
if (fb) {
AsyncWebServerResponse *response = request->beginResponse_P(200, "image/jpeg", fb->buf, fb->len);
request->send(response); // 发送摄像头图像数据
esp_camera_fb_return(fb); // 释放图像缓冲
}
});