VScode+esp-idf:例程(esp32-web-camera)保存图片到sd卡(文末附源码)


esp32-camera的官方例程《esp32-web-camera》增加保存图片到sd卡功能。
这篇文章以前面的文章为基础:
1.《 VScode+esp-idf:编译安信可esp32-cam例程(esp32-web-camera)
2.《 VScode+esp-idf:安信可esp32-cam开发板测试sd卡

1.工程中加入app_sd.c文件

《esp32-web-camera》例程中,是有 app_sd.c (文件附最后),但是没有加入工程中。 app_sd.c 放到 《main》文件夹中,修改《main》文件夹中的 CMakeLists.txt

set(COMPONENT_SRCS "app_main.c" "app_wifi.c" "app_camera.c" "app_httpd.c" "app_mdns.c"
					"app_sd.c")
set(COMPONENT_ADD_INCLUDEDIRS "include")

set(COMPONENT_REQUIRES
    esp32-camera
    esp-face
    nvs_flash
    esp_http_server
    fb_gfx
    mdns
    fatfs
    )

set(COMPONENT_EMBED_FILES
        "www/index_ov2640.html.gz"
        "www/index_ov3660.html.gz"
        "www/index_ov5640.html.gz"
        "www/monitor.html.gz")

register_component()

增加了2个地方:
在这里插入图片描述
接下来看看 app_sd.h 的内容:

#ifndef _APP_SD_H_
#define _APP_SD_H_
#ifdef __cplusplus
extern "C" {
#endif

void SdCard_init();
void sd_write_jpg(const char *_jpg_buf, int _jpg_buf_len);
#ifdef __cplusplus
}
#endif

#endif

就2个函数,SdCard_init 在app_main() 调用:

... ...
#include "app_sd.h"

void app_main()
{
    SdCard_init();

    app_wifi_main();
    app_camera_main();
    app_httpd_main();
    app_mdns_main();
}

2.关键函数sd_write_jpg

这个函数是将jpeg图片的数据写入sd卡,我们在网页中看到的图像,无论是抓拍还是视频流,都是以jpeg的数据传输,这个数据是现成的,我们只要在适当的地方调用这个函数就可以了。主要流程:

fopen
fwrite
fclose

源码参考:

void sd_write_jpg(const char *_jpg_buf, int _jpg_buf_len)
{
    time_t now;
    char fname[64];
    struct tm timeinfo;
    
    time(&now);
    // Set timezone to China Standard Time
    setenv("TZ", "CST-8", 1);
    tzset();
    
    localtime_r(&now, &timeinfo);
    sprintf(fname,"/sdcard/%08x.jpg",(unsigned int)now);
    //strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
    ESP_LOGI(TAG,"fname:%s",fname);
    //上面先用系统时间生成文件名,然后开始写入流程
    FILE* f = fopen(fname, "w");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open file for writing");
        return;
    }
    //fprintf(f, "Hello file IO  %s!\n", card->cid.name);
        //进行写入数据
    fwrite(_jpg_buf,_jpg_buf_len,1,f);
    fclose(f);
    ESP_LOGI(TAG, "File written");
}

3.保存图片的时机

我们通过网页浏览器可以抓拍图片和观看实时视频,在抓拍图片时就可以保存下来。
在这里插入图片描述
抓拍图片对应的代码:
在这里插入图片描述
调用 sd_write_jpg 的代码:

......
        if (fb->format == PIXFORMAT_JPEG)
        {
            fb_len = fb->len;
            res = httpd_resp_send(req, (const char *)fb->buf, fb->len);
            //  写入到sd卡
            sd_write_jpg((const char *)fb->buf, fb->len);
            /
        }
......

4.编译测试工程

编译,烧录,运行:

I (3076) esp_netif_handlers: sta ip: 192.168.110.218, mask: 255.255.255.0, gw: 192.168.110.1
I (3076) camera wifi: got ip:192.168.110.218
I (3116) camera_httpd: Starting web server on port: '80'
I (3116) camera_httpd: Starting stream server on port: '81'
W (14056) wifi:<ba-add>idx:0 (ifx:0, 04:95:e6:8d:43:c1), tid:0, ssn:4, winSize:64
I (20096) app_sd: fname:/sdcard/00000012.jpg
I (20206) app_sd: File written
I (20206) camera_httpd: JPG: 37282B 347ms

在浏览器输入ip地址,点击 Get Still ,根据打印结果显示保存的图片名字为 00000012.jpg(我这里获取网络时间的功能没有做),用读卡器在电脑查看。确认保存图片正常。

5. 《app_sd.c》

/* SD card and FAT filesystem example.
   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <stdio.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_vfs_fat.h"
#include "driver/sdmmc_host.h"
#include "driver/sdspi_host.h"
#include "sdmmc_cmd.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_camera.h"

#include "app_sd.h"
#include <time.h>
#include "sdkconfig.h"
//#include "Jpeg2AVI.h"
#undef  CONFIG_ESP_SDCARD_STORAGE_ENABLED
#define     CONFIG_ESP_SDCARD_STORAGE_ENABLED       1
static const char *TAG = "app_sd";
FILE *fp_avi;
typedef struct {
    QueueHandle_t data_ready;
    QueueHandle_t fb_in;
    QueueHandle_t fb_out;

    SemaphoreHandle_t frame_ready;
    TaskHandle_t dma_filter_task;
} sd_state_t;
static sdmmc_card_t* card;
//sd_state_t* sd_state = NULL;
QueueHandle_t sd_ready;
#ifdef USE_SPI_MODE
// Pin mapping when using SPI mode.
// With this mapping, SD card can be used both in SPI and 1-line SD mode.
// Note that a pull-up on CS line is required in SD mode.
#define PIN_NUM_MISO 2
#define PIN_NUM_MOSI 15
#define PIN_NUM_CLK  14
#define PIN_NUM_CS   13
#endif //USE_SPI_MODE


void sd_write_jpg(const char *_jpg_buf, int _jpg_buf_len)
{
    time_t now;
    char fname[64];
    struct tm timeinfo;
    
    time(&now);
    // Set timezone to China Standard Time
    setenv("TZ", "CST-8", 1);
    tzset();
    
    localtime_r(&now, &timeinfo);
    sprintf(fname,"/sdcard/%08x.jpg",(unsigned int)now);
    //strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
    ESP_LOGI(TAG,"fname:%s",fname);
    /写入    
    FILE* f = fopen(fname, "w");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open file for writing");
        return;
    }
    //fprintf(f, "Hello file IO  %s!\n", card->cid.name);
        //进行写入数据
    fwrite(_jpg_buf,_jpg_buf_len,1,f);
    fclose(f);
    ESP_LOGI(TAG, "File written");
}
static void Sdcard_task(void *pvParameters)
{

        while (true){
           
            vTaskDelay((5*1000) / portTICK_PERIOD_MS);
        }
     vTaskDelete(NULL);
}
void SdCard_init(void)
{
#if CONFIG_ESP_SNTP_ENABLED
    {
    time_t now;
    char strftime_buf[64];
    struct tm timeinfo;

    time(&now);
    // Set timezone to China Standard Time
    setenv("TZ", "CST-8", 1);
    tzset();

    localtime_r(&now, &timeinfo);
    strftime(strftime_buf, sizeof(strftime_buf), "%Y-%m-%d %H:%M:%S", &timeinfo);
    ESP_LOGI(TAG, "The current date/time is: %s", strftime_buf);
    }
#endif
#if CONFIG_ESP_SDCARD_STORAGE_ENABLED
    ESP_LOGI(TAG, "Initializing SD card");

#ifndef USE_SPI_MODE
    ESP_LOGI(TAG, "Using SDMMC peripheral");
    sdmmc_host_t host = SDMMC_HOST_DEFAULT();

    // This initializes the slot without card detect (CD) and write protect (WP) signals.
    // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
    sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();

    // To use 1-line SD mode, uncomment the following line:
    // slot_config.width = 1;

    // GPIOs 15, 2, 4, 12, 13 should have external 10k pull-ups.
    // Internal pull-ups are not sufficient. However, enabling internal pull-ups
    // does make a difference some boards, so we do that here.
    gpio_set_pull_mode(15, GPIO_PULLUP_ONLY);   // CMD, needed in 4- and 1- line modes
    gpio_set_pull_mode(2, GPIO_PULLUP_ONLY);    // D0, needed in 4- and 1-line modes
    gpio_set_pull_mode(4, GPIO_PULLUP_ONLY);    // D1, needed in 4-line mode only
    gpio_set_pull_mode(12, GPIO_PULLUP_ONLY);   // D2, needed in 4-line mode only
    gpio_set_pull_mode(13, GPIO_PULLUP_ONLY);   // D3, needed in 4- and 1-line modes

#else
    ESP_LOGI(TAG, "Using SPI peripheral");--

    sdmmc_host_t host = SDSPI_HOST_DEFAULT();
    sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
    slot_config.gpio_miso = PIN_NUM_MISO;
    slot_config.gpio_mosi = PIN_NUM_MOSI;
    slot_config.gpio_sck  = PIN_NUM_CLK;
    slot_config.gpio_cs   = PIN_NUM_CS;
    // This initializes the slot without card detect (CD) and write protect (WP) signals.
    // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
#endif //USE_SPI_MODE

    // Options for mounting the filesystem.
    // If format_if_mount_failed is set to true, SD card will be partitioned and
    // formatted in case when mounting fails.
    esp_vfs_fat_sdmmc_mount_config_t mount_config = {
        .format_if_mount_failed = false,// 2 false
        .max_files = 5,
        .allocation_unit_size = 16 * 1024
    };

    // Use settings defined above to initialize SD card and mount FAT filesystem.
    // Note: esp_vfs_fat_sdmmc_mount is an all-in-one convenience function.
    // Please check its source code and implement error recovery when developing
    // production applications.
    esp_err_t ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card);

    if (ret != ESP_OK) {
        if (ret == ESP_FAIL) {
            ESP_LOGE(TAG, "Failed to mount filesystem. "
                "If you want the card to be formatted, set format_if_mount_failed = true.");
        } else {
            ESP_LOGE(TAG, "Failed to initialize the card (%s). "
                "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
        }
        return;
    }

    // Card has been initialized, print its properties
    sdmmc_card_print_info(stdout, card);
/*
    fp_avi = fopen("/sdcard/sample.avi","wb");
    if(fp_avi == NULL){
        ESP_LOGE(TAG, "Failed to open file for writing");
        return;
    }    
    jpeg2avi_start(fp_avi);
    */
    //xTaskCreate(&Sdcard_task, "Sdcard_task", 4096, NULL, 5, NULL);
#endif
}

6.闪光灯和sd卡引脚冲突

闪光灯控制脚和 HS2_DATA1复用了,只能舍弃闪光灯功能:
在这里插入图片描述
板上有个0603的LED,使用GPIO33,可以配置为33:
在这里插入图片描述

7.完整工程源码

https://gitee.com/huangweide001/esp32_test/tree/master/camera_web_server_pic

posted @ 2022-09-03 16:50  汉塘阿德  阅读(133)  评论(0编辑  收藏  举报  来源