Files
MTGcompanion/main/display_manager.c

279 lines
8.0 KiB
C

/*
* 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);
}
void display_draw_big_text(int x, int y, const char *str, int scale, uint16_t fg, uint16_t bg)
{
int cell_w = FONT5X7_W * scale;
int cell_h = FONT5X7_H * scale;
int num = (int)strlen(str);
if (num <= 0) {
return;
}
if (x + num * (cell_w + scale) - scale > DISP_W || y + cell_h > DISP_H) {
return;
}
/* Staging buffer for one glyph, scaled */
uint16_t *glyph_buf = malloc((size_t)cell_w * cell_h * sizeof(uint16_t));
if (glyph_buf == NULL) {
return;
}
for (int i = 0; i < num; i++) {
char c = str[i];
if (c < FONT5X7_FIRST_CHAR || c > FONT5X7_LAST_CHAR) {
x += cell_w + scale;
continue;
}
const uint8_t *g = font5x7[c - FONT5X7_FIRST_CHAR];
for (int gy = 0; gy < FONT5X7_H; gy++) {
for (int gx = 0; gx < FONT5X7_W; gx++) {
uint16_t color = ((g[gy] >> gx) & 1) ? fg : bg;
for (int sy = 0; sy < scale; sy++) {
for (int sx = 0; sx < scale; sx++) {
glyph_buf[(gy * scale + sy) * cell_w + (gx * scale + sx)] = color;
}
}
}
}
display_draw_bitmap(x, y, x + cell_w, y + cell_h, glyph_buf);
x += cell_w + scale;
}
free(glyph_buf);
}
/* ------------------------------------------------------------------ */
/* 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;
}