驴友花雕 发表于 2024-9-12 15:11:02

【花雕学编程】Arduino动手做(230)--深度休眠长延时摄影



ESP32-Cam 是一款运行在 ESP32-S 芯片上并使用 OV2640 摄像头的小型摄像头模块。ESP32_Cam 也可以 OV7670 摄像头,但 OV2640 更好(更高的分辨率和内置的 JPEG 编码,这消除了 ESP32-S 的处理任务)。
ESP-32 Cam 规格ESP-32 系列
它支持 Wi-Fi (802.11b/g/n)
支持蓝牙 (4.2 带 BLE)
内置 LED 闪光灯
9 个 IO 端口
支持 UART、SPI、I2C 和 PWM
内置 micro SD 读卡器
输入电源:3.3V / 5V(据报道,5V 供电比 3.3V 更稳定)
OV2640 摄像头
2 百万像素
阵列尺寸:UXGA (1600 x 1200)
镜头尺寸:1/4 英寸(6.35 毫米)
最大图像传输速率:15 帧/秒



驴友花雕 发表于 2024-9-12 15:14:57

【花雕学编程】Arduino动手做(230)--深度休眠长延时摄影

【Arduino】168种传感器模块系列实验(资料代码+仿真编程+图形编程)
实验二百三十:ESP32 CAM开发板 带OV2640摄像头模块 WIFI+蓝牙模块
项目实验之十二:ESP32 CAM 长时延时摄影:在拍摄之间使设备休眠并记住帧号

实验开源代码

/*
【Arduino】168种传感器模块系列实验(资料代码+仿真编程+图形编程)
实验二百三十:ESP32 CAM开发板 带OV2640摄像头模块 WIFI+蓝牙模块
项目实验之十二:ESP32 CAM 长时延时摄影:在拍摄之间使设备休眠并记住帧号
*/

#include "esp_camera.h"
#include "SD_MMC.h"
#include "EEPROM.h"

#define EEPROM_SIZE_IN_BYTES 1 // 定义EEPROM的大小为1字节

// 选择摄像头型号
//#define CAMERA_MODEL_WROVER_KIT // 有PSRAM
//#define CAMERA_MODEL_ESP_EYE // 有PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM // 有PSRAM
//#define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera版本B有PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE // 有PSRAM
//#define CAMERA_MODEL_M5STACK_ESP32CAM // 无PSRAM
#define CAMERA_MODEL_AI_THINKER // 有PSRAM
//#define CAMERA_MODEL_TTGO_T_JOURNAL // 无PSRAM

#include "camera_pins.h"
const char* photoPrefix = "/photo_"; // 照片前缀
int photoNumber = 0; // 照片编号

#define MICROSECONDS_IN_SECONDS 1000000 // 每秒的微秒数
#define SLEEP_TIME_IN_SECONDS 120 // 睡眠时间(秒)
unsigned long sleepTime = MICROSECONDS_IN_SECONDS * SLEEP_TIME_IN_SECONDS; // 睡眠时间(微秒)

void setup() {
Serial.begin(115200); // 初始化串口通信,波特率为115200
//Serial.setDebugOutput(true);
//Serial.println();

Serial.println("ESP32正在唤醒..."); // 打印唤醒信息

EEPROM.begin(EEPROM_SIZE_IN_BYTES); // 初始化EEPROM
photoNumber = EEPROM.read(0); // 从EEPROM读取照片编号

Serial.println("从偏好设置加载的下一个照片编号: " + String(photoNumber)); // 打印照片编号

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; // XCLK频率
config.pixel_format = PIXFORMAT_JPEG; // 像素格式

// 如果存在PSRAM IC,使用UXGA分辨率和更高的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;
}

#if defined(CAMERA_MODEL_ESP_EYE)
pinMode(13, INPUT_PULLUP);
pinMode(14, INPUT_PULLUP);
#endif

// 摄像头初始化
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
    Serial.printf("摄像头初始化失败,错误代码0x%x", err);
    return;
}

sensor_t * s = esp_camera_sensor_get();
// 初始传感器垂直翻转,颜色有点饱和
if (s->id.PID == OV3660_PID) {
    s->set_vflip(s, 1); // 翻转回来
    s->set_brightness(s, 1); // 提高亮度
    s->set_saturation(s, -2); // 降低饱和度
}

