Phase 4: ST7789 240x320 panel (esp_lcd), 5x7 font, Core 1 UI task, streaming tjpgd JPEG decode + baked storage image
This commit is contained in:
237
main/display_manager.c
Normal file
237
main/display_manager.c
Normal file
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* MTG RFID Companion - ST7789 display manager (Core 1)
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_panel_vendor.h"
|
||||
#include "esp_lcd_panel_st7789.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "tjpgd.h"
|
||||
#include "hw_pins.h"
|
||||
#include "spi_bus_manager.h"
|
||||
#include "display_manager.h"
|
||||
#include "ui_font.h"
|
||||
|
||||
static const char *TAG = "display";
|
||||
|
||||
#define LCD_HOST HW_SPI_HOST
|
||||
#define LCD_PIXEL_CLOCK_HZ (20 * 1000 * 1000)
|
||||
#define LCD_CMD_BITS 8
|
||||
#define LCD_PARAM_BITS 8
|
||||
#define LCD_TRANS_QUEUE 10
|
||||
|
||||
#define JPEG_POOL_SIZE (10 * 1024)
|
||||
#define JPEG_SBUF (512) /* must match CONFIG_JD_SZBUF */
|
||||
|
||||
static esp_lcd_panel_handle_t s_panel;
|
||||
static uint16_t *s_staging = NULL; /* 240 px line staging buffer for JPEG */
|
||||
|
||||
static esp_err_t panel_io_init(esp_lcd_panel_io_handle_t *io)
|
||||
{
|
||||
esp_lcd_panel_io_spi_config_t io_config = {
|
||||
.dc_gpio_num = HW_LCD_DC_GPIO,
|
||||
.cs_gpio_num = HW_LCD_CS_GPIO,
|
||||
.pclk_hz = LCD_PIXEL_CLOCK_HZ,
|
||||
.lcd_cmd_bits = LCD_CMD_BITS,
|
||||
.lcd_param_bits = LCD_PARAM_BITS,
|
||||
.spi_mode = 0,
|
||||
.trans_queue_depth = LCD_TRANS_QUEUE,
|
||||
};
|
||||
return esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, io);
|
||||
}
|
||||
|
||||
esp_err_t display_manager_init(void)
|
||||
{
|
||||
if (s_panel != NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_lcd_panel_io_handle_t io = NULL;
|
||||
esp_err_t ret = panel_io_init(&io);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "panel io init failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = HW_LCD_RST_GPIO,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.bits_per_pixel = 16,
|
||||
};
|
||||
ret = esp_lcd_new_panel_st7789(io, &panel_config, &s_panel);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "st7789 panel create failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(s_panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(s_panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(s_panel, true));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(s_panel, true));
|
||||
|
||||
s_staging = malloc(DISP_W * sizeof(uint16_t));
|
||||
if (s_staging == NULL) {
|
||||
ESP_LOGE(TAG, "staging buffer alloc failed");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "ST7789 %dx%d ready (CS=%d DC=%d RST=%d)",
|
||||
DISP_W, DISP_H, HW_LCD_CS_GPIO, HW_LCD_DC_GPIO, HW_LCD_RST_GPIO);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void display_draw_bitmap(int x0, int y0, int x1, int y1, const uint16_t *pixels)
|
||||
{
|
||||
if (s_panel == NULL || pixels == NULL) {
|
||||
return;
|
||||
}
|
||||
esp_lcd_panel_draw_bitmap(s_panel, x0, y0, x1, y1, pixels);
|
||||
}
|
||||
|
||||
void display_fill_rect(int x0, int y0, int x1, int y1, uint16_t color)
|
||||
{
|
||||
if (x0 < 0) x0 = 0;
|
||||
if (y0 < 0) y0 = 0;
|
||||
if (x1 > DISP_W) x1 = DISP_W;
|
||||
if (y1 > DISP_H) y1 = DISP_H;
|
||||
if (x1 <= x0 || y1 <= y0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int width = x1 - x0;
|
||||
int height = y1 - y0;
|
||||
|
||||
static uint16_t line[DISP_W];
|
||||
for (int i = 0; i < width; i++) {
|
||||
line[i] = color;
|
||||
}
|
||||
for (int r = 0; r < height; r++) {
|
||||
display_draw_bitmap(x0, y0 + r, x1, y0 + r + 1, line);
|
||||
}
|
||||
}
|
||||
|
||||
void display_fill(uint16_t color)
|
||||
{
|
||||
display_fill_rect(0, 0, DISP_W, DISP_H, color);
|
||||
}
|
||||
|
||||
int display_put_text(int x, int y, const char *str, uint16_t fg, uint16_t bg)
|
||||
{
|
||||
int x_start = x;
|
||||
|
||||
while (str != NULL && *str != '\0') {
|
||||
char c = *str++;
|
||||
if (c >= FONT5X7_FIRST_CHAR && c <= FONT5X7_LAST_CHAR) {
|
||||
const uint8_t *glyph = font5x7[c - FONT5X7_FIRST_CHAR];
|
||||
uint16_t cell[FONT5X7_W][FONT5X7_H];
|
||||
|
||||
for (int gy = 0; gy < FONT5X7_H; gy++) {
|
||||
for (int gx = 0; gx < FONT5X7_W; gx++) {
|
||||
cell[gx][gy] = (glyph[gy] >> gx) & 1 ? fg : bg;
|
||||
}
|
||||
}
|
||||
if (x + FONT5X7_W <= DISP_W && y + FONT5X7_H <= DISP_H) {
|
||||
display_draw_bitmap(x, y, x + FONT5X7_W, y + FONT5X7_H, &cell[0][0]);
|
||||
}
|
||||
x += FONT5X7_W + 1;
|
||||
} else if (c == ' ') {
|
||||
x += FONT5X7_W + 1;
|
||||
}
|
||||
}
|
||||
return x - x_start;
|
||||
}
|
||||
|
||||
void display_put_text_center(int y, const char *str, uint16_t fg, uint16_t bg)
|
||||
{
|
||||
int len = (int)strlen(str);
|
||||
int width = len * (FONT5X7_W + 1) - 1;
|
||||
int x = (DISP_W - width) / 2;
|
||||
if (x < 0) x = 0;
|
||||
display_put_text(x, y, str, fg, bg);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* tjpgd streaming decode */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
typedef struct {
|
||||
FILE *f;
|
||||
} jpeg_io_t;
|
||||
|
||||
static size_t jpeg_input_cb(JDEC *jd, uint8_t *buf, size_t ndata)
|
||||
{
|
||||
jpeg_io_t *io = (jpeg_io_t *)jd->device;
|
||||
if (io == NULL || io->f == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (buf == NULL) {
|
||||
/* tjpgd requests skipping bytes during header scan */
|
||||
if (fseek(io->f, (long)ndata, SEEK_CUR) == 0) {
|
||||
return ndata;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return fread(buf, 1, ndata, io->f);
|
||||
}
|
||||
|
||||
static int jpeg_output_cb(JDEC *jd, void *bitmap, JRECT *rect)
|
||||
{
|
||||
(void)jd;
|
||||
uint16_t *pix = (uint16_t *)bitmap;
|
||||
int w = rect->right - rect->left + 1;
|
||||
int h = rect->bottom - rect->top + 1;
|
||||
|
||||
for (int row = 0; row < h; row++) {
|
||||
/* Convert little-endian RGB565 words (tjpgd) to big-endian wire order */
|
||||
for (int col = 0; col < w; col++) {
|
||||
uint16_t v = pix[row * w + col];
|
||||
s_staging[col] = (uint16_t)((v << 8) | (v >> 8));
|
||||
}
|
||||
display_draw_bitmap(rect->left, rect->top + row, rect->left + w, rect->top + row + 1, s_staging);
|
||||
}
|
||||
return 1; /* continue decoding */
|
||||
}
|
||||
|
||||
esp_err_t display_show_jpeg_file(const char *path)
|
||||
{
|
||||
esp_err_t ret = ESP_FAIL;
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "cannot open %s", path);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
jpeg_io_t io = { .f = f };
|
||||
JDEC jdec;
|
||||
void *pool = malloc(JPEG_POOL_SIZE);
|
||||
if (pool == NULL) {
|
||||
ESP_LOGE(TAG, "jpeg pool alloc failed");
|
||||
fclose(f);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
JRESULT jr = jd_prepare(&jdec, jpeg_input_cb, pool, JPEG_POOL_SIZE, &io);
|
||||
if (jr != JDR_OK) {
|
||||
ESP_LOGE(TAG, "jd_prepare failed (%d)", jr);
|
||||
goto done;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "decoding %s: %ux%u", path, (unsigned)jdec.width, (unsigned)jdec.height);
|
||||
jr = jd_decomp(&jdec, jpeg_output_cb, 0);
|
||||
if (jr != JDR_OK) {
|
||||
ESP_LOGE(TAG, "jd_decomp failed (%d)", jr);
|
||||
goto done;
|
||||
}
|
||||
ESP_LOGI(TAG, "JPEG displayed: %s", path);
|
||||
ret = ESP_OK;
|
||||
|
||||
done:
|
||||
free(pool);
|
||||
fclose(f);
|
||||
return ret;
|
||||
}
|
||||
Reference in New Issue
Block a user