281 lines
8.1 KiB
C
281 lines
8.1 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];
|
|
/* Row-major [row][col] so the linear buffer matches what
|
|
* esp_lcd_panel_draw_bitmap expects. */
|
|
uint16_t cell[FONT5X7_H][FONT5X7_W];
|
|
|
|
for (int gy = 0; gy < FONT5X7_H; gy++) {
|
|
for (int gx = 0; gx < FONT5X7_W; gx++) {
|
|
cell[gy][gx] = (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;
|
|
} |