1 Star 0 Fork 1

VENTIM / 22.7 esp32cam多功能摄像机

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
esp32cam_TFT5.2 移植SD卡.txt 15.14 KB
一键复制 编辑 原始数据 按行查看 历史
志宇益生菌 提交于 2023-10-28 12:08 . 提交已有的代码备份文件
// Example for library:
// https://github.com/Bodmer/TJpg_Decoder
//这里使用内核0 解码jpg 内核1执行逻辑代码和tft显示
//并行使用esp32 的2个内核,例程测试解码+渲染=66+37=66ms
//我自己测试结果:
//自带320 240 图片,无缩放显示160 120上,36ms
//缩放2 79ms
//改为DMA传输 缩放2 为80ms 使用DMA改善不大?
//测试改为摄像头图像 缩放1 24-28ms, DMA 比上一个解码库快20ms左右!
//测试 缩放1 320 240 的一角 48ms左右
//测试 缩放2 320 240变160 120 65-71ms,整体都比上一个库快!
/*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)
FRAMESIZE_UXGA (1600 x 1200)
*/
//1 文字图形ok
//2 拍照显示到tft,周期显示特定图片。 显示缩放0.5和左上拍摄图片 相关函数
// 》有时正常,有时显示拍照的图片不完整
//3 拍照设置为160x120,尝试改善刷屏的屏闪 使用DMA方式 测试原装板子,运行正常 刚好大小的 1张图43ms
//4 移植SDMMC代码 拍照存储。间歇显示存储,存完需要重启tft才正常,原因可能是去初始化DMA导致。 重启前后图片间隔3s
//5.1 使用新库TJpg_Decoder,实现拍照显示,速度比上一个库快。
//5.2 将SD卡相关代码移植进来。 360*240缩放dma显示 70ms左右。 整体刷新间隔: 差不多70ms
//自动复位后,存储SD卡失败!!
//adc按键、
//SD卡存储 与 tft分时使用! 或者能否同时
//大led引脚是否改动?
//现在tft的引脚连接: 在tft的User_Setup.h里
//#define TFT_MOSI 13 固定
//#define TFT_SCLK 14 固定
//#define TFT_CS 15 //可省略 片选 可接地持续运行
//#define TFT_DC 2 //1数据 0命令
//#define TFT_RST 12 //可省略 低电平复位 可连接esp32复位引脚,上电复位
//SD卡占用引脚: 剩余引脚: 串口(1、3),0,16(spram占用) 33(adc做多按键!)
//HS2_DATA0 2 spi-MISO
//HS2_DATA1 4 可省略
//HS2_DATA2 12 可省略
//HS2_DATA3 13 spi-SS
//HS2_CMD 15 spi-MOSI
//HS2_CLK 14 spi-SCK
/*#########################
Taking picture...
Camera capture ok
===============
JPEG image info
===============
Width :320
Height :240
Components :1
MCU / row :20
MCU / col :30
Scan type :2
MCU width :16
MCU height :8
===============
Total render time was : 80 ms
#########################
Mouse 1
===============
JPEG image info
===============
Width :160
Height :107
Components :1
MCU / row :10
MCU / col :7
Scan type :4
MCU width :16
MCU height :16
===============
Total render time was : 51 ms
*/
#include <SPI.h>
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
#include <TJpg_Decoder.h> //该文件里注释了//#include <LittleFS.h>
#include "FS.h"
#include "SD_MMC.h"
#include "esp_camera.h"
// 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
camera_fb_t * fb = NULL; //照片指针
char frame_size_num1=2; //有关设置图片大小的 0-8 越大越大
char jpeg_quality_num1=10; //有关图片 质量 越小越好 10跟20好像肉眼差不多
//DMA有关的 缓冲区
uint16_t dmaBuffer1[16*8]; //乒乓缓冲区1
uint16_t dmaBuffer2[16*8]; //2
uint16_t* dmaBufferPtr = dmaBuffer1; //指向缓冲区
bool dmaBufferSel = 0; //选哪一个区
//文件
String path = "/2020_6_18_TIME_19_0_47.jpg";
//其他变量
uint16_t main_count=0;
// 全局变量对处理器0和1都可用
TaskHandle_t Task1;
const uint8_t* arrayName; // 包含Jpeg的闪存阵列的名称
bool doDecoding = false; // 开始解码的互斥标志 解码中为1
bool mcuReady = false; // 互斥标志,用于指示MCU块已准备好进行渲染 已完成一个mcu块为1
uint16_t mcuBuffer[16*16]; // 抓取解码MCU块快照的缓冲区
int32_t mcu_x, mcu_y, mcu_w, mcu_h; // 呈现MCU的位置的快照
bool camera_init(char frame_size_num = 3 , char jpeg_quality_num = 12){ //图像大小 0-6 越大越大
//配置摄像头 参数
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;
//判断 若有PSRAM则质量更高一些
if (psramFound()) { //有PSRAM硬件,更大的ram空间
Serial.println("psramFound!"); //此时io16被占用 CS
switch(frame_size_num){ //FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
case 0: config.frame_size = FRAMESIZE_QQVGA; break; //QQVGA (160 x 120)
case 1: config.frame_size = FRAMESIZE_QQVGA2; break; //QQVGA2 (128 x 160)
//HQVGA
case 2: config.frame_size = FRAMESIZE_QVGA; break; //QVGA (320 x 240)
case 3: config.frame_size = FRAMESIZE_CIF; break; //CIF (352 x 288)
case 4: config.frame_size = FRAMESIZE_VGA; break; //VGA (640 x 480)
case 5: config.frame_size = FRAMESIZE_SVGA; break; //SVGA (800 x 600)
case 6: config.frame_size = FRAMESIZE_XGA; break; //XGA (1024 x 768)
case 7: config.frame_size = FRAMESIZE_SXGA; break; //SXGA (1280 x 1024)
case 8: config.frame_size = FRAMESIZE_UXGA; break; //UXGA (1600 x 1200)
default: frame_size_num=3;config.frame_size = FRAMESIZE_SVGA; break;
}
Serial.printf("frame_size = %d [0-6]\r\n", frame_size_num);
Serial.printf("jpeg_quality = %d [0-xx]\r\n", jpeg_quality_num);
// config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = jpeg_quality_num; //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("Camera init failed with error 0x%x", err);
return false;
}
else return true;
}
//在jpeg文件解码过程中,TJpg_Decoder库将调用下一个函数
//抓取解码后的MCU块的副本进行呈现,以便解码可以继续进行
//MCU块呈现在TFT上。 注意:这个函数由处理器0调用
bool mcu_decoded(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap)
{
// Stop further decoding as image is running off bottom of screen
if ( y >= tft.height() ) return 0;
while(mcuReady) yield(); // Wait here if rendering of last MCU block to TFT is still in progress
memcpy(mcuBuffer, bitmap, 16*16*2); // Grab a copy of the MCU block image
mcu_x = x; // Grab postion and size of MCU block
mcu_y = y;
mcu_w = w;
mcu_h = h;
mcuReady = true; // Flag to tell processor 1 that rendering of MCU can start
// Return 1 to decode next Jpeg MCU block
return 1;
}
//这是在处理器0上运行的任务(Arduino sketch在处理器1上运行)
//它解码Jpeg图像
void decodeJpg(void* p) {
// 这是一个无限循环,实际上与普通草图循环相同()
//但是这个函数和循环正在处理器0上运行
for(;;) {
// 解码Jpeg图像
if (doDecoding) { // Only start decoding if main sketch sets this flag
TJpgDec.drawJpg(0, 0, fb->buf, fb->len); // 运行,直到完整的图像解码
doDecoding = false; // 将互斥体设置为false以指示解码已经结束
}
// Must yield in this loop
yield();
}
}
void renderJPEG_160x120DMA(int ypos=8) { //使用dma的方式 屏幕刷新不闪烁!
//espcam中 mcu_w=16,mcu_h=8
uint16_t w = 0, h = 0;
//检索关于图像的信息
//TJpgDec.getJpgSize(&w, &h, fb->buf, fb->len);
//Serial.print("Width = "); Serial.print(w); Serial.print(", height = "); Serial.println(h);
uint32_t drawTime = millis(); //有关计算耗时
mcuReady = false; // Flag which is set true when a MCU block is ready for display
doDecoding = true; // Flag to tell task to decode the image
//仅在解码正在进行或MCU准备好渲染时渲染MCU块
//注意:需要或mcuReady,以便在解码结束后呈现最后一个块
// 必须首先使用startWrite,以便在DMA和SPI通道设置保持配置期间TFT片选保持低电平
tft.startWrite();
while(doDecoding || mcuReady) {
if (mcuReady) {
//tft.pushImage(mcu_x, mcu_y, mcu_w, mcu_h, mcuBuffer);
if (dmaBufferSel) dmaBufferPtr = dmaBuffer2;
else dmaBufferPtr = dmaBuffer1;
dmaBufferSel = !dmaBufferSel; //交替选择 缓冲区
tft.pushImageDMA(mcu_x, mcu_y + ypos, mcu_w, mcu_h, mcuBuffer, dmaBufferPtr);
//tft.pushImageDMA(mcu_x, mcu_y, mcu_w, mcu_h, mcuBuffer, dmaBufferPtr);
mcuReady = false;
}
// Must yield in this loop
yield();
}
// 必须使用endWrite来释放TFT片选并释放SPI通道
tft.endWrite();
// 计算绘制图像需要多长时间
drawTime = millis() - drawTime;
Serial.print(F("render time was (ms) : ")); Serial.println(drawTime);
}
//SDMMC初始化------------
void sd_init(){
if (!SD_MMC.begin()) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD_MMC card attached");
return;
}
Serial.print("SD_MMC Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
}
else if (cardType == CARD_SD) {
Serial.println("SDSC");
}
else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
}
else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024); //获取SD卡大小,大小单位是MB
Serial.printf("SD_MMC size: %lluMB\n", cardSize);
//SD卡测试
// listDir(SD_MMC, "/", 0);
// createDir(SD_MMC, "/mydir");
// listDir(SD_MMC, "/", 0);
// removeDir(SD_MMC, "/mydir");
// listDir(SD_MMC, "/", 2);
// writeFile(SD_MMC, "/hello.txt", "Hello ");
// appendFile(SD_MMC, "/hello.txt", "World!\n");
// readFile(SD_MMC, "/hello.txt");
// deleteFile(SD_MMC, "/foo.txt");
// renameFile(SD_MMC, "/hello.txt", "/foo.txt");
// readFile(SD_MMC, "/foo.txt");
// testFileIO(SD_MMC, "/test.txt");
// Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024));
// Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024));
//
}
void sd_deinit(){
SD_MMC.end(); //卸载
// pinMode(2,INPUT ); //
// pinMode(4,INPUT ); //
// pinMode(12,INPUT ); //
// pinMode(13,INPUT ); //
// pinMode(14,INPUT ); //
// pinMode(15,INPUT ); //
}
//存储图片到SD卡中
void save_pic(camera_fb_t *fb){ //图片指针
path = "/2022+"+String(millis())+".jpg";
Serial.print("save pic path:"); Serial.println(path);
if (fb == NULL)
{
Serial.println( "get picture failed"); //代表获取图片失败
} else {
fs::FS &fs = SD_MMC;
// Serial.printf("Writing file: ");
// Serial.println(path);
File file = fs.open(path, FILE_WRITE);
if (!file)
{
Serial.println("file creat failed");
}
else
{
file.write(fb->buf , fb->len); //payload , lengte vd payload
Serial.println("write ok");
}
//return the frame buffer back to the driver for reuse
//esp_camera_fb_return(fb);
}
}
void tft_init(){
tft.begin();
// pinMode(2,INPUT ); //
// pinMode(4,INPUT ); //
// pinMode(12,INPUT ); //
// pinMode(13,INPUT ); //
// pinMode(14,INPUT ); //
// pinMode(15,INPUT ); //
tft.initDMA(); //初始化对应DMA
// pinMode(15,OUTPUT ); //
// digitalWrite(15, 0);
//
// pinMode(12,OUTPUT ); //
// digitalWrite(12, 1);
}
void tft_deinit(){
tft.deInitDMA(); //初始化对应DMA
// pinMode(2,INPUT ); //
// pinMode(12,INPUT ); //
// pinMode(13,INPUT ); //
// pinMode(14,INPUT ); //
// pinMode(15,INPUT ); //
}
//返回小的数值
//#define minimum(a,b) (((a) < (b)) ? (a) : (b))
void setup()
{
//创建在处理器0上运行的任务decodeJpg来解码Jpeg
xTaskCreatePinnedToCore(decodeJpg, "decodeJpg", 10000, NULL, 0, NULL, 0);
Serial.begin(115200);
Serial.println("\n\n 【项目】: esp32 cam tft 摄像机");
//sd_init(); //SD卡初始化
//tft.init(); //之前用这个
tft.begin();
tft.initDMA(); //初始化对应DMA
tft.setTextColor(0xFFFF, 0x0000);
tft.fillScreen(TFT_BLACK);
tft.setRotation(3); //显示方向 13横向 3为wifi天线与tft排针同向
// 设置缩放 1, 2, 4, or 8
TJpgDec.setJpgScale(2);
// The byte order can be swapped (set true for TFT_eSPI)
TJpgDec.setSwapBytes(true);
// The decoder must be given the exact name of the mcu buffer function above
TJpgDec.setCallback(mcu_decoded);
esp_camera_deinit(); //摄像头去初始化
pinMode(32,OUTPUT ); // 32是 cam电源控制引脚
digitalWrite(32, 0); // 猜测 低电平通电 !在睡眠期间可能无法控制断电!
delay(100);
digitalWrite(32, 1); // 猜测高电平 断电 cam的
delay(100);
pinMode(32,INPUT ); //释放 引脚
if(camera_init(frame_size_num1,jpeg_quality_num1)==true) Serial.println("camera init ok!"); //默认是 FRAMESIZE_UXGA
else{
Serial.println("camera init error!");
return;
}
}
void loop()
{
if(main_count>60000) main_count=0;
else main_count++;
Serial.println("Taking picture...");
esp_err_t res = ESP_OK;
fb = esp_camera_fb_get(); //拍照 返回照片指针
if (!fb) { //拍照失败
Serial.println("Camera capture failed!");
}
else{
if(main_count%200==0){ //200次存一张图片
tft_deinit();
sd_init(); //SD卡初始化
save_pic(fb);
//delay(2000);
sd_deinit();
delay(200);
//tft_init(); //SD卡后 好像无法完全初始化
ESP.restart(); // 板子重启 这里复位前后图片时间间隔3s左右
}
else
{ //在这的话,大约 40-73ms 复杂图70ms左右
//tft_init();
//tft.setRotation(3); //显示方向 横向
//tft.fillScreen(TFT_BLACK);
// JpegDec.decodeArray(fb->buf, fb->len); //解码 jpg
// Serial.println("decode ok");
renderJPEG_160x120DMA(8); //固定偏移 解码+显示
Serial.println("#########################");
//delay(2000);
//tft_deinit();
//delay(200);
}
esp_camera_fb_return(fb); //应该是销毁图片指针
}
}
C++
1
https://gitee.com/ventim/esp32cam-camera.git
git@gitee.com:ventim/esp32cam-camera.git
ventim
esp32cam-camera
22.7 esp32cam多功能摄像机
master

搜索帮助