开始前适用开发板:ESP32 系列 · 开发框架:ESP-IDF
文档类型:早期 Korvo S3 二维码笔记。 请优先阅读:Korvo qrcode · P4C5 qrcode
1. 项目概述
本项目基于ESP32-S3开发板,实现了一个摄像头实时捕获并识别二维码的应用。项目使用LVGL图形库显示摄像头画面,并利用ESP32的二维码识别库实时解析摄像头捕获的二维码内容。
2. 硬件准备
- ESP32-S3开发板(KSDIY Korvo系列)
- 摄像头模块(OV系列)
- LCD显示屏(240x240分辨率)
- 连接线缆
3. 软件环境
- ESP-IDF开发环境
- 依赖组件:
- LVGL图形库 (v8.3.0)
- ESP32摄像头驱动 (v2.0.13)
- ESP二维码扫描库
- 触摸屏驱动 (CST816S)
4. 项目结构
项目主要由以下几个部分组成:
- 摄像头初始化与配置 (app_camera.c/h)
- LVGL图形界面初始化 (ksdiy_lvgl_port.c/h)
- 摄像头页面与二维码识别 (page_cam.c/h)
- 主程序入口 (app_main.c)
5. 代码详解
5.1 摄像头初始化 (app_camera.c)
摄像头初始化是整个项目的基础,我们需要配置摄像头的各种参数:
#include "app_camera.h"
static const char *TAG = "app_camera";
static camera_config_t camera_config = {
.pin_pwdn = CAM_PIN_PWDN, // 电源引脚,-1表示不使用
.pin_reset = CAM_PIN_RESET, // 复位引脚,-1表示使用软件复位
.pin_xclk = CAM_PIN_XCLK, // XCLK时钟引脚
.pin_sscb_sda = CAM_PIN_SIOD, // SCCB数据线
.pin_sscb_scl = CAM_PIN_SIOC, // SCCB时钟线
// 摄像头数据引脚
.pin_d7 = CAM_PIN_D7,
.pin_d6 = CAM_PIN_D6,
.pin_d5 = CAM_PIN_D5,
.pin_d4 = CAM_PIN_D4,
.pin_d3 = CAM_PIN_D3,
.pin_d2 = CAM_PIN_D2,
.pin_d1 = CAM_PIN_D1,
.pin_d0 = CAM_PIN_D0,
.pin_vsync = CAM_PIN_VSYNC, // 垂直同步信号
.pin_href = CAM_PIN_HREF, // 水平参考信号
.pin_pclk = CAM_PIN_PCLK, // 像素时钟
// 时钟配置
.xclk_freq_hz = 20000000, // XCLK 20MHz
.ledc_timer = LEDC_TIMER_0, // 使用LEDC定时器0
.ledc_channel = LEDC_CHANNEL_0, // 使用LEDC通道0
// 图像配置
.fb_location = CAMERA_FB_IN_PSRAM, // 帧缓冲区存放在外部PSRAM中
.pixel_format = PIXFORMAT_RGB565, // 使用RGB565格式
.frame_size = FRAMESIZE_240X240, // 设置分辨率为240x240
.jpeg_quality = 12, // JPEG质量(如果使用JPEG格式)
.fb_count = 2, // 帧缓冲区数量
.grab_mode = CAMERA_GRAB_WHEN_EMPTY // 抓取模式
};
void app_camera_init()
{
// 初始化摄像头
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 摄像头引脚定义 (app_camera.h)
在头文件中,我们定义了摄像头的引脚和参数:
#ifndef MATERIALS_CLASSIFIER_ESP_APP_CAMERA_ESP_H_
#define MATERIALS_CLASSIFIER_ESP_APP_CAMERA_ESP_H_
#include "esp_camera.h"
#include "esp_log.h"
#include "esp_system.h"
#include "sensor.h"
/**
* 像素格式定义:
* PIXFORMAT_RGB565, // 2BPP/RGB565
* PIXFORMAT_YUV422, // 2BPP/YUV422
* PIXFORMAT_GRAYSCALE, // 1BPP/GRAYSCALE
* PIXFORMAT_JPEG, // JPEG/COMPRESSED
* PIXFORMAT_RGB888, // 3BPP/RGB888
*/
#define CAMERA_PIXEL_FORMAT PIXFORMAT_RGB565
/**
* 分辨率定义:
* FRAMESIZE_96X96, // 96x96
* FRAMESIZE_QQVGA, // 160x120
* FRAMESIZE_QQVGA2, // 128x160
* FRAMESIZE_QCIF, // 176x144
* FRAMESIZE_HQVGA, // 240x176
* FRAMESIZE_240X240 // 240x240
* FRAMESIZE_QVGA, // 320x240
* ...更多分辨率...
*/
#define CAMERA_FRAME_SIZE FRAMESIZE_QVGA
// 摄像头引脚定义
#define CAM_PIN_PWDN -1
#define CAM_PIN_RESET -1
#define CAM_PIN_XCLK 40
#define CAM_PIN_SIOD -1
#define CAM_PIN_SIOC -1
#define CAM_PIN_D7 39
#define CAM_PIN_D6 41
#define CAM_PIN_D5 42
#define CAM_PIN_D4 12
#define CAM_PIN_D3 3
#define CAM_PIN_D2 14
#define CAM_PIN_D1 47
#define CAM_PIN_D0 13
#define CAM_PIN_VSYNC 21
#define CAM_PIN_HREF 38
#define CAM_PIN_PCLK 11
#define FLIP_CAMERA 1
#define XCLK_FREQ 20000000
#ifdef __cplusplus
extern "C"
{
#endif
void app_camera_init();//初始化摄像头
void web_camera_init();
#ifdef __cplusplus
}
#endif
#endif
5.3 摄像头页面与二维码识别 (page_cam.c)
这是项目的核心部分,负责获取摄像头图像、显示到屏幕上,并进行二维码识别:
#include "page_cam.h"
#include "stdio.h"
#include <stdlib.h>
#include <string.h>
#include "lvgl.h"
#include "app_camera.h"
#include <esp_system.h>
#include "esp_log.h"
#include "esp_code_scanner.h"
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
#include "esp_timer.h"
#endif
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define TAG "PAGE_CAM"
camera_fb_t *fb;
lv_obj_t *img_cam; // 用于显示图像的LVGL对象
lv_obj_t *label_detect; // 用于显示识别结果的标签
lv_img_dsc_t img_dsc = {
.header.always_zero = 0,
.header.w = 240,
.header.h = 240,
.data_size = 240 * 240 * 2,
.header.cf = LV_IMG_CF_TRUE_COLOR,
.data = NULL,
};
// 摄像头任务,负责获取图像并识别二维码
void Cam_Task(void *pvParameters)
{
int64_t time1, time2;
while (1)
{
// 记录帧率
static int64_t last_frame = 0;
if (!last_frame)
{
last_frame = esp_timer_get_time();
}
// 获取摄像头图像
fb = esp_camera_fb_get();
time1 = esp_timer_get_time();
if (fb == NULL)
{
vTaskDelay(100);
ESP_LOGE(TAG, "Get image failed!");
}
else
{
// 将图像数据传给LVGL图像对象
img_dsc.data = fb->buf;
lv_img_set_src(img_cam, &img_dsc);
// 创建二维码扫描器并配置
esp_image_scanner_t *esp_scn = esp_code_scanner_create();
esp_code_scanner_config_t config = {
ESP_CODE_SCANNER_MODE_FAST, // 快速模式
ESP_CODE_SCANNER_IMAGE_RGB565, // RGB565图像格式
fb->width, fb->height // 图像尺寸
};
esp_code_scanner_set_config(esp_scn, config);
// 扫描图像中的二维码
int decoded_num = esp_code_scanner_scan_image(esp_scn, fb->buf);
if (decoded_num) // 如果识别到二维码
{
// 获取识别结果
esp_code_scanner_symbol_t result = esp_code_scanner_result(esp_scn);
time2 = esp_timer_get_time();
// 打印识别信息
ESP_LOGI(TAG, "Decode time in %lld ms.", (time2 - time1) / 1000);
ESP_LOGI(TAG, "Decoded %s symbol \"%s\"\n", result.type_name, result.data);
// 在标签上显示识别结果
lv_label_set_text(label_detect, (char *)result.data);
}
else
{
// 未识别到二维码
lv_label_set_text(label_detect, "Nothing...");
}
// 释放资源
esp_code_scanner_destroy(esp_scn);
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);
}
}
// 永远不会到达这里
while (1)
{
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
// 初始化图像显示和结果标签
void imgcam_init(void)
{
// 创建图像对象
img_cam = lv_img_create(lv_scr_act());
lv_obj_set_pos(img_cam, 0, 0);
lv_obj_set_size(img_cam, 240, 240);
// 创建标签对象用于显示识别结果
label_detect = lv_label_create(lv_scr_act());
// 设置标签属性
lv_label_set_long_mode(label_detect, LV_LABEL_LONG_WRAP);
lv_obj_set_pos(label_detect, 0, LV_VER_RES - 40);
lv_obj_set_size(label_detect, LV_HOR_RES, 50);
lv_label_set_recolor(label_detect, true);
lv_label_set_text(label_detect, "Hello!");
}
// 加载摄像头页面
void page_cam_load()
{
// 初始化摄像头
app_camera_init();
// 初始化图像显示界面
imgcam_init();
// 创建摄像头任务,优先级14,在核心0上运行
xTaskCreatePinnedToCore(&Cam_Task, "Cam_Task", 1024 * 5, NULL, 14, NULL, 0);
}
5.4 主程序入口 (app_main.c)
主程序负责初始化系统并启动摄像头页面:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"
#include "lv_demos.h"
#include "ksdiy_lvgl_port.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_vfs.h"
#include "esp_spiffs.h"
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#include "page_cam.h"
static const char *TAG = "example";
void app_main(void)
{
ESP_LOGI(TAG, "Compile time: %s %s", __DATE__, __TIME__);
/* 初始化NVS闪存 */
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
/* 初始化LVGL图形库 */
ksdiy_lvgl_port_init();
/* 获取LVGL互斥锁并加载摄像头页面 */
if (ksdiy_lvgl_lock(0))
{
// 加载摄像头页面
page_cam_load();
ksdiy_lvgl_unlock();
}
}
5.5 LVGL图形库初始化 (ksdiy_lvgl_port.c/h)
LVGL是一个轻量级图形库,用于显示摄像头图像和二维码识别结果。初始化过程包括:
- 配置LCD显示屏
- 初始化触摸屏
- 创建LVGL任务
- 设置LVGL缓冲区
6. 项目依赖组件
项目依赖的组件在idf_component.yml中定义:
dependencies:
espressif/esp_new_jpeg: "^0.5.0"
espressif/esp32-camera: "^2.0.13"
idf: ">=4.4"
lvgl/lvgl: "~8.3.0"
esp_lcd_touch_cst816s: "^1.0"
esp_io_expander_tca9554: "^2.0.0"
7. 编译与烧录
- 确保ESP-IDF环境已正确配置
- 进入项目目录
- 执行以下命令编译并烧录:
idf.py build
idf.py -p COM端口 flash
8. 使用说明
- 上电后,设备会自动初始化摄像头和显示屏
- 摄像头画面会实时显示在屏幕上
- 将二维码对准摄像头,系统会自动识别并在屏幕底部显示二维码内容
- 如果没有识别到二维码,屏幕底部会显示"Nothing..."
9. 常见问题
- 摄像头初始化失败:检查摄像头连接和引脚定义是否正确
- 屏幕显示异常:检查LVGL配置和LCD引脚定义
- 二维码识别不准确:调整摄像头与二维码的距离,保持适当光照
10. 进阶开发
- 增加更多二维码类型的支持
- 优化识别算法提高识别速度
- 添加声音或震动反馈
- 实现识别结果的保存和分享功能
希望本教程能帮助你理解ESP32-S3摄像头二维码识别项目的实现原理和开发流程!