feat: add config view for lvgl.

Signed-off-by: Bo <boironic@gmail.com>
This commit is contained in:
Bo
2026-02-10 03:16:00 +08:00
committed by lvbo
parent 106fe3b5b0
commit 7918bab27d
15 changed files with 1130 additions and 2 deletions

View File

@@ -2,6 +2,7 @@
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include "esp_check.h"
#include "esp_log.h"
#include "driver/ledc.h"
@@ -9,6 +10,8 @@
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "display/Vernon_ST7789T/Vernon_ST7789T.h"
#include "display/font5x7.h"
#include "qrcode.h"
#define LCD_HOST SPI3_HOST
@@ -47,10 +50,109 @@ static const char *TAG = "display";
static esp_lcd_panel_handle_t panel_handle = NULL;
static uint8_t backlight_percent = 50;
static uint16_t *framebuffer = NULL;
typedef struct {
int x;
int y;
int box;
uint16_t fg;
} qr_draw_ctx_t;
static qr_draw_ctx_t s_qr_ctx;
extern const uint8_t _binary_banner_320x172_rgb565_start[];
extern const uint8_t _binary_banner_320x172_rgb565_end[];
static inline uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b)
{
return (uint16_t)(((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3));
}
static void fb_ensure(void)
{
if (!framebuffer) {
framebuffer = (uint16_t *)calloc(BANNER_W * BANNER_H, sizeof(uint16_t));
}
}
static inline void fb_set_pixel(int x, int y, uint16_t color)
{
if (x < 0 || y < 0 || x >= BANNER_W || y >= BANNER_H || !framebuffer) {
return;
}
framebuffer[y * BANNER_W + x] = color;
}
static void fb_fill_rect(int x, int y, int w, int h, uint16_t color)
{
if (!framebuffer) {
return;
}
for (int yy = y; yy < y + h; yy++) {
for (int xx = x; xx < x + w; xx++) {
fb_set_pixel(xx, yy, color);
}
}
}
static void fb_fill_rect_clipped(int x, int y, int w, int h, uint16_t color, int clip_x0, int clip_x1)
{
if (!framebuffer) {
return;
}
int x0 = x;
int x1 = x + w;
if (x0 < clip_x0) {
x0 = clip_x0;
}
if (x1 > clip_x1) {
x1 = clip_x1;
}
if (x1 <= x0) {
return;
}
for (int yy = y; yy < y + h; yy++) {
for (int xx = x0; xx < x1; xx++) {
fb_set_pixel(xx, yy, color);
}
}
}
static void fb_draw_char_scaled_clipped(int x, int y, char c, uint16_t color, int scale, int clip_x0, int clip_x1)
{
if (c < 32 || c > 127) {
c = '?';
}
const uint8_t *glyph = font5x7[(uint8_t)c - 32];
for (int col = 0; col < FONT5X7_WIDTH; col++) {
uint8_t bits = glyph[col];
for (int row = 0; row < FONT5X7_HEIGHT; row++) {
if (bits & (1 << row)) {
int px = x + col * scale;
int py = y + row * scale;
fb_fill_rect_clipped(px, py, scale, scale, color, clip_x0, clip_x1);
}
}
}
}
static void fb_draw_text_clipped(int x, int y, const char *text, uint16_t color, int line_height, int scale,
int clip_x0, int clip_x1)
{
int cx = x;
int cy = y;
for (size_t i = 0; text[i] != '\0'; i++) {
if (text[i] == '\n') {
cy += line_height;
cx = x;
continue;
}
fb_draw_char_scaled_clipped(cx, cy, text[i], color, scale, clip_x0, clip_x1);
cx += (FONT5X7_WIDTH + 1) * scale;
}
}
static void backlight_ledc_init(void)
{
ledc_timer_config_t ledc_timer = {
@@ -169,6 +271,106 @@ void display_show_banner(void)
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, BANNER_W, BANNER_H, start));
}
static void qr_draw_cb(esp_qrcode_handle_t qrcode)
{
int size = esp_qrcode_get_size(qrcode);
int quiet = 2;
int scale = s_qr_ctx.box / (size + quiet * 2);
if (scale < 1) {
scale = 1;
}
int qr_px = (size + quiet * 2) * scale;
int origin_x = s_qr_ctx.x + (s_qr_ctx.box - qr_px) / 2 + quiet * scale;
int origin_y = s_qr_ctx.y + (s_qr_ctx.box - qr_px) / 2 + quiet * scale;
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
if (esp_qrcode_get_module(qrcode, x, y)) {
fb_fill_rect(origin_x + x * scale, origin_y + y * scale, scale, scale, s_qr_ctx.fg);
}
}
}
}
void display_show_config_screen(const char *qr_text, const char *ip_text,
const char **lines, size_t line_count, size_t scroll,
size_t selected, int selected_offset_px)
{
if (!panel_handle) {
ESP_LOGW(TAG, "display not initialized");
return;
}
if (!qr_text || !ip_text || !lines) {
return;
}
fb_ensure();
if (!framebuffer) {
ESP_LOGW(TAG, "framebuffer alloc failed");
return;
}
const uint16_t color_bg = rgb565(0, 0, 0);
const uint16_t color_fg = rgb565(255, 255, 255);
const uint16_t color_qr_bg = rgb565(255, 255, 255);
const uint16_t color_qr_fg = rgb565(0, 0, 0);
const uint16_t color_title = rgb565(100, 200, 255);
const uint16_t color_sel_bg = rgb565(50, 80, 120);
fb_fill_rect(0, 0, BANNER_W, BANNER_H, color_bg);
// QR area (left column)
const int left_pad = 6;
const int qr_box = 110;
const int qr_x = left_pad;
const int qr_y = (BANNER_H - qr_box) / 2 - 8;
fb_fill_rect(qr_x, qr_y, qr_box, qr_box, color_qr_bg);
s_qr_ctx.x = qr_x;
s_qr_ctx.y = qr_y;
s_qr_ctx.box = qr_box;
s_qr_ctx.fg = color_qr_fg;
esp_qrcode_config_t cfg = ESP_QRCODE_CONFIG_DEFAULT();
cfg.display_func = qr_draw_cb;
cfg.max_qrcode_version = 6;
cfg.qrcode_ecc_level = ESP_QRCODE_ECC_MED;
esp_qrcode_generate(&cfg, qr_text);
// IP text under QR
fb_draw_text_clipped(qr_x, qr_y + qr_box + 4, ip_text, color_fg, 10, 1, 0, BANNER_W);
// Right column
const int right_x = qr_x + qr_box + 10;
const int right_w = BANNER_W - right_x - 6;
(void)right_w;
fb_draw_text_clipped(right_x, 4, "Configuration", color_title, 14, 2, right_x, BANNER_W);
const int line_height = 16;
const int start_y = 24;
size_t lines_per_page = (BANNER_H - start_y - 6) / line_height;
for (size_t i = 0; i < lines_per_page; i++) {
if (line_count == 0) {
break;
}
size_t idx = (scroll + i) % line_count;
if (idx < line_count) {
int line_y = start_y + (int)i * line_height;
if (idx == selected) {
fb_fill_rect(right_x, line_y - 1, BANNER_W - right_x - 2, line_height + 2, color_sel_bg);
fb_draw_text_clipped(right_x - selected_offset_px, line_y, lines[idx], color_fg, line_height, 2, right_x, BANNER_W);
} else {
fb_fill_rect(right_x, line_y - 1, BANNER_W - right_x - 2, line_height + 2, color_bg);
fb_draw_text_clipped(right_x, line_y, lines[idx], color_fg, line_height, 2, right_x, BANNER_W);
}
}
}
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, BANNER_W, BANNER_H, framebuffer));
}
bool display_get_banner_center_rgb(uint8_t *r, uint8_t *g, uint8_t *b)
{
if (!r || !g || !b) {

View File

@@ -2,18 +2,25 @@
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 172
esp_err_t display_init(void);
void display_show_banner(void);
void display_set_backlight_percent(uint8_t percent);
uint8_t display_get_backlight_percent(void);
void display_cycle_backlight(void);
bool display_get_banner_center_rgb(uint8_t *r, uint8_t *g, uint8_t *b);
void display_show_config_screen(const char *qr_text, const char *ip_text,
const char **lines, size_t line_count, size_t scroll,
size_t selected, int selected_offset_px);
#ifdef __cplusplus
}

105
main/display/font5x7.h Normal file
View File

@@ -0,0 +1,105 @@
#pragma once
#include <stdint.h>
#define FONT5X7_WIDTH 5
#define FONT5X7_HEIGHT 7
static const uint8_t font5x7[96][5] = {
{0x00,0x00,0x00,0x00,0x00}, // ' '
{0x00,0x00,0x5f,0x00,0x00}, // '!'
{0x00,0x07,0x00,0x07,0x00}, // '"'
{0x14,0x7f,0x14,0x7f,0x14}, // '#'
{0x24,0x2a,0x7f,0x2a,0x12}, // '$'
{0x23,0x13,0x08,0x64,0x62}, // '%'
{0x36,0x49,0x55,0x22,0x50}, // '&'
{0x00,0x05,0x03,0x00,0x00}, // '\''
{0x00,0x1c,0x22,0x41,0x00}, // '('
{0x00,0x41,0x22,0x1c,0x00}, // ')'
{0x14,0x08,0x3e,0x08,0x14}, // '*'
{0x08,0x08,0x3e,0x08,0x08}, // '+'
{0x00,0x50,0x30,0x00,0x00}, // ','
{0x08,0x08,0x08,0x08,0x08}, // '-'
{0x00,0x60,0x60,0x00,0x00}, // '.'
{0x20,0x10,0x08,0x04,0x02}, // '/'
{0x3e,0x51,0x49,0x45,0x3e}, // '0'
{0x00,0x42,0x7f,0x40,0x00}, // '1'
{0x42,0x61,0x51,0x49,0x46}, // '2'
{0x21,0x41,0x45,0x4b,0x31}, // '3'
{0x18,0x14,0x12,0x7f,0x10}, // '4'
{0x27,0x45,0x45,0x45,0x39}, // '5'
{0x3c,0x4a,0x49,0x49,0x30}, // '6'
{0x01,0x71,0x09,0x05,0x03}, // '7'
{0x36,0x49,0x49,0x49,0x36}, // '8'
{0x06,0x49,0x49,0x29,0x1e}, // '9'
{0x00,0x36,0x36,0x00,0x00}, // ':'
{0x00,0x56,0x36,0x00,0x00}, // ';'
{0x08,0x14,0x22,0x41,0x00}, // '<'
{0x14,0x14,0x14,0x14,0x14}, // '='
{0x00,0x41,0x22,0x14,0x08}, // '>'
{0x02,0x01,0x51,0x09,0x06}, // '?'
{0x32,0x49,0x79,0x41,0x3e}, // '@'
{0x7e,0x11,0x11,0x11,0x7e}, // 'A'
{0x7f,0x49,0x49,0x49,0x36}, // 'B'
{0x3e,0x41,0x41,0x41,0x22}, // 'C'
{0x7f,0x41,0x41,0x22,0x1c}, // 'D'
{0x7f,0x49,0x49,0x49,0x41}, // 'E'
{0x7f,0x09,0x09,0x09,0x01}, // 'F'
{0x3e,0x41,0x49,0x49,0x7a}, // 'G'
{0x7f,0x08,0x08,0x08,0x7f}, // 'H'
{0x00,0x41,0x7f,0x41,0x00}, // 'I'
{0x20,0x40,0x41,0x3f,0x01}, // 'J'
{0x7f,0x08,0x14,0x22,0x41}, // 'K'
{0x7f,0x40,0x40,0x40,0x40}, // 'L'
{0x7f,0x02,0x0c,0x02,0x7f}, // 'M'
{0x7f,0x04,0x08,0x10,0x7f}, // 'N'
{0x3e,0x41,0x41,0x41,0x3e}, // 'O'
{0x7f,0x09,0x09,0x09,0x06}, // 'P'
{0x3e,0x41,0x51,0x21,0x5e}, // 'Q'
{0x7f,0x09,0x19,0x29,0x46}, // 'R'
{0x46,0x49,0x49,0x49,0x31}, // 'S'
{0x01,0x01,0x7f,0x01,0x01}, // 'T'
{0x3f,0x40,0x40,0x40,0x3f}, // 'U'
{0x1f,0x20,0x40,0x20,0x1f}, // 'V'
{0x3f,0x40,0x38,0x40,0x3f}, // 'W'
{0x63,0x14,0x08,0x14,0x63}, // 'X'
{0x07,0x08,0x70,0x08,0x07}, // 'Y'
{0x61,0x51,0x49,0x45,0x43}, // 'Z'
{0x00,0x7f,0x41,0x41,0x00}, // '['
{0x02,0x04,0x08,0x10,0x20}, // '\\'
{0x00,0x41,0x41,0x7f,0x00}, // ']'
{0x04,0x02,0x01,0x02,0x04}, // '^'
{0x40,0x40,0x40,0x40,0x40}, // '_'
{0x00,0x01,0x02,0x04,0x00}, // '`'
{0x20,0x54,0x54,0x54,0x78}, // 'a'
{0x7f,0x48,0x44,0x44,0x38}, // 'b'
{0x38,0x44,0x44,0x44,0x20}, // 'c'
{0x38,0x44,0x44,0x48,0x7f}, // 'd'
{0x38,0x54,0x54,0x54,0x18}, // 'e'
{0x08,0x7e,0x09,0x01,0x02}, // 'f'
{0x0c,0x52,0x52,0x52,0x3e}, // 'g'
{0x7f,0x08,0x04,0x04,0x78}, // 'h'
{0x00,0x44,0x7d,0x40,0x00}, // 'i'
{0x20,0x40,0x44,0x3d,0x00}, // 'j'
{0x7f,0x10,0x28,0x44,0x00}, // 'k'
{0x00,0x41,0x7f,0x40,0x00}, // 'l'
{0x7c,0x04,0x18,0x04,0x78}, // 'm'
{0x7c,0x08,0x04,0x04,0x78}, // 'n'
{0x38,0x44,0x44,0x44,0x38}, // 'o'
{0x7c,0x14,0x14,0x14,0x08}, // 'p'
{0x08,0x14,0x14,0x18,0x7c}, // 'q'
{0x7c,0x08,0x04,0x04,0x08}, // 'r'
{0x48,0x54,0x54,0x54,0x20}, // 's'
{0x04,0x3f,0x44,0x40,0x20}, // 't'
{0x3c,0x40,0x40,0x20,0x7c}, // 'u'
{0x1c,0x20,0x40,0x20,0x1c}, // 'v'
{0x3c,0x40,0x30,0x40,0x3c}, // 'w'
{0x44,0x28,0x10,0x28,0x44}, // 'x'
{0x0c,0x50,0x50,0x50,0x3c}, // 'y'
{0x44,0x64,0x54,0x4c,0x44}, // 'z'
{0x00,0x08,0x36,0x41,0x00}, // '{'
{0x00,0x00,0x7f,0x00,0x00}, // '|'
{0x00,0x41,0x36,0x08,0x00}, // '}'
{0x10,0x08,0x08,0x10,0x08}, // '~'
{0x00,0x00,0x00,0x00,0x00} // DEL
};