// 如果需要,微调图像
s->set_brightness(s, -1); // 亮度范围-2到2
//s->set_contrast(s, 1); // 对比度范围-2到2
//s->set_saturation(s, 1); // 饱和度范围-2到2
//s->set_wb_mode(s, 0); // 白平衡模式0到4
//s->set_special_effect(s, 0); // 特效0: 无, 1: 负片, 2: 灰度, 3: 红色, 4: 绿色, 5: 蓝色, 6: 棕褐色
//s->set_colorbar(s, 1); // 彩条1或0

// 降低帧大小以提高初始帧率
//s->set_framesize(s, FRAMESIZE_QVGA);
s->set_framesize(s, FRAMESIZE_XGA);
//s->set_framesize(s, FRAMESIZE_HD);

#if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
s->set_vflip(s, 1);
s->set_hmirror(s, 1);
#endif

Serial.println("初始化SD卡");
if (!SD_MMC.begin()) {
    Serial.println("SD卡初始化失败!");
    return;
}

uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
    Serial.println("SD卡槽似乎是空的!");
    return;
}

// 如果SD卡为空,则将EEPROM文件计数器重置为0
ResetPhotoNumbering();

// 拍摄第一张照片但不保存,因为它通常有绿色的色调
TakePhoto(false);

// 拍摄第二张照片并保存到SD卡
TakePhoto(true);

Serial.println("将进入深度睡眠,持续 " + String(sleepTime) + " 微秒...");
Serial.flush();

// 设置ESP32的睡眠时间间隔
esp_sleep_enable_timer_wakeup(sleepTime);

// 让ESP32进入深度睡眠
esp_deep_sleep_start();

}

void loop() {
// 本示例不需要循环代码
}

void TakePhoto(bool savePhoto) {

camera_fb_t * fb = NULL;

// 使用摄像头拍照
fb = esp_camera_fb_get();
if (!fb) {
    Serial.println("摄像头拍照失败");
    return;
}

if (!savePhoto) {
    return;
}

String photoFileName = photoPrefix + String(photoNumber) + ".jpg";

fs::FS &fs = SD_MMC;
Serial.printf("照片文件名: %s\n", photoFileName.c_str());

File file = fs.open(photoFileName.c_str(), FILE_WRITE);
if (!file) {
    Serial.println("打开文件写入模式失败");
}
else {
    file.write(fb->buf, fb->len); // 负载(图像),负载长度
    Serial.println("文件保存路径: " + String(photoFileName));
    ++photoNumber;
    if (photoNumber > 255) {
      photoNumber = 0;
    }

    EEPROM.write(0, photoNumber);
    EEPROM.commit();
    Serial.println("下一个照片编号已保存到偏好设置: " + String(photoNumber));
}
file.close();
esp_camera_fb_return(fb);
}

void ResetPhotoNumbering() {

fs::FS &fs = SD_MMC;
File sdCardRoot = fs.open("/");

if (!sdCardRoot) {
    Serial.println("打开SD卡根目录失败!");
    return;
}

if (!sdCardRoot.isDirectory()) {
    Serial.println("无法读取SD卡根目录!");
    return;
}

File file = sdCardRoot.openNextFile();
if (file.available() > 0) {
    Serial.println("SD卡不为空");
} else {
    Serial.println("SD卡为空");
    photoNumber = 0;
    EEPROM.write(0, photoNumber);
    EEPROM.commit();
    Serial.println("下一个照片编号重置为0");
}
}


驴友花雕 发表于 2024-9-12 15:16:23

【花雕学编程】Arduino动手做(230)--深度休眠长延时摄影

实验串口返回情况


驴友花雕 发表于 2024-9-12 15:28:41

【花雕学编程】Arduino动手做(230)--深度休眠长延时摄影

代码解释:

1、引入库和定义常量


#include "esp_camera.h"

#include "SD_MMC.h"

#include "EEPROM.h"



#define EEPROM_SIZE_IN_BYTES 1 // 定义EEPROM的大小为1字节

esp_camera.h: 用于摄像头的初始化和操作。
SD_MMC.h: 用于SD卡的初始化和操作。
EEPROM.h: 用于EEPROM的读写操作。
EEPROM_SIZE_IN_BYTES: 定义EEPROM的大小为1字节,用于存储照片编号。

