/* * MTG RFID Companion - ST7789 display manager (Core 1) */ #include #include #include #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; }