ESP32S3将摄像头映射到LCD屏
概述
该文将讲解ESP32S3如何将camera捕捉的图片实时映射到LCD屏,做成监控的类似效果。
硬件准备
嘉立创实战派S3开发板
camera型号:GC0308(30万像素)
显示屏:ST7789(2.0寸 分辨率320*240)
步骤
- 初始化LCD屏
- 初始化camera
- 创建camera任务捕捉实时帧画面,并从任务队列将画面帧传递
- 创建LCD任务,接受帧画面内存并显示在LCD屏上
程序实现
此次实现只展示关键步骤,不展示完整代码
初始化lcd代码
static const char *TAG = "esp32_s3_szp";/*** @brief 初始化并创建一个新的显示面板** 此函数负责初始化显示面板的硬件接口,包括SPI总线、面板IO和LCD驱动,并配置显示参数。** @return 返回操作结果。如果成功,返回ESP_OK;否则返回错误代码。*/
esp_err_t bsp_display_new(void)
{esp_err_t ret = ESP_OK;// 初始化显示屏亮度,失败则返回错误ESP_RETURN_ON_ERROR(bsp_display_brightness_init(), TAG, "Brightness init failed");// 日志输出,表示正在初始化SPI总线ESP_LOGD(TAG, "Initialize SPI bus");// SPI总线配置const spi_bus_config_t buscfg = {.sclk_io_num = BSP_LCD_SPI_CLK,.mosi_io_num = BSP_LCD_SPI_MOSI,.miso_io_num = GPIO_NUM_NC,.quadwp_io_num = GPIO_NUM_NC,.quadhd_io_num = GPIO_NUM_NC,.max_transfer_sz = BSP_LCD_H_RES * BSP_LCD_V_RES * sizeof(uint16_t),};// 初始化SPI总线,失败则返回错误ESP_RETURN_ON_ERROR(spi_bus_initialize(BSP_LCD_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI init failed");// 日志输出,表示正在安装面板IOESP_LOGD(TAG, "Install panel IO");// 面板IO配置const esp_lcd_panel_io_spi_config_t io_config = {.dc_gpio_num = BSP_LCD_DC,.cs_gpio_num = BSP_LCD_SPI_CS,.pclk_hz = BSP_LCD_PIXEL_CLOCK_HZ,.lcd_cmd_bits = LCD_CMD_BITS,.lcd_param_bits = LCD_PARAM_BITS,.spi_mode = 2,.trans_queue_depth = 10,};// 创建新的面板IO,失败则跳转到错误处理ESP_GOTO_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)BSP_LCD_SPI_NUM, &io_config, &io_handle), err, TAG, "New panel IO failed");// 日志输出,表示正在安装LCD驱动ESP_LOGD(TAG, "Install LCD driver");// 面板配置const esp_lcd_panel_dev_config_t panel_config = {.reset_gpio_num = BSP_LCD_RST,.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,.bits_per_pixel = BSP_LCD_BITS_PER_PIXEL,};// 创建新的面板,失败则跳转到错误处理ESP_GOTO_ON_ERROR(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle), err, TAG, "New panel failed");// 重置面板esp_lcd_panel_reset(panel_handle);// 关闭CS信号lcd_cs(0);// 初始化面板esp_lcd_panel_init(panel_handle);// 反转颜色esp_lcd_panel_invert_color(panel_handle, true);// 显示翻转esp_lcd_panel_swap_xy(panel_handle, true); // 显示翻转// 镜像esp_lcd_panel_mirror(panel_handle, true, false); // 镜像return ret;err:// 如果存在面板句柄,则删除面板if (panel_handle){esp_lcd_panel_del(panel_handle);}// 如果存在IO句柄,则删除IOif (io_handle){esp_lcd_panel_io_del(io_handle);}// 释放SPI总线spi_bus_free(BSP_LCD_SPI_NUM);return ret;
}/*** @brief 设置LCD屏幕颜色 用于测试屏幕是否正常 填充全色 可不调用** 设置LCD屏幕为指定的颜色。** @param color 要设置的颜色值,通常是一个16位的RGB565颜色值。*/
void lcd_set_color(int color)
{// 在堆上分配内存用于存储颜色值uint16_t *buffer = (uint16_t *)heap_caps_malloc(BSP_LCD_H_RES * sizeof(uint16_t), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);// 检查内存分配是否成功if (NULL == buffer){// 如果内存分配失败,输出错误日志ESP_LOGE(TAG, "Memory for bitmap is not enough");}else{// 将分配的内存填充为指定的颜色值 存储一行的颜色值for (size_t i = 0; i < BSP_LCD_H_RES; i++){buffer[i] = color;}// 将填充好的颜色值逐行绘制到LCD屏幕上 逐行绘制for (int y = 0; y < BSP_LCD_V_RES; y++){esp_lcd_panel_draw_bitmap(panel_handle, 0, y, BSP_LCD_H_RES, y + 1, buffer);}// 释放分配的内存heap_caps_free(buffer);}
}/*** @brief 在LCD上显示图片 用于功能测试,可不调用,与本实验无关** 在指定的LCD区域显示一张图片。** @param x_start 图片显示的起始x坐标* @param y_start 图片显示的起始y坐标* @param x_end 图片显示的结束x坐标* @param y_end 图片显示的结束y坐标* @param gImage 图片数据指针*/
void lcd_draw_pictrue(int x_start, int y_start, int x_end, int y_end, const unsigned char *gImage)
{// 计算像素大小size_t pixels_size = (x_end - x_start) * (y_end - y_start) * 2;// 在堆中分配内存uint16_t *pixels = (uint16_t *)heap_caps_malloc(pixels_size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);// 检查内存分配是否成功if (NULL == pixels){// 内存不足时打印日志ESP_LOGE(TAG, "Memory for bitmap is not enough");return;}// 将图像数据复制到分配的内存中memcpy(pixels, gImage, pixels_size);// 在LCD面板上绘制位图esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_end, y_end, (uint16_t *)pixels);// 释放内存heap_caps_free(pixels);
}/*** @brief 初始化液晶屏** 该函数用于初始化液晶屏,包括创建新的显示设备、设置背景颜色、打开液晶屏显示以及打开背光。*/
void lcd_init(void)
{// 初始化显示设备bsp_display_new();// 设置液晶屏颜色为白色lcd_set_color(0xffff); // 打开液晶屏显示// 需引用以下头文件// #include "esp_lcd_types.h"// #include "esp_lcd_panel_io.h"// #include "esp_lcd_panel_vendor.h"// #include "esp_lcd_panel_ops.h"esp_lcd_panel_disp_on_off(panel_handle, true); // 打开背光bsp_display_backlight_on();
}
初始化camera
void bsp_camera_init(void)
{//DVP_PWDN 是一个低电平有效的控制引脚,用于开启或关闭传感器内部的 DVP 接口模块//低电平(LOW):DVP 接口模块通电,允许数据传输,传感器正常输出图像数据。dvp_pwdn(0);// 配置相机参数camera_config_t config;config.ledc_channel = LEDC_CHANNEL_0; // LEDC通道选择 用于生成XCLK时钟 但是S3不用config.ledc_timer = LEDC_TIMER_0; // LEDC timer选择 用于生成XCLK时钟 但是S3不用config.pin_d0 = CAMERA_PIN_D0; // 数据引脚D0config.pin_d1 = CAMERA_PIN_D1; // 数据引脚D1config.pin_d2 = CAMERA_PIN_D2; // 数据引脚D2config.pin_d3 = CAMERA_PIN_D3; // 数据引脚D3config.pin_d4 = CAMERA_PIN_D4; // 数据引脚D4config.pin_d5 = CAMERA_PIN_D5; // 数据引脚D5config.pin_d6 = CAMERA_PIN_D6; // 数据引脚D6config.pin_d7 = CAMERA_PIN_D7; // 数据引脚D7config.pin_xclk = CAMERA_PIN_XCLK; // 时钟引脚config.pin_pclk = CAMERA_PIN_PCLK; // 像素时钟引脚config.pin_vsync = CAMERA_PIN_VSYNC; // 垂直同步引脚config.pin_href = CAMERA_PIN_HREF; // 水平参考引脚config.pin_sccb_sda = -1; // SCCB数据线SDA未使用config.pin_sccb_scl = CAMERA_PIN_SIOC; // SCCB时钟线SCLconfig.sccb_i2c_port = 0; // I2C端口config.pin_pwdn = CAMERA_PIN_PWDN; // 电源引脚config.pin_reset = CAMERA_PIN_RESET; // 复位引脚config.xclk_freq_hz = XCLK_FREQ_HZ; // 时钟频率config.pixel_format = PIXFORMAT_RGB565; // 像素格式设置为RGB565config.frame_size = FRAMESIZE_QVGA; // 设置为QVGA分辨率 320*240config.jpeg_quality = 12; // JPEG质量config.fb_count = 2; // 2帧缓存config.fb_location = CAMERA_FB_IN_PSRAM; // 帧缓存位置config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; // 捕获模式// 初始化相机esp_err_t err = esp_camera_init(&config);if (err != ESP_OK){// 如果初始化失败,则打印错误信息并返回ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);return;}// 获取相机传感器sensor_t *s = esp_camera_sensor_get();if (s->id.PID == GC0308_PID){// 如果相机传感器是GC0308,则设置水平镜像s->set_hmirror(s, 1); // 1镜像 0不镜像}
}
创建camera任务和lcd任务
static QueueHandle_t xQueueLCDFrame = NULL; //帧数据缓存队列
/*** @brief 处理LCD显示任务的函数** 从队列中接收一帧图像数据,并将其显示在LCD屏幕上。** @param arg 参数指针,未使用*/
static void task_process_lcd(void *arg)
{camera_fb_t *frame = NULL;while (true){if (xQueueReceive(xQueueLCDFrame, &frame, portMAX_DELAY)) // 接受到一帧的队列消息{esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, frame->width, frame->height, (uint16_t *)frame->buf); // 显示图像数据到液晶屏上esp_camera_fb_return(frame); //清楚缓存帧}}
}/*** @brief 处理摄像头数据的任务函数** 该函数负责从摄像头获取图像数据,并将图像数据发送到队列中供后续处理。** @param arg 任务参数,此处未使用*/
static void task_process_camera(void *arg)
{while (true){camera_fb_t *frame = esp_camera_fb_get(); // 从摄像头获取一帧图像if (frame)xQueueSend(xQueueLCDFrame, &frame, portMAX_DELAY); // 发送图像数据}
}/*** @brief 初始化相机和LCD显示功能** 此函数用于初始化相机和LCD显示功能,创建必要的队列和任务。** 具体步骤如下:* 1. 创建一个队列xQueueLCDFrame,用于存储指向camera_fb_t结构的指针,队列大小为2。* 2. 创建一个任务task_process_camera,绑定到核心1上,用于处理相机数据。任务栈大小为3KB,优先级为5。* 3. 创建一个任务task_process_lcd,绑定到核心0上,用于处理LCD显示。任务栈大小为4KB,优先级为5。*/
void app_camera_lcd(void)
{// 创建一个队列,用于存储指向camera_fb_t结构的指针,队列大小为2xQueueLCDFrame = xQueueCreate(2, sizeof(camera_fb_t *));// 创建一个任务,绑定到核心1上,用于处理相机数据xTaskCreatePinnedToCore(task_process_camera, "task_process_camera", 3 * 1024, NULL, 5, NULL, 1);// 创建一个任务,绑定到核心0上,用于处理LCD显示xTaskCreatePinnedToCore(task_process_lcd, "task_process_lcd", 4 * 1024, NULL, 5, NULL, 0);
}
main函数
void app_main(void)
{// 初始化I2C接口bsp_i2c_init();// 初始化PCA9557 I/O扩展器pca9557_init();// 初始化LCD显示屏lcd_init();// 绘制图片,但此行代码被注释掉了,不会执行// lcd_draw_pictrue(0,0,320,240,gImage_su7);// 初始化摄像头bsp_camera_init();// 应用摄像头到LCD显示屏的函数app_camera_lcd();
}
实验结果
正确实现的话现在的效果是像相机的自拍器