2、选择摄像头型号


#define CAMERA_MODEL_AI_THINKER // 有PSRAM

#include "camera_pins.h"


CAMERA_MODEL_AI_THINKER: 选择AI Thinker摄像头模块。
camera_pins.h: 包含摄像头引脚定义。

3、定义全局变量和常量


const char* photoPrefix = "/photo_"; // 照片前缀

int photoNumber = 0; // 照片编号



#define MICROSECONDS_IN_SECONDS 1000000 // 每秒的微秒数

#define SLEEP_TIME_IN_SECONDS 120 // 睡眠时间(秒)

unsigned long sleepTime = MICROSECONDS_IN_SECONDS * SLEEP_TIME_IN_SECONDS; // 睡眠时间(微秒)


photoPrefix: 照片文件名前缀。
photoNumber: 照片编号,从EEPROM读取。
MICROSECONDS_IN_SECONDS: 每秒的微秒数。
SLEEP_TIME_IN_SECONDS: 睡眠时间(秒)。
sleepTime: 睡眠时间(微秒)。

4、初始化设置


void setup() {

Serial.begin(115200); // 初始化串口通信,波特率为115200

Serial.println("ESP32正在唤醒...");



EEPROM.begin(EEPROM_SIZE_IN_BYTES); // 初始化EEPROM

photoNumber = EEPROM.read(0); // 从EEPROM读取照片编号

Serial.println("从偏好设置加载的下一个照片编号: " + String(photoNumber));

Serial.begin(115200): 初始化串口通信,波特率为115200。
EEPROM.begin(EEPROM_SIZE_IN_BYTES): 初始化EEPROM。
photoNumber = EEPROM.read(0): 从EEPROM读取照片编号。

5、摄像头配置和初始化


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; // XCLK频率

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;

}



esp_err_t err = esp_camera_init(&config);

if (err != ESP_OK) {

    Serial.printf("摄像头初始化失败,错误代码0x%x", err);

    return;

}


camera_config_t config: 定义摄像头配置结构体。
config.pin_xxx: 设置摄像头引脚。
config.xclk_freq_hz: 设置XCLK频率。
config.pixel_format: 设置像素格式为JPEG。
psramFound(): 检查是否存在PSRAM,调整帧大小和JPEG质量。
esp_camera_init(&config): 初始化摄像头。


驴友花雕 发表于 2024-9-12 15:31:15

【花雕学编程】Arduino动手做(230)--深度休眠长延时摄影

6、传感器设置


sensor_t * s = esp_camera_sensor_get();

if (s->id.PID == OV3660_PID) {

    s->set_vflip(s, 1); // 翻转回来

    s->set_brightness(s, 1); // 提高亮度

    s->set_saturation(s, -2); // 降低饱和度

}



s->set_brightness(s, -1); // 亮度范围-2到2

s->set_framesize(s, FRAMESIZE_XGA);

sensor_t * s = esp_camera_sensor_get(): 获取摄像头传感器。
s->set_vflip(s, 1): 垂直翻转图像。
s->set_brightness(s, 1): 设置亮度。
s->set_saturation(s, -2): 设置饱和度。
s->set_framesize(s, FRAMESIZE_XGA): 设置帧大小。

7、初始化SD卡

Serial.println("初始化SD卡");

if(!SD_MMC.begin()){

    Serial.println("SD卡初始化失败!");

    return;

}



uint8_t cardType = SD_MMC.cardType();

if(cardType == CARD_NONE){

    Serial.println("SD卡槽似乎是空的!");

    return;

}



ResetPhotoNumbering();

SD_MMC.begin(): 初始化SD卡。
SD_MMC.cardType(): 检查SD卡类型。
ResetPhotoNumbering(): 重置照片编号。

8、拍照和保存照片



TakePhoto(false); // 拍摄第一张照片但不保存

TakePhoto(true); // 拍摄第二张照片并保存到SD卡



Serial.println("将进入深度睡眠,持续 " + String(sleepTime) + " 微秒...");

Serial.flush();



esp_sleep_enable_timer_wakeup(sleepTime);

esp_deep_sleep_start();

}

