37款传感器与执行器的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里准备逐一动手尝试系列实验,不管成功(程序走通)与否,都会记录下来——小小的进步或是搞不掂的问题,希望能够抛砖引玉。
【Arduino】168种传感器模块系列实验(资料代码+仿真编程+图形编程)
实验二百三十:ESP32 CAM开发板 带OV2640摄像头模块 WIFI+蓝牙模块
ESP32-CAM 是一款非常小的摄像头模块,配备 ESP32-S 芯片。除了 OV2640 摄像头和几个用于连接外围设备的 GPIO 外,它还具有一个 microSD 卡插槽,可用于存储使用摄像头拍摄的图像或存储文件以提供给客户。包括乐鑫 ESP32-S Wifi + 蓝牙+BLE 芯片、2MP 摄像头模块 OV2640 和带有 CH340 UART 芯片的 USB 编程适配器。
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 帧/秒
实验模块接线示意图
【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");
}
}
实验串口返回情况
代码解释:
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): 初始化摄像头。
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): 释放帧缓冲区。
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卡,同时保持低功耗模式。
实验场景图
标签:动手做,pin,Arduino,长时,GPIO,Serial,config,EEPROM,SD From: https://blog.csdn.net/weixin_41659040/article/details/142176752