开始前适用开发板:ESP32-S3 Korvo · 开发框架:ESP-IDF
适用开发板:Korvo S3 · 工程
04.advanced.camera_lvgl_display· targetesp32s3
摄像头:DVP 并口 sensor(非 P4 的 MIPI CSI)
学习时长:约 25 分钟 · 难度:★★★☆☆
学习目标
完成本节后,你将能够:
- 理解 DVP 摄像头的工作原理与初始化流程
- 掌握如何在 LVGL 中显示实时摄像头画面
- 了解帧缓冲管理与内存分配策略
- 处理 RGB565 字节序问题
1. 这个例子在干嘛
在 LVGL 界面中 实时显示摄像头画面——就像手机的相机预览界面(但分辨率低很多)。
数据流:
┌──────────┐ DVP并口 ┌──────────────┐ lv_img 更新 ┌──────────┐
│ 摄像头 │───────────────→│ 帧缓冲 PSRAM │───────────────→│ LVGL 屏幕│
│ Sensor │ RGB565/YUV │ (fb_get) │ RGB565 │ SPI LCD │
└──────────┘ └──────────────┘ └──────────┘
↑ fb_return()
└── 用完归还
与 P4C5 的区别:Korvo 只有 单路 DVP 摄像头,P4C5 支持 MIPI CSI + USB UVC 双路。画质和帧率差距明显,但编程思路相同。P4 双路版见 P4C5 camera_lvgl_display。
2. 编译烧录
cd Korvo_Firmware\04.advanced.camera_lvgl_display
idf.py set-target esp32s3
idf.py menuconfig # 摄像头型号、引脚(若可配)
idf.py build flash monitor
烧录前必做:确认 Korvo 摄像头模组/FPC 排线已正确连接。建议先跑 korvo_board_test 的 Camera 页验证硬件。
3. 运行现象
| 阶段 | 屏幕表现 | 串口输出 |
|---|---|---|
| 启动 | LVGL 界面加载 | camera init OK / sensor 型号 |
| 预览中 | LVGL 窗口内实时画面 | 帧率 log(若启用) |
| 异常 | 黑屏或花屏 | 错误日志(I2C fail / no sensor 等) |
4. 关键文件与代码解析
| 文件 | 作用 | 重点关注 |
|---|---|---|
main/app_main.c | 入口,加载 page | 初始化顺序 |
main/app_camera.c | DVP 摄像头驱动初始化 | camera_config_t 参数 |
main/page_cam.c | LVGL 页面 + 取帧任务 | Cam_Task 核心循环 |
4.1 摄像头初始化(app_camera.c)
camera_config_t config = {
.pin_d0 ~ d7 // 8位并口数据线
.pin_xclk // 主时钟输出给 sensor
.pin_pclk // pixel clock(sensor 输出)
.pin_vsync // 帧同步
.pin_href // 行同步
.pin_sccb_sda/scl // I2C(SCCB 协议,用于配置 sensor 寄存器)
.frame_size = FRAMESIZE_QVGA // 320×240,SPI 小屏的甜点分辨率
.pixel_format = PIXFORMAT_RGB565
.fb_count = 2 // 双缓冲
.fb_location = CAMERA_FB_IN_PSRAM
};
esp_camera_init(&config);
4.2 核心取帧循环(page_cam.c · Cam_Task)
void Cam_Task(void *arg) {
while (1) {
// 1. 获取一帧
camera_fb_t *fb = esp_camera_fb_get();
// 2. RGB565 字节序转换(若 sensor 与 LVGL 字节序不同)
swap_bytes(fb->buf, fb->len);
// 3. 更新 LVGL 图片对象
img_dsc.data = fb->buf;
img_dsc.data_size = fb->len;
lv_img_set_src(img_obj, &img_dsc);
// 4. 归还帧缓冲
esp_camera_fb_return(fb);
// 5. 短延时让出 CPU
vTaskDelay(pdMS_TO_TICKS(10));
}
}
关键规则:
fb_get()和fb_return()必须成对调用。如果只 get 不 return,帧缓冲很快耗尽,摄像头停止工作。
4.3 LVGL 线程安全
摄像头任务和 LVGL 任务在不同线程中运行。更新 lv_img 时需要加锁:
bsp_display_lock(0); // 获取 LVGL 互斥锁
lv_img_set_src(img_obj, &img_dsc);
bsp_display_unlock(); // 释放锁
5. 技术要点深入
5.1 分辨率选择
| 分辨率 | 像素数 | 帧率(估计) | 适用场景 |
|---|---|---|---|
| QQVGA (160×120) | 19,200 | 高 | 低功耗/快速预览 |
| QVGA (320×240) | 76,800 | 中 | 推荐,SPI 小屏适配 |
| VGA (640×480) | 307,200 | 低 | SPI 带宽不足,不推荐 |
5.2 RGB565 字节序
DVP 摄像头输出的 RGB565 通常是大端序(MSB first),而 LVGL 默认期望小端序。不做转换会导致画面色彩异常(偏蓝/偏红),但不会黑屏。
快速检验方法:拍一个红色物体,屏幕显示蓝色 → 字节序反了。
5.3 帧缓冲内存
- PSRAM 优先:RGB565 320×240 一帧 = 150 KB,内部 SRAM 放不下
- 双缓冲(
fb_count = 2):一个在 DMA 传输,另一个在接收新帧,避免撕裂
6. 常见问题与排查
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 黑屏无画面 | FPC 排线松动 | 重新插拔排线;先用 board_test Camera 页验证 |
| 黑屏无画面 | Sensor I2C 未检测到 | 检查 SCCB 引脚;i2c_scanner 扫地址 |
| 花屏/色彩异常 | RGB565 字节序未 swap | 检查 Cam_Task 中的 swap_bytes |
| 花屏/色彩异常 | 图片宽高与 dsc 不匹配 | img_dsc 的 w/h 必须与 frame_size 一致 |
| 画面卡顿 | 分辨率过高 | 降到 QVGA;减小 LVGL 刷新区域 |
| 画面卡顿 | PSRAM 配置不当 | menuconfig → PSRAM → QPI 80MHz |
| 启动崩溃 | PSRAM 未启用 | 必须开启 PSRAM 支持(CONFIG_SPIRAM) |
7. 动手改造建议
- 添加拍照功能:按下按钮时保存当前帧到 SPIFFS / SD 卡
- 叠加 OSD 信息:用 LVGL label 在画面上叠加时间戳或温度
- 切换分辨率:添加 UI 按钮动态切换 QVGA / QQVGA
- 灰度模式:修改
pixel_format为PIXFORMAT_GRAYSCALE,体验不同格式 - 帧率计数器:统计每秒刷新了多少帧,显示在角落
8. 知识延伸
- DVP vs MIPI CSI:DVP 是传统并口(8/10/12 位数据线),最高约 VGA@30fps;MIPI CSI 是串行差分高速接口,支持 1080p 甚至 4K
- SCCB 协议:摄像头 sensor 使用 SCCB(Serial Camera Control Bus)配置寄存器,本质就是 I2C 的变种
- ESP32-S3 的摄像头优势:硬件 LCD CAM 外设可直接接收 DVP 数据并 DMA 到内存,无需 CPU 逐字节搬运
9. 下一步
| 方向 | 推荐例程 | 说明 |
|---|---|---|
| 网页图传 | camera_webserver | 浏览器远程看画面 |
| 人脸识别 | face_recognition | 在摄像头画面上叠加 AI |
| 颜色追踪 | 04.advanced.color_tracking_lvgl | 识别并追踪特定颜色 |
| 二维码扫描 | qrcode_detection | 摄像头 + 解码 |
酷世DIY · Kevincoooool