@@ -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) {
|
||||
|
||||
@@ -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
105
main/display/font5x7.h
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user