第三十六章 照相机实验
本章我们结合前面的摄像头实验,实现一个简单的照相机功能。本章分为如下几个小节: 36.1 OV5640和CAMERA模块简介 36.2 硬件设计 36.3 程序设计 36.4 下载验证
36.1 OV5640和CAMERA模块简介 关于这部分的内容前一章节已经做出了详细介绍,请读者们回顾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_t esp_camera_init(const camera_config_t *config); 该函数的形参描述,如下表所示: 表36.3.2.1 函数esp_camera_init ()形参描述 该函数的返回值描述,如下表所示: 表36.3.2.2 函数esp_camera_init () 返回值描述该函数使用camera_config_t类型的结构体变量传入,该结构体的定义如下所示: | | | | | | | | | | | | | | | | | | | | | | | | | | | 像素数据格式 PIXFORMAT_+YUV422| GRAYSCALE| RGB565| JPEG | | 输出图像大小 FRAMESIZE_+QVGA| CIF| VGA| SVGA| XGA| SXGA| UXGA | | | | 要分配的帧缓冲区数。如果不止一个,则将获取每个帧(双倍速度) | | | | | | |
表36.3.2.3 camera_config_t结构体参数值描述 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的接口,设计如下: /** * @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显示效果图
|