TakePhoto(false): 拍摄第一张照片但不保存。
TakePhoto(true): 拍摄第二张照片并保存到SD卡。
esp_sleep_enable_timer_wakeup(sleepTime): 设置ESP32的睡眠时间间隔。
esp_deep_sleep_start(): 让ESP32进入深度睡眠。

9、拍照函数


void TakePhoto(bool savePhoto) {

if (!savePhoto) {

    return;

}



camera_fb_t * fb = esp_camera_fb_get();

if (!fb) {

    Serial.println("摄像头拍照失败");

    return;

}



String photoFileName = photoPrefix + String(photoNumber) + ".jpg";

fs::FS &fs = SD_MMC;

Serial.printf("照片文件名: %s\n", photoFileName.c_str());



File file = fs.open(photoFileName.c_str(), FILE_WRITE);

if (!file) {

    Serial.println("打开文件写入模式失败");

} else {

    file.write(fb->buf, fb->len); // 写入图像数据

    Serial.println("文件保存路径: " + String(photoFileName));

    ++photoNumber;

    if (photoNumber > 255) {

      photoNumber = 0;

    }



    EEPROM.write(0, photoNumber);

    EEPROM.commit();

    Serial.println("下一个照片编号已保存到偏好设置: " + String(photoNumber));

}

file.close();

esp_camera_fb_return(fb);

}

TakePhoto(bool savePhoto): 拍照函数。
camera_fb_t * fb = esp_camera_fb_get(): 获取摄像头帧缓冲区。
String photoFileName = photoPrefix + String(photoNumber) + “.jpg”: 创建照片文件名。
File file = fs.open(photoFileName.c_str(), FILE_WRITE): 打开文件写入模式。
file.write(fb->buf, fb->len): 写入图像数据。
++photoNumber: 增加照片编号。
EEPROM.write(0, photoNumber): 更新EEPROM中的照片编号。
EEPROM.commit(): 提交EEPROM更改。
esp_camera_fb_return(fb): 释放帧缓冲区。

驴友花雕 发表于 2024-9-12 15:32:51

【花雕学编程】Arduino动手做(230)--深度休眠长延时摄影

10、重置照片编号函数

void ResetPhotoNumbering() {

fs::FS &fs = SD_MMC;

File sdCardRoot = fs.open("/");



if (!sdCardRoot) {

    Serial.println("打开SD卡根目录失败!");

    return;

}



if (!sdCardRoot.isDirectory()) {

    Serial.println("无法读取SD卡根目录!");

    return;

}



File file = sdCardRoot.openNextFile();

if (file) {

    Serial.println("SD卡不为空");

} else {

    Serial.println("SD卡为空");

    photoNumber = 0;

    EEPROM.write(0, photoNumber);

    EEPROM.commit();

    Serial.println("下一个照片编号重置为0");

}

}

fs::FS &fs = SD_MMC: 使用SD_MMC文件系统。
File sdCardRoot = fs.open(“/”): 打开SD卡根目录。
if (!sdCardRoot): 检查是否成功打开根目录。
if (!sdCardRoot.isDirectory()): 检查根目录是否为目录。
File file = sdCardRoot.openNextFile(): 打开根目录中的下一个文件。
if (file): 检查SD卡是否不为空。
if (!file): 如果SD卡为空,重置照片编号为0,并更新EEPROM。

11、主循环函数

void loop() {

// 本示例不需要循环代码

}

void loop(): 主循环函数。在这个示例中,不需要在循环中执行任何操作,因为所有逻辑都在setup()函数中完成。

总结
这个代码实现了以下功能:
1、初始化ESP32-CAM和SD卡。
2、从EEPROM读取照片编号。
3、配置摄像头参数并初始化摄像头。
4、拍摄照片并保存到SD卡。
5、更新EEPROM中的照片编号。
6、进入深度睡眠以节省电力。
7、在SD卡为空时重置照片编号。
这些功能使你的ESP32-CAM能够定期拍照并保存到SD卡,同时保持低功耗模式。


驴友花雕 发表于 2024-9-12 15:35:35

【花雕学编程】Arduino动手做(230)--深度休眠长延时摄影

实验场景图


页: [1]
查看完整版本: 【花雕学编程】Arduino动手做(230)--深度休眠长延时摄影