开发环境:linux,vscode,esp-idf v5.4.1
预期行为:识别二维码,在串口打印二维码内容,并在lcd上框出二维码
问题:quirc识别不到二维码,加的信息显示一直qrcode count是0
复现过程:
使用idf以template-app创建工程
加入espressif/quirc '*'和espressif/esp32-camera ‘*”组件
在menuconfig中打开PSRAM
尝试过的办法:
调整RGB565转灰度的算法
使用更清晰/内容更简单的二维码
使用quirc_flip
lcd显示摄像头帧是正常的,感觉quirc的处理并没有带来多少显示的延迟
另外,我的lcd是240x320,而QVGA是320x240,导致显示的图像是横着的,而我又没找到完全一致的framesize。如果还能帮忙解决这个小问题,我将不胜感激。
main.c
Code: Select all
#include <stdio.h>
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "driver/i2c.h"
#include "esp_log.h"
#include "esp_camera.h"
#include "quirc.h"
#include "quirc_internal.h"
#include "freertos/semphr.h" // 用于任务同步的信号量
SemaphoreHandle_t xDrawSemaphore;
#define TAG "APP_MAIN" // 定义TAG用于日志输出
#define DRAW_RECT_DEBUG 1
#if DRAW_RECT_DEBUG == 1
#define DRAW_RECT_LOG(...) ESP_LOGI(TAG, __VA_ARGS__)
#else
#define DRAW_RECT_LOG(...)
#endif
#define PWDN_GPIO_NUM 17 // power down is not used
#define RESET_GPIO_NUM -1 // software reset will be performed
#define XCLK_GPIO_NUM 8
#define SIOD_GPIO_NUM 21
#define SIOC_GPIO_NUM 16
#define Y9_GPIO_NUM 2
#define Y8_GPIO_NUM 7
#define Y7_GPIO_NUM 10
#define Y6_GPIO_NUM 14
#define Y5_GPIO_NUM 11
#define Y4_GPIO_NUM 15
#define Y3_GPIO_NUM 13
#define Y2_GPIO_NUM 12
#define VSYNC_GPIO_NUM 6
#define HREF_GPIO_NUM 4
#define PCLK_GPIO_NUM 9
#define PIN_NUM_SCLK 39
#define PIN_NUM_MOSI 38
#define PIN_NUM_MISO 40
#define SPI_HOST SPI2_HOST
#define I2C_NUM 0 // I2C number
#define PIN_NUM_I2C_SDA 48
#define PIN_NUM_I2C_SCL 47
#define LCD_PIXEL_CLOCK_HZ (80 * 1000 * 1000)
#define PIN_NUM_LCD_DC 42
#define PIN_NUM_LCD_RST -1
#define PIN_NUM_LCD_CS 45
#define LCD_CMD_BITS 8
#define LCD_PARAM_BITS 8
#define LCD_H_RES 240
#define LCD_V_RES 320
#define PIN_NUM_BK_LIGHT 1
#define LCD_BL_LEDC_TIMER LEDC_TIMER_0
#define LCD_BL_LEDC_MODE LEDC_LOW_SPEED_MODE
#define LCD_BL_LEDC_CHANNEL LEDC_CHANNEL_0
#define LCD_BL_LEDC_DUTY_RES LEDC_TIMER_10_BIT // Set duty resolution to 13 bits
#define LCD_BL_LEDC_DUTY (1024) // Set duty to 50%. (2 ** 13) * 50% = 4096
#define LCD_BL_LEDC_FREQUENCY (10000) // Frequency in Hertz. Set frequency at 5 kHz
esp_lcd_panel_handle_t panel_handle;
void draw_rect(struct quirc_point* points, uint16_t* frame_buffer, int width, int height);
void camera_init(void)
{
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_1;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.frame_size = FRAMESIZE_QVGA;
config.pixel_format = PIXFORMAT_RGB565; // for streaming
// config.pixel_format = PIXFORMAT_RGB565; // for face detection/recognition
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.jpeg_quality = 12;
config.fb_count = 2;
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK)
{
printf("Camera init failed with error 0x%x", err);
vTaskDelete(NULL);
return;
}
sensor_t *s = esp_camera_sensor_get();
s->set_hmirror(s, 1);
}
void display_init(void)
{
ESP_LOGI(TAG, "SPI BUS init");
spi_bus_config_t buscfg = {
.sclk_io_num = PIN_NUM_SCLK,
.mosi_io_num = PIN_NUM_MOSI,
.miso_io_num = PIN_NUM_MISO,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4000,
};
ESP_ERROR_CHECK(spi_bus_initialize(SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
ESP_LOGI(TAG, "Install panel IO");
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = PIN_NUM_LCD_DC,
.cs_gpio_num = PIN_NUM_LCD_CS,
.pclk_hz = LCD_PIXEL_CLOCK_HZ,
.lcd_cmd_bits = LCD_CMD_BITS,
.lcd_param_bits = LCD_PARAM_BITS,
.spi_mode = 0,
.trans_queue_depth = 10,
};
// Attach the LCD to the SPI bus
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI_HOST, &io_config, &io_handle));
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = PIN_NUM_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
ESP_LOGI(TAG, "Install ST7789 panel driver");
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, false, true));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, true));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
}
void bsp_brightness_init(void)
{
gpio_set_direction(PIN_NUM_BK_LIGHT, GPIO_MODE_OUTPUT);
gpio_set_level(PIN_NUM_BK_LIGHT, 1);
// Prepare and then apply the LEDC PWM timer configuration
ledc_timer_config_t ledc_timer = {
.speed_mode = LCD_BL_LEDC_MODE,
.timer_num = LCD_BL_LEDC_TIMER,
.duty_resolution = LCD_BL_LEDC_DUTY_RES,
.freq_hz = LCD_BL_LEDC_FREQUENCY, // Set output frequency at 5 kHz
.clk_cfg = LEDC_AUTO_CLK};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
// Prepare and then apply the LEDC PWM channel configuration
ledc_channel_config_t ledc_channel = {
.speed_mode = LCD_BL_LEDC_MODE,
.channel = LCD_BL_LEDC_CHANNEL,
.timer_sel = LCD_BL_LEDC_TIMER,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = PIN_NUM_BK_LIGHT,
.duty = 0, // Set duty to 0%
.hpoint = 0};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
}
void bsp_brightness_set_level(uint8_t level)
{
if (level > 100)
{
ESP_LOGE(TAG, "Brightness value out of range");
return;
}
uint32_t duty = (level * (LCD_BL_LEDC_DUTY - 1)) / 100;
ESP_ERROR_CHECK(ledc_set_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL, duty));
ESP_ERROR_CHECK(ledc_update_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL));
ESP_LOGI(TAG, "LCD brightness set to %d%%", level);
}
// 这个函数没有使用
static void camera_task(void *param)
{
ESP_LOGI(TAG, "camera task");
camera_fb_t *pic;
bool last_frame_has_qr = false;
while (1)
{
// 获取摄像头帧
pic = esp_camera_fb_get();
if (pic != NULL)
{
ESP_LOGI(TAG, "Frame size: %zu bytes, Resolution: %dx%d, Format: %d",
pic->len, pic->width, pic->height, pic->width);
// 如果不是连续显示二维码帧,则更新为普通帧
if (!last_frame_has_qr && xSemaphoreTake(xDrawSemaphore, portMAX_DELAY) == pdTRUE)
{
// 直接显示原始图像
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, pic->width, pic->height, pic->buf);
// 释放信号量
xSemaphoreGive(xDrawSemaphore);
}
// 释放摄像头帧
esp_camera_fb_return(pic);
}
// 控制任务执行频率
vTaskDelay(pdMS_TO_TICKS(10));
}
}
static void qrcode_process_task(void *param)
{
ESP_LOGI("QRCODE", "QRCODE process task start");
// 创建二维码解码器
struct quirc *qr_coder = quirc_new();
assert(qr_coder);
int err = quirc_resize(qr_coder, LCD_H_RES, LCD_V_RES);
if (err != 0)
{
ESP_LOGE(TAG, "quirc_resize failed ,error code:%d", err);
return;
}
camera_fb_t *pic;
while (1)
{
// 获取一帧图片
pic = esp_camera_fb_get();
if (pic == NULL)
{
ESP_LOGE(TAG, "Failed to get camera frame");
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
// 处理图片缓冲区
int w = (int)pic->width;
int h = (int)pic->height;
uint8_t *qr_buf = quirc_begin(qr_coder, &w, &h);
if (qr_buf == NULL)
{
ESP_LOGE("QRCODE", "Failed to get QR code buffer");
esp_camera_fb_return(pic);
continue;
}
// 将图像转换为灰度图像并复制到缓冲区
for (int y = 0; y < pic->height; y++)
{
for (int x = 0; x < pic->width; x++)
{
uint16_t pixel = ((uint16_t*)pic->buf)[(y * pic->width + x)];
uint8_t r = (pixel >> 11) & 0x1F; // 获取红色
uint8_t g = (pixel >> 5) & 0x3F; // 获取绿色
uint8_t b = (pixel & 0x1F); // 获取蓝色
// 转化为RGB888
r = (r << 3) | (r >> 2);
g = (g <<2) | (g >> 4);
b = (b << 3) | (b >> 2);
// 转化为灰度
qr_buf[y * pic->width + x] = (uint8_t)(r * 0.299 + g * 0.587 + b * 0.114);
}
}
// 结束缓冲区处理
quirc_end(qr_coder);
// 开始解码
bool has_qr_code = false;
int qr_count = quirc_count(qr_coder);
ESP_LOGI(TAG, "QRCODE COUNT: %d", qr_count); // 添加调试信息:打印检测到的二维码数量
for (int i = 0; i < qr_count; i++)
{
struct quirc_code code;
struct quirc_data data;
quirc_extract(qr_coder, i, &code);
quirc_decode_error_t err = quirc_decode(&code, &data);
if (err == (quirc_decode_error_t)QUIRC_SUCCESS)
{
// 获取二维码数据
char *qr_data = (char *)data.payload;
ESP_LOGI(TAG, "QRCODE DATA: %s", qr_data);
// 绘制矩形框
draw_rect(code.corners, (uint16_t*)pic->buf, pic->width, pic->height);
has_qr_code = true;
}
else
{
if (err != (quirc_decode_error_t)QUIRC_ERROR_DATA_ECC)
{
// 如果识别失败,尝试翻转二维码
quirc_flip(&code);
err = quirc_decode(&code, &data);
}
else
{
// 获取二维码数据失败
ESP_LOGW(TAG, "QRCODE DECODE FAILED");
}
}
}
// 总是尝试获取信号量以确保独占访问
if (xSemaphoreTake(xDrawSemaphore, portMAX_DELAY) == pdTRUE)
{
// 提交带有二维码框的图像到显示
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, pic->width, pic->height, pic->buf);
// 设置标志表示当前显示的是包含二维码的帧
((void**)param)[0] = (void*)(uintptr_t)has_qr_code;
// 释放信号量
xSemaphoreGive(xDrawSemaphore);
}
// 释放资源
esp_camera_fb_return(pic);
pic = NULL;
qr_buf = NULL;
// 控制任务执行频率
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void app_main(void)
{
camera_init();
display_init();
bsp_brightness_init();
bsp_brightness_set_level(80);
// 创建互斥信号量用于显示同步
xDrawSemaphore = xSemaphoreCreateMutex();
assert(xDrawSemaphore != NULL);
// 创建用于任务间通信的状态变量
bool *p_has_qr_code = (bool*)calloc(1, sizeof(bool));
assert(p_has_qr_code != NULL);
// xTaskCreatePinnedToCore(camera_task, "camera_task", 1024 * 20, p_has_qr_code, 1, NULL, 0);
xTaskCreatePinnedToCore(qrcode_process_task, "qrcode_process_task", 1024 * 15, p_has_qr_code, 2, NULL, 1);
}
void draw_rect(struct quirc_point* points, uint16_t* frame_buffer, int width, int height)
{
// 定义矩形框的颜色(RGB565格式:0xRRGGBB)
uint16_t color = 0xF800; // 红色
// 设置矩形框的宽度
uint8_t rect_width = 3;
// 绘制四条边
for (int i = 0; i < 4; i++) {
int x1 = points[i].x;
int y1 = points[i].y;
int x2 = points[(i + 1) % 4].x;
int y2 = points[(i + 1) % 4].y;
// 计算线段的方向
int dx = abs(x2 - x1);
int dy = abs(y2 - y1);
int sx = (x1 < x2) ? 1 : -1;
int sy = (y1 < y2) ? 1 : -1;
int err = dx - dy;
// 使用Bresenham算法绘制线段
while (1) {
// 绘制线段周围的像素,形成矩形框
for (int r = -rect_width; r <= rect_width; r++) {
for (int s = -rect_width; s <= rect_width; s++) {
int px = x1 + r;
int py = y1 + s;
// 确保坐标在屏幕范围内
if (px >= 0 && px < width && py >= 0 && py < height) {
// 在帧缓冲区中绘制像素点
frame_buffer[py * width + px] = color;
}
}
}
if (x1 == x2 && y1 == y2) break;
int e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x1 += sx;
}
if (e2 < dx) {
err += dx;
y1 += sy;
}
}
}
}