数码之家

 找回密码
 立即注册

QQ登录

只需一步,快速开始

微信登录

微信扫一扫,快速登录

搜索
查看: 77|回复: 1

《DNESP32S3使用指南-IDF版_V1.6》第三十六章 照相机实验

[复制链接]
发表于 5 天前 | 显示全部楼层 |阅读模式
第三十六章 照相机实验

本章我们结合前面的摄像头实验,实现一个简单的照相机功能。本章分为如下几个小节:
36.1 OV5640CAMERA模块简介
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_SETLCD_DC相连)
PWR- IO1_3XL9555)
RST- IO1_2XL9555)
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_5XL9555)
PWDN-IO0_4XL9555)
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兼容的图像传感器驱动程序。此外,它还提供了一些工具,允许将捕获的帧数据转换为更常见的BMPJPEG格式。要使用此功能,需要导入必要的头文件:
#include "esp_camera.h"
接下来,作者将介绍一些常用的ESP32-S3中的CAMERA函数,这些函数的描述及其作用如下:
1,初始化摄像头驱动
该函数用于检测并配置摄像头,其函数原型如下所示:
esp_err_t esp_camera_init(const camera_config_t *config);
该函数的形参描述,如下表所示:
形参
描述
config
这是指向摄像机配置参数的指针
表36.3.2.1 函数esp_camera_init ()形参描述
该函数的返回值描述,如下表所示:
返回值
描述
ESP_OK
返回:0,表示配置成功
表36.3.2.2 函数esp_camera_init ()
返回值描述该函数使用camera_config_t类型的结构体变量传入,该结构体的定义如下所示:
结构体
成员变量
参数介绍
camera_config_t
.pin_pwdn
相机电源下降线的GPIO引脚
.pin_reset
摄像机复位线GPIO引脚
.pin_xclk
相机XCLK线的GPIO引脚
.pin_sccb_sda
相机SDA线的GPIO引脚
.pin_sccb_scl
相机SCL线的GPIO引脚
.pin_d0~pin_d7
摄像机D0~D7线的GPIO引脚
.pin_vsync
摄像机VSYNC线的GPIO引脚
.pin_href
摄像机HREF线的GPIO引脚
.pin_pclk
摄像机PCLK线的GPIO引脚
.xclk_freq_hz
用于生成XCLKLEDC计时器
.ledc_timer
用于生成XCLKLEDC通道
.ledc_channel
像素数据格式
PIXFORMAT_+YUV422|
GRAYSCALE|
RGB565|
JPEG
.fb_location
输出图像大小
FRAMESIZE_+QVGA|
CIF|
VGA|
SVGA|
XGA|
SXGA|
UXGA
.pixel_format
JPEG输出的质量。0-63更低意味着更高的质量
.frame_size
要分配的帧缓冲区数。如果不止一个,则将获取每个帧(双倍速度)
.jpeg_quality
何时应填充缓冲区
.fb_count
相机电源下降线的GPIO引脚
.grab_mode
摄像机复位线GPIO引脚
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 ()形参描述
该函数的返回值描述,如下表所示:
返回值
描述
NULL
返回:0,即空
&s_state->sensor
指向结构体参数camera_state_t
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路径下,目标文件的总个数
* @param       path : 路径
* @retval      总有效文件数
*/
uint16_t pic_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, "img open err\r\n");
            }
/* 写入头数据 */
            f_write(fftemp, (const void *)lcd_buf, sizeof(lcd_buf), &writelen);
            if (writelen != sizeof(lcd_buf))
            {
                ESP_LOGE(TAG, "img Write err");
            }
            else
            {
                ESP_LOGI(TAG, "write buff len %d byte", writelen);
                pictureNumber++;
            }
            f_close(fftemp);
            free(fftemp);
        }
    }
}
上述代码通过任务调度的方式,实现申请内存、分配内存以及读取指定目录下的的文件的操作。
main函数代码如下:
/**
* @brief       程序入口
* @param      
* @retval      
*/
void app_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, "SD Card 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, "CAMERA TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    /* 初始化摄像头 */
    while (camera_init())
    {
        lcd_show_string(30, 110, 200, 16, 16, "CAMERA Fail!", BLUE);
        vTaskDelay(500);
    }
    rgb565 = malloc(240 * 320 * 2);
    if (NULL == rgb565)
    {
        ESP_LOGE(TAG, "can't alloc 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, "Get frame failed");
        }
        i = 0;
        pic = NULL;
        vTaskDelay(pdMS_TO_TICKS(1));
    }
    free(rgb565);
}
该函数完成对各相关硬件的初始化,然后检测摄像头,初始化摄像头为RGB565模式,显示采集到的图像到LCD上面,实现对图像进行预览。进入主循环以后,按KEY0按键,可以实现拍照。
至此照相机实验代码编写完成。
36.4 下载验证
程序下载到开发板后,LCD显示屏不断更新摄像头输出的图像数据,如下图所示。
图36.4.1 LCD显示效果图

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册 微信登录

x
您需要登录后才可以回帖 登录 | 立即注册 微信登录

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2025-8-6 09:32 , Processed in 0.156001 second(s), 9 queries , Redis On.

Powered by Discuz!

© 2006-2025 MyDigit.Net

快速回复 返回顶部 返回列表