开始前适用开发板:ESP32 系列 · 开发框架:ESP-IDF
文档类型:早期 Korvo S3 摄像头+LVGL 笔记。 请优先阅读:Korvo camera_lvgl_display · P4C5 camera_lvgl_display
1. 项目概述
本教程将指导你如何使用ESP32-S3开发板配合摄像头模块和LCD显示屏,实现一个简单的实时摄像头图像显示系统。项目基于ESP-IDF框架和LVGL图形库开发,适合初学者学习ESP32-S3的摄像头和显示功能。
2. 硬件准备
- ESP32-S3开发板
- 摄像头模块(OV系列)
- LCD显示屏(支持SPI接口)
- 触摸屏模块(可选,CST816S)
- IO扩展芯片(可选,TCA9554/TCA9554A)
3. 引脚连接
3.1 摄像头引脚定义
摄像头引脚连接:
- XCLK: GPIO40
- D7: GPIO39
- D6: GPIO41
- D5: GPIO42
- D4: GPIO12
- D3: GPIO3
- D2: GPIO14
- D1: GPIO47
- D0: GPIO13
- VSYNC: GPIO21
- HREF: GPIO38
- PCLK: GPIO11
3.2 LCD显示屏引脚定义
LCD显示屏引脚连接:
- SCLK: GPIO1
- MOSI: GPIO0
- DC: GPIO2
- CS: GPIO46
3.3 触摸屏引脚定义
触摸屏I2C接口:
- SCL: GPIO18
- SDA: GPIO17
4. 软件环境配置
4.1 依赖组件
项目依赖以下组件,在idf_component.yml中定义:
dependencies:
idf: ">=4.4"
lvgl/lvgl: "~8.4.0"
esp_lcd_touch_cst816s: "^1.0"
esp_io_expander_tca9554: "^2.0.0"
4.2 项目文件结构
app_camera.c/h: 摄像头初始化和配置ksdiy_lvgl_port.c/h: LVGL与ESP32-S3的接口层page_cam.c/h: 摄像头图像显示页面app_main.c: 主程序入口
5. 代码详解
5.1 摄像头初始化 (app_camera.c)
摄像头初始化是整个项目的基础,我们需要配置摄像头的各种参数:
void app_camera_init()
{
// 摄像头配置结构体
static camera_config_t camera_config = {
// 引脚配置
.pin_pwdn = CAM_PIN_PWDN,
.pin_reset = CAM_PIN_RESET,
.pin_xclk = CAM_PIN_XCLK,
// ... 其他引脚配置 ...
// 时钟配置
.xclk_freq_hz = 20000000, // 20MHz时钟
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
// 图像配置
.fb_location = CAMERA_FB_IN_PSRAM, // 帧缓冲区存放在PSRAM中
.pixel_format = PIXFORMAT_RGB565, // RGB565格式
.frame_size = FRAMESIZE_QVGA, // 320x240分辨率
.jpeg_quality = 12, // JPEG质量(如果使用JPEG格式)
.fb_count = 2, // 帧缓冲区数量
.grab_mode = CAMERA_GRAB_WHEN_EMPTY // 抓取模式
};
// 初始化摄像头
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);
return;
}
// 获取摄像头传感器并设置镜像翻转
sensor_t *s = esp_camera_sensor_get();
s->set_vflip(s, 0);
s->set_hmirror(s, 1);
}
5.2 LVGL初始化 (ksdiy_lvgl_port.c)
LVGL是一个轻量级图形库,需要与ESP32-S3的显示驱动进行对接:
void ksdiy_lvgl_port_init(void)
{
// 初始化I2C总线(用于触摸屏和IO扩展芯片)
i2c_init();
// 初始化IO扩展芯片
esp_io_expander_handle_t io_expander = NULL;
// 尝试初始化TCA9554,如果失败则尝试TCA9554A
if ((esp_io_expander_new_i2c_tca9554(touch_i2c_bus_, BSP_IO_EXPANDER_I2C_ADDRESS_TCA9554, &io_expander) != ESP_OK))
{
// ... 初始化逻辑 ...
}
// 初始化SPI总线(用于LCD显示屏)
spi_bus_config_t buscfg = {
.sclk_io_num = KSDIY_PIN_NUM_SCLK,
.mosi_io_num = KSDIY_PIN_NUM_MOSI,
.miso_io_num = KSDIY_PIN_NUM_MISO,
// ... 其他SPI配置 ...
};
ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
// 初始化LCD面板
// ... LCD面板初始化代码 ...
// 初始化LVGL
// ... LVGL初始化代码 ...
// 创建LVGL任务
xTaskCreatePinnedToCore(ksdiy_lvgl_port_task, "lvgl", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL, 0);
}
5.3 摄像头显示页面 (page_cam.c)
这部分代码负责从摄像头获取图像并显示在LCD上:
void Cam_Task(void *pvParameters)
{
while (1)
{
// 记录帧率
static int64_t last_frame = 0;
if (!last_frame)
{
last_frame = esp_timer_get_time();
}
// 获取摄像头图像
fb = esp_camera_fb_get();
if (fb == NULL)
{
vTaskDelay(100);
ESP_LOGE(TAG, "Get image failed!");
}
else
{
// 获取LVGL互斥锁
if (ksdiy_lvgl_lock(0))
{
// 设置图像数据
img_dsc.data = fb->buf;
// 更新LVGL图像对象
lv_img_set_src(img_cam, &img_dsc);
// 释放LVGL互斥锁
ksdiy_lvgl_unlock();
}
// 返回图像缓冲区
esp_camera_fb_return(fb);
// 计算帧率
int64_t fr_end = esp_timer_get_time();
int64_t frame_time = fr_end - last_frame;
last_frame = fr_end;
frame_time /= 1000;
ESP_LOGI("esp", "MJPG: %ums (%.1ffps)", (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time);
}
}
}
void imgcam_init(void)
{
// 创建LVGL图像对象
img_cam = lv_img_create(lv_scr_act());
// 居中显示
lv_obj_center(img_cam);
// 创建画布对象
camera_canvas = lv_canvas_create(lv_scr_act());
assert(camera_canvas);
lv_obj_center(camera_canvas);
}
void page_cam_load()
{
// 初始化摄像头
app_camera_init();
// 初始化图像显示
imgcam_init();
// 创建摄像头任务
xTaskCreatePinnedToCore(&Cam_Task, "Cam_Task", 1024 * 5, NULL, 14, NULL, 0);
}
5.4 主程序 (app_main.c)
主程序负责初始化各个模块并启动应用:
void app_main(void)
{
// 初始化NVS(非易失性存储)
esp_err_t ret = nvs_flash_init();
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();
}
ESP_ERROR_CHECK(ret);
// 初始化LVGL
ksdiy_lvgl_port_init();
// 获取LVGL互斥锁
if (ksdiy_lvgl_lock(0))
{
// 加载摄像头页面
page_cam_load();
// 释放LVGL互斥锁
ksdiy_lvgl_unlock();
}
}
6. 关键技术点解析
6.1 摄像头配置
摄像头配置中有几个重要参数需要理解:
pixel_format: 像素格式,本项目使用RGB565格式,每个像素占用2字节frame_size: 图像分辨率,QVGA表示320x240fb_location: 帧缓冲区位置,CAMERA_FB_IN_PSRAM表示存放在外部PSRAM中fb_count: 帧缓冲区数量,影响图像采集的连续性
6.2 LVGL与ESP32-S3的对接
LVGL需要与ESP32-S3的显示驱动对接,主要包括:
- 显示缓冲区的创建和管理
- 刷新回调函数的实现
- 触摸输入的处理
- 任务调度机制
6.3 多任务协作
项目中使用了FreeRTOS的多任务功能:
- LVGL任务:负责处理LVGL的渲染和事件
- 摄像头任务:负责获取摄像头图像并更新显示
为了避免资源冲突,使用了互斥锁(Mutex)进行任务同步。
7. 编译和烧录
7.1 编译项目
使用ESP-IDF的命令行工具编译项目:
cd 8.lcd_camera_lvgl_v8
idf.py build
7.2 烧录到开发板
idf.py -p COM端口号 flash
7.3 查看日志
idf.py -p COM端口号 monitor
8. 常见问题与解决方案
8.1 摄像头初始化失败
可能原因:
- 摄像头连接不正确
- 引脚定义错误
- 摄像头型号不兼容
解决方案:
- 检查硬件连接
- 确认引脚定义与实际连接一致
- 尝试不同的摄像头配置参数
8.2 显示异常
可能原因:
- LCD驱动参数不正确
- 图像格式与LCD不匹配
- 内存不足
解决方案:
- 调整LCD驱动参数
- 确保图像格式与LCD兼容
- 减小图像分辨率或使用PSRAM
8.3 帧率低
可能原因:
- CPU负载过高
- 数据传输瓶颈
- 内存访问效率低
解决方案:
- 优化任务优先级
- 使用DMA加速数据传输
- 调整缓冲区策略
9. 扩展功能
基于本项目,你可以进一步开发以下功能:
- 图像处理:添加滤镜、边缘检测等
- 人脸识别:结合ESP-WHO库实现人脸检测
- 二维码识别:添加二维码扫描功能
- 视频录制:将图像保存为视频文件
10. 总结
本教程介绍了如何使用ESP32-S3开发板配合摄像头和LCD显示屏实现实时图像显示。通过学习本教程,你应该能够理解:
- ESP32-S3摄像头接口的使用
- LVGL图形库的基本应用
- FreeRTOS多任务编程
- ESP-IDF项目的组织结构
希望这个教程能帮助你开始ESP32-S3的摄像头应用开发!