《ESP32-S3使用指南—IDF版 V1.6》第三十六章照相机实验

第三十六章照相机实验

本章我们结合前面的摄像头实验,实现一个简单的照相机功能。本章分为如下几个小节:

36.1 OV5640和CAMERA模块简介

36.2 硬件设计

36.3 程序设计

36.4 下载验证

36.1 OV5640CAMERA模块简介

关于这部分的内容前一章节已经做出了详细介绍,请读者们回顾36.1小节的内容,笔者在此不再赘述。

36.2 硬件设计

36.2.1.例程功能

本章实验功能简介:程序下载完成,摄像头的图像数据在SPILCD显示屏上显示。

36.2.2.硬件资源

1. XL9555

IIC_SDA-IO41

IIC_SCL-IO42

2. SPILCD

CS-IO21

SCK-IO12

SDA-IO11

DC-IO40(在P5端口,使用跳线帽将IO_SET和LCD_DC相连)

PWR- IO1_3(XL9555)

RST- IO1_2(XL9555)

3. CAMERA

OV_SCL-IO38

OV_SDA- IO 39

VSYNC- IO 47

HREF- IO 48

PCLK- IO 45

D0- IO 4

D1- IO 5

D2- IO 6

D3- IO 7

D4- IO 15

D5- IO 16

D6- IO 17

D7- IO 18

RESET-IO0_5(XL9555)

PWDN-IO0_4(XL9555)

36.2.3.原理图

CAMERA接口与ESP32-S3的连接关系,如下图所示:

图36.2.3.1 CAMERA接口与ESP32-S3的连接电路图

36.3 程序设计

36.3.1 程序流程图

程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。下面看看本实验的程序流程图:

图36.3.1.1 CAMERA_PHOTOGRAPH实验程序流程图

36.3.2 CAMERA_PHOTOGRAPH函数解析

本章实验要使用到乐鑫官方的esp32-camera驱动库,此驱动库承载ESP32系列Soc兼容的图像传感器驱动程序。此外,它还提供了一些工具,允许将捕获的帧数据转换为更常见的BMP和JPEG格式。要使用此功能,需要导入必要的头文件:

#include"esp_camera.h"

接下来,作者将介绍一些常用的ESP32-S3中的CAMERA函数,这些函数的描述及其作用如下:

1,初始化摄像头驱动

该函数用于检测并配置摄像头,其函数原型如下所示:

esp_err_tesp_camera_init(const camera_config_t *config);

该函数的形参描述,如下表所示:

表36.3.2.1 函数esp_camera_init ()形参描述

该函数的返回值描述,如下表所示:

表36.3.2.2 函数esp_camera_init ()

返回值描述该函数使用camera_config_t类型的结构体变量传入,该结构体的定义如下所示:

表36.3.2.3 camera_config_t结构体参数值描述

完成上述结构体参数配置之后,可以将结构传递给 esp_camera_init () 函数,用以实例化CAMERA。

2,获取摄像头图像传感器

该函数用于获取指向图像传感器控制结构的指针,其函数原型如下所示:

sensor_t *esp_camera_sensor_get(void);

该函数的形参描述,如下表所示:

表36.3.2.4 函数esp_camera_sensor_get ()形参描述

该函数的返回值描述,如下表所示:

表36.3.2.5 函数esp_camera_sensor_get()返回值描述

36.3.3 CAMERA_PHOTOGRAPH驱动解析

在IDF版的25_2_camera_photograph例程中,作者在25_2_camera_photograph\components\decoder_ijg路径下新增了一个JPEG库用户文件。由于,25_1_ camera实验与25_2_camera_photograph用到的驱动一样,笔者在此也不再详细赘述,请读者们回顾第三十五章节的相关内容。

36.3.4 CMakeLists.txt文件

打开本实验BSP下的CMakeLists.txt文件,其内容如下所示:

set(src_dirs

            CAMERA

            IIC

            LCD

            LED

            SPI

            XL9555)

set(include_dirs

            CAMERA

            IIC

            LCD

            LED

            SPI

            XL9555)

set(requires

            driver

            esp_lcd

            esp32-camera)

idf_component_register(SRC_DIRS${src_dirs}

INCLUDE_DIRS ${include_dirs}REQUIRES ${requires})

component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)

上述的红色CAMERA驱动以及esp_ camera依赖库需要由开发者自行添加,以确保CAMERA驱动能够顺利集成到构建系统中。这一步骤是必不可少的,它确保了CAMERA驱动的正确性和可用性,为后续的开发工作提供了坚实的基础。

36.3.5 实验应用代码

打开main/main.c文件,该文件定义了工程入口函数,名为app_main。该函数代码如下。main.c函数我们在之前摄像头实验的基本上进行改动,首先我们要为图片分配一个与图片文件夹下名字不重复的文件名,我们复用FATFS的接口,设计如下:

/**

* @brief      得到path路径下,目标文件的总个数

@parampath : 路径

* @retval     总有效文件数

*/

uint16_tpic_get_tnum(char *path)

{

    uint8_t res;

    uint16_t rval = 0;

    FF_DIR tdir;                                         /* 临时目录 */

    FILINFO *tfileinfo;                                 /* 临时文件信息 */

    tfileinfo = (FILINFO*)malloc(sizeof(FILINFO));    /* 申请内存 */

    res =f_opendir(&tdir, (const TCHAR*)path);           /* 打开目录 */

    if (res == FR_OK&& tfileinfo)

    {

        while (1)                                         /* 查询总的有效文件数 */

        {

            res =f_readdir(&tdir, tfileinfo);        /* 读取目录下的一个文件 */

         /* 错误了/到末尾了,退出 */

            if (res != FR_OK|| tfileinfo->fname[0] == 0)break;

            res =exfuns_file_type(tfileinfo->fname);

            if ((res & 0X0F) != 0X00)               /* 取低四位,看看是不是图片文件 */

            {

                rval++;                                   /* 有效文件数增加1 */

            }

        }

    }

    free(tfileinfo);                                     /* 释放内存 */

    return rval;

}

通过以上程序,可以生成一个与当前文件夹下图片不重名的文件名字符串,并传给针对应的缓冲区。

/**

* @brief      task3

* @param      pvParameters : 传入参数(未用到)

* @retval     无

*/

void task3(void *pvParameters)

{

    pvParameters =pvParameters;

    char file_name[30];

    uint32_t pictureNumber = 0;

    uint8_t res = 0;

    size_t writelen = 0;

    FIL *fftemp;

    res =exfuns_init();                            /* 为fatfs相关变量申请内存 */

    pictureNumber =pic_get_tnum("0:/PICTURE");      /* 得到总有效文件数 */

    pictureNumber =pictureNumber + 1;

    while (1)

    {

        xSemaphoreTake(BinarySemaphore,portMAX_DELAY);/* 获取二值信号量 */


        /* SD卡挂载了,才能拍照 */

        if (sd_check_en== 1)

        {

            sprintf(file_name, "0:/PICTURE/img%ld.jpg",pictureNumber);

/* 分配内存 */

            fftemp = (FIL *)malloc(sizeof(FIL));

            res =f_open(fftemp,

(const TCHAR *)file_name,

FA_WRITE |

FA_CREATE_NEW);   /* 尝试打开 */

            if (res != FR_OK)

            {

                ESP_LOGE(TAG, "imgopen err\r\n");

            }

/* 写入头数据 */

            f_write(fftemp, (const void *)lcd_buf, sizeof(lcd_buf), &writelen);

            if (writelen!= sizeof(lcd_buf))

            {

                ESP_LOGE(TAG, "imgWrite err");

            }

            else

            {

                ESP_LOGI(TAG, "writebuff len %d byte", writelen);

                pictureNumber++;

            }

            f_close(fftemp);

            free(fftemp);

        }

    }

}

上述代码通过任务调度的方式,实现申请内存、分配内存以及读取指定目录下的的文件的操作。

main函数代码如下:

/**

* @brief      程序入口

* @param      无

* @retval     无

*/

voidapp_main(void)

{

    unsigned long i = 0;

    unsigned long j = 0;

    uint8_t key = 0;

    esp_err_t ret;


    ret =nvs_flash_init();               /* 初始化NVS */

if (ret ==ESP_ERR_NVS_NO_FREE_PAGES ||

       ret == ESP_ERR_NVS_NEW_VERSION_FOUND)

    {

        ESP_ERROR_CHECK(nvs_flash_erase());

        ret =nvs_flash_init();

    }

    led_init();                             /* 初始化LED*/

    i2c0_master =iic_init(I2C_NUM_0);      /* 初始化IIC0 */

    spi2_init();                            /* 初始化SPI2 */

    xl9555_init(i2c0_master);            /* 初始化XL9555 */

    lcd_init();                             /* 初始化LCD*/

    while (sd_spi_init())                 /* 检测不到SD卡 */

    {

        lcd_show_string(30, 50, 200, 16, 16, "SDCard Failed!", RED);

        vTaskDelay(200);

        lcd_fill(30, 50, 200 + 30, 50 + 16, WHITE);

        vTaskDelay(200);

        sd_check_en = 0;

    }

    sd_check_en = 1;

    lcd_show_string(30, 50, 200, 16, 16, "ESP32", RED);

    lcd_show_string(30, 70, 200, 16, 16, "CAMERATEST", RED);

    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);

    /* 初始化摄像头 */

    while (camera_init())

    {

        lcd_show_string(30, 110, 200, 16, 16, "CAMERAFail!", BLUE);

        vTaskDelay(500);

    }

    rgb565 =malloc(240 * 320 * 2);

    if (NULL ==rgb565)

    {

        ESP_LOGE(TAG, "can'talloc memory for rgb565 buffer");

    }

    lcd_clear(BLACK);

    BinarySemaphore =xSemaphoreCreateBinary();

    /* 创建任务3 */

    xTaskCreatePinnedToCore((TaskFunction_t)task3,            /* 任务函数 */

                            (const char*    )"task3",         /* 任务名称 */

                            (uint16_t       )TASK3_STK_SIZE, /* 任务堆栈大小 */

                            (void*          )NULL,        /* 传入给任务函数的参数 */

                            (UBaseType_t    )TASK3_PRIO,     /* 任务优先级 */

                            (TaskHandle_t*  )&Task3Task_Handler,/* 任务句柄 */

                            (BaseType_t     ) 0);       /* 该任务哪个内核运行 */

    while (1)

    {

        key =xl9555_key_scan(0);

        camera_fb_t *pic =esp_camera_fb_get();

        if (pic)

        {

            mjpegdraw(pic->buf, pic->len, (uint8_t *)rgb565, NULL);

            lcd_set_window(0, 0, 0 + pic->width - 1, 0 + pic->height- 1);

            if (key ==KEY0_PRES)

            {

                /* lcd_buf存储摄像头整一帧RGB数据 */

                for (j = 0; j < pic->width * pic->height; j++)

                {

                    lcd_buf[2 * j] = (pic->buf[2 * i]) ;

                    lcd_buf[2 * j + 1] =  (pic->buf[2 * i + 1]);

                    i ++;

                }

                xSemaphoreGive(BinarySemaphore);             /* 释放二值信号量 */

            }

            /* 处理SD卡释放挂载 */

            if (sd_check_en== 1)

            {

                if (sdmmc_get_status(card) !=ESP_OK)

                {

                    sd_check_en = 0;

                }

            }

            else

            {

                if (sd_spi_init() ==ESP_OK)

                {

                    if (sdmmc_get_status(card) ==ESP_OK)

                    {

                        sd_check_en = 1;

                    }

                }

            }


            /* 例如:96*96*2/1536 = 12;分12次发送RGB数据 */

            for(j = 0; j < (pic->width * pic->height* 2 / LCD_BUF_SIZE); j++)

            {

                /*&lcd_buf[j * LCD_BUF_SIZE] 偏移地址发送数据 */

                lcd_write_data(&rgb565[j *LCD_BUF_SIZE] , LCD_BUF_SIZE);

            }

            esp_camera_fb_return(pic);

        }

        else

        {

            ESP_LOGE(TAG, "Getframe failed");

        }

        i = 0;

        pic = NULL;

        vTaskDelay(pdMS_TO_TICKS(1));

    }

    free(rgb565);

}

该函数完成对各相关硬件的初始化,然后检测摄像头,初始化摄像头为RGB565模式,显示采集到的图像到LCD上面,实现对图像进行预览。进入主循环以后,按KEY0按键,可以实现拍照。

至此照相机实验代码编写完成。

36.4 下载验证

程序下载到开发板后,LCD显示屏不断更新摄像头输出的图像数据,如下图所示。

图36.4.1 LCD显示效果图
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容