#include "display.h" #include #include #include #include #include "driver/gpio.h" #include "driver/spi_master.h" #include "esp_check.h" #include "esp_log.h" #include "esp_rom_sys.h" #include "font5x7.h" #define TAG "display" #define PIN_NUM_MOSI 35 #define PIN_NUM_CLK 36 #define PIN_NUM_CS 42 #define PIN_NUM_DC 40 #define PIN_NUM_RST 41 #define PIN_NUM_BL 45 #define PIN_NUM_TFT_POWER 7 #define LCD_HOST SPI2_HOST #define ST7789_SWRESET 0x01 #define ST7789_SLPOUT 0x11 #define ST7789_COLMOD 0x3A #define ST7789_MADCTL 0x36 #define ST7789_CASET 0x2A #define ST7789_RASET 0x2B #define ST7789_RAMWR 0x2C #define ST7789_DISPON 0x29 #define ST7789_INVON 0x21 #define X_OFFSET 40 #define Y_OFFSET 52 #define CHAR_SCALE 2 #define CHAR_W (6 * CHAR_SCALE) #define CHAR_H (8 * CHAR_SCALE) #define CHAR_SCALE_SMALL 1 #define CHAR_W_SMALL (6 * CHAR_SCALE_SMALL) #define CHAR_H_SMALL (8 * CHAR_SCALE_SMALL) static spi_device_handle_t s_lcd = NULL; static bool s_initialized = false; static esp_err_t lcd_send_cmd(uint8_t cmd) { gpio_set_level(PIN_NUM_DC, 0); spi_transaction_t t = { .length = 8, .tx_buffer = &cmd, }; return spi_device_transmit(s_lcd, &t); } static esp_err_t lcd_send_data(const void *data, size_t len) { if (len == 0) { return ESP_OK; } gpio_set_level(PIN_NUM_DC, 1); spi_transaction_t t = { .length = len * 8, .tx_buffer = data, }; return spi_device_transmit(s_lcd, &t); } static esp_err_t lcd_set_window(int x, int y, int w, int h) { if (w <= 0 || h <= 0) { return ESP_ERR_INVALID_ARG; } uint16_t x0 = (uint16_t)(x + X_OFFSET); uint16_t x1 = (uint16_t)(x + w - 1 + X_OFFSET); uint16_t y0 = (uint16_t)(y + Y_OFFSET); uint16_t y1 = (uint16_t)(y + h - 1 + Y_OFFSET); uint8_t col[4] = { x0 >> 8, x0 & 0xFF, x1 >> 8, x1 & 0xFF }; uint8_t row[4] = { y0 >> 8, y0 & 0xFF, y1 >> 8, y1 & 0xFF }; ESP_RETURN_ON_ERROR(lcd_send_cmd(ST7789_CASET), TAG, "CASET failed"); ESP_RETURN_ON_ERROR(lcd_send_data(col, sizeof(col)), TAG, "CASET data failed"); ESP_RETURN_ON_ERROR(lcd_send_cmd(ST7789_RASET), TAG, "RASET failed"); ESP_RETURN_ON_ERROR(lcd_send_data(row, sizeof(row)), TAG, "RASET data failed"); ESP_RETURN_ON_ERROR(lcd_send_cmd(ST7789_RAMWR), TAG, "RAMWR failed"); return ESP_OK; } static esp_err_t lcd_hw_reset(void) { gpio_set_level(PIN_NUM_RST, 1); esp_rom_delay_us(10000); gpio_set_level(PIN_NUM_RST, 0); esp_rom_delay_us(10000); gpio_set_level(PIN_NUM_RST, 1); esp_rom_delay_us(120000); return ESP_OK; } static esp_err_t lcd_panel_init(void) { ESP_RETURN_ON_ERROR(lcd_hw_reset(), TAG, "HW reset failed"); ESP_RETURN_ON_ERROR(lcd_send_cmd(ST7789_SWRESET), TAG, "SWRESET failed"); esp_rom_delay_us(150000); ESP_RETURN_ON_ERROR(lcd_send_cmd(ST7789_SLPOUT), TAG, "SLPOUT failed"); esp_rom_delay_us(120000); const uint8_t colmod = 0x55; // 16-bit ESP_RETURN_ON_ERROR(lcd_send_cmd(ST7789_COLMOD), TAG, "COLMOD cmd failed"); ESP_RETURN_ON_ERROR(lcd_send_data(&colmod, 1), TAG, "COLMOD data failed"); esp_rom_delay_us(10000); const uint8_t madctl = 0xA0; // MV | MY, landscape for Reverse TFT Feather ESP_RETURN_ON_ERROR(lcd_send_cmd(ST7789_MADCTL), TAG, "MADCTL cmd failed"); ESP_RETURN_ON_ERROR(lcd_send_data(&madctl, 1), TAG, "MADCTL data failed"); ESP_RETURN_ON_ERROR(lcd_send_cmd(ST7789_INVON), TAG, "INVON failed"); esp_rom_delay_us(10000); ESP_RETURN_ON_ERROR(lcd_send_cmd(ST7789_DISPON), TAG, "DISPON failed"); esp_rom_delay_us(120000); gpio_set_level(PIN_NUM_BL, 1); return ESP_OK; } static inline uint16_t clamp_u16_color(uint16_t c) { return (uint16_t)((c >> 8) | (c << 8)); // swap endian for wire order } esp_err_t display_init(void) { if (s_initialized) { return ESP_OK; } gpio_config_t io_conf = { .pin_bit_mask = (1ULL << PIN_NUM_DC) | (1ULL << PIN_NUM_RST) | (1ULL << PIN_NUM_BL) | (1ULL << PIN_NUM_TFT_POWER), .mode = GPIO_MODE_OUTPUT, .pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; ESP_RETURN_ON_ERROR(gpio_config(&io_conf), TAG, "gpio_config failed"); // Reverse TFT Feather requires TFT/I2C power gate enabled for panel logic. gpio_set_level(PIN_NUM_TFT_POWER, 1); esp_rom_delay_us(10000); spi_bus_config_t buscfg = { .mosi_io_num = PIN_NUM_MOSI, .miso_io_num = -1, .sclk_io_num = PIN_NUM_CLK, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = DISPLAY_WIDTH * 20 * sizeof(uint16_t), }; ESP_RETURN_ON_ERROR(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO), TAG, "spi_bus_initialize failed"); spi_device_interface_config_t devcfg = { .clock_speed_hz = 40 * 1000 * 1000, .mode = 0, .spics_io_num = PIN_NUM_CS, .queue_size = 4, }; ESP_RETURN_ON_ERROR(spi_bus_add_device(LCD_HOST, &devcfg, &s_lcd), TAG, "spi_bus_add_device failed"); ESP_RETURN_ON_ERROR(lcd_panel_init(), TAG, "panel init failed"); s_initialized = true; ESP_RETURN_ON_ERROR(display_clear(RGB565_BLACK), TAG, "clear failed"); ESP_LOGI(TAG, "ST7789 initialized"); return ESP_OK; } esp_err_t display_fill_rect(int x, int y, int w, int h, uint16_t color) { if (!s_initialized) { return ESP_ERR_INVALID_STATE; } if (x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT || w <= 0 || h <= 0) { return ESP_OK; } if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (x + w > DISPLAY_WIDTH) { w = DISPLAY_WIDTH - x; } if (y + h > DISPLAY_HEIGHT) { h = DISPLAY_HEIGHT - y; } if (w <= 0 || h <= 0) { return ESP_OK; } const uint16_t wire_color = clamp_u16_color(color); const int chunk_pixels = DISPLAY_WIDTH * 8; static uint16_t line_buf[DISPLAY_WIDTH * 8]; for (int i = 0; i < chunk_pixels; ++i) { line_buf[i] = wire_color; } ESP_RETURN_ON_ERROR(lcd_set_window(x, y, w, h), TAG, "set window failed"); int total = w * h; while (total > 0) { int batch = total > chunk_pixels ? chunk_pixels : total; ESP_RETURN_ON_ERROR(lcd_send_data(line_buf, batch * sizeof(uint16_t)), TAG, "fill data failed"); total -= batch; } return ESP_OK; } esp_err_t display_clear(uint16_t color) { return display_fill_rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, color); } static esp_err_t draw_char_scaled(int x, int y, char c, int scale, uint16_t fg, uint16_t bg) { if (c < 0x20 || c > 0x7F) { c = '?'; } if (scale <= 0) { return ESP_ERR_INVALID_ARG; } const uint8_t *glyph = font5x7[(uint8_t)c - 0x20]; for (int col = 0; col < 5; ++col) { uint8_t bits = glyph[col]; for (int row = 0; row < 7; ++row) { bool on = (bits >> row) & 0x1; ESP_RETURN_ON_ERROR( display_fill_rect(x + (col * scale), y + (row * scale), scale, scale, on ? fg : bg), TAG, "pixel draw failed"); } } /* spacing column + bottom row */ ESP_RETURN_ON_ERROR(display_fill_rect(x + (5 * scale), y, scale, 7 * scale, bg), TAG, "spacing failed"); ESP_RETURN_ON_ERROR(display_fill_rect(x, y + (7 * scale), 6 * scale, scale, bg), TAG, "baseline failed"); return ESP_OK; } static esp_err_t draw_text_scaled(int x, int y, const char *text, int scale, uint16_t fg, uint16_t bg) { if (!s_initialized) { return ESP_ERR_INVALID_STATE; } if (!text || scale <= 0) { return ESP_ERR_INVALID_ARG; } const int char_w = 6 * scale; const int char_h = 8 * scale; int cursor_x = x; int cursor_y = y; for (size_t i = 0; text[i] != '\0'; ++i) { char c = text[i]; if (c == '\n') { cursor_x = x; cursor_y += char_h; continue; } if (cursor_x + char_w > DISPLAY_WIDTH) { cursor_x = x; cursor_y += char_h; } if (cursor_y + char_h > DISPLAY_HEIGHT) { break; } ESP_RETURN_ON_ERROR(draw_char_scaled(cursor_x, cursor_y, c, scale, fg, bg), TAG, "draw_char failed"); cursor_x += char_w; } return ESP_OK; } esp_err_t display_draw_text(int x, int y, const char *text, uint16_t fg, uint16_t bg) { return draw_text_scaled(x, y, text, CHAR_SCALE, fg, bg); } esp_err_t display_draw_text_small(int x, int y, const char *text, uint16_t fg, uint16_t bg) { return draw_text_scaled(x, y, text, CHAR_SCALE_SMALL, fg, bg); } esp_err_t display_draw_text_centered(int y, const char *text, uint16_t fg, uint16_t bg) { int x; if (text == NULL) { return ESP_ERR_INVALID_ARG; } x = (DISPLAY_WIDTH - ((int)strlen(text) * CHAR_W)) / 2; if (x < 0) { x = 0; } return display_draw_text(x, y, text, fg, bg); } esp_err_t display_draw_text_small_centered(int y, const char *text, uint16_t fg, uint16_t bg) { int x; if (text == NULL) { return ESP_ERR_INVALID_ARG; } x = (DISPLAY_WIDTH - ((int)strlen(text) * CHAR_W_SMALL)) / 2; if (x < 0) { x = 0; } return display_draw_text_small(x, y, text, fg, bg); } esp_err_t display_draw_text_inverse(int x, int y, const char *text, uint16_t bg, uint16_t fg) { int w; int h = CHAR_H; if (text == NULL) { return ESP_ERR_INVALID_ARG; } w = (int)strlen(text) * CHAR_W; ESP_RETURN_ON_ERROR(display_fill_rect(x, y, w, h, bg), TAG, "inverse bg fill failed"); return display_draw_text(x, y, text, fg, bg); } esp_err_t display_draw_button_status(int btn_id, bool pressed) { if (btn_id < 0 || btn_id > 2) { return ESP_ERR_INVALID_ARG; } const int x = 10; const int y = 30 + btn_id * 32; const int w = DISPLAY_WIDTH - 20; const int h = 24; uint16_t box_color = pressed ? RGB565_GREEN : RGB565_RED; uint16_t text_color = RGB565_WHITE; char line[32]; snprintf(line, sizeof(line), "D%d: %s", btn_id, pressed ? "PRESSED" : "RELEASED"); ESP_RETURN_ON_ERROR(display_fill_rect(x, y, w, h, box_color), TAG, "status box failed"); ESP_RETURN_ON_ERROR(display_draw_text(x + 6, y + 8, line, text_color, box_color), TAG, "status text failed"); return ESP_OK; }