#include "platform.h" #include #include #include #include "driver/gpio.h" #include "driver/spi_master.h" #include "esp_check.h" #include "esp_heap_caps.h" #include "esp_lcd_panel_io.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_vendor.h" #include "esp_log.h" #include "sdkconfig.h" static const char *TAG = "display_st7789"; #define ST7789_SPI_HOST SPI2_HOST #define ST7789_COLOR_BYTES_PER_PIXEL 2 #ifndef CONFIG_TQ_SCREEN_SPI_MODE #define CONFIG_TQ_SCREEN_SPI_MODE 0 #endif #ifndef CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH #define CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH 1 #endif #if CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH #define ST7789_BACKLIGHT_ON_LEVEL 1 #define ST7789_BACKLIGHT_OFF_LEVEL 0 #else #define ST7789_BACKLIGHT_ON_LEVEL 0 #define ST7789_BACKLIGHT_OFF_LEVEL 1 #endif typedef struct { esp_lcd_panel_io_handle_t io_handle; esp_lcd_panel_handle_t panel_handle; bool bus_owned; bool initialized; } st7789_display_state_t; static st7789_display_state_t s_display; #if CONFIG_TQ_SCREEN_COLOR_ORDER_BGR #define ST7789_COLOR_ORDER LCD_RGB_ELEMENT_ORDER_BGR #else #define ST7789_COLOR_ORDER LCD_RGB_ELEMENT_ORDER_RGB #endif #if CONFIG_TQ_SCREEN_SWAP_XY #define ST7789_SWAP_XY true #else #define ST7789_SWAP_XY false #endif #if CONFIG_TQ_SCREEN_MIRROR_X #define ST7789_MIRROR_X true #else #define ST7789_MIRROR_X false #endif #if CONFIG_TQ_SCREEN_MIRROR_Y #define ST7789_MIRROR_Y true #else #define ST7789_MIRROR_Y false #endif static uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b) { return (uint16_t)(((uint16_t)(r & 0xF8) << 8) | ((uint16_t)(g & 0xFC) << 3) | ((uint16_t)(b & 0xF8) >> 3)); } static uint16_t test_pattern_color(int x, int y, int h_res, int v_res) { int top = v_res / 3; int middle = (v_res * 2) / 3; if (y < top) { uint8_t r = (uint8_t)((x * 255) / (h_res - 1)); uint8_t g = (uint8_t)(255 - r); return rgb565(r, g, 48); } if (y < middle) { uint8_t gray = (uint8_t)(((y - top) * 255) / ((middle - top) > 1 ? (middle - top - 1) : 1)); return rgb565(gray, gray, gray); } bool checker = (((x / 16) + (y / 16)) & 1) != 0; return checker ? rgb565(250, 250, 250) : rgb565(8, 8, 8); } static void fill_test_pattern_chunk(uint16_t *buf, int y_start, int lines, int h_res, int v_res) { for (int row = 0; row < lines; ++row) { int y = y_start + row; for (int x = 0; x < h_res; ++x) { uint16_t color = test_pattern_color(x, y, h_res, v_res); if (x == 0 || y == 0 || x == (h_res - 1) || y == (v_res - 1)) { color = rgb565(255, 0, 0); } else if (x == (h_res / 2) || y == (v_res / 2)) { color = rgb565(255, 255, 255); } buf[row * h_res + x] = color; } } } void platform_display_deinit(void) { #if CONFIG_TQ_SCREEN_ENABLE if (CONFIG_TQ_SCREEN_BACKLIGHT_PIN >= 0) { (void)gpio_set_level(CONFIG_TQ_SCREEN_BACKLIGHT_PIN, ST7789_BACKLIGHT_OFF_LEVEL); } if (s_display.panel_handle != NULL) { (void)esp_lcd_panel_del(s_display.panel_handle); s_display.panel_handle = NULL; } if (s_display.io_handle != NULL) { (void)esp_lcd_panel_io_del(s_display.io_handle); s_display.io_handle = NULL; } if (s_display.bus_owned) { (void)spi_bus_free(ST7789_SPI_HOST); s_display.bus_owned = false; } s_display.initialized = false; #endif } esp_err_t platform_display_init(void) { #if !CONFIG_TQ_SCREEN_ENABLE return ESP_ERR_NOT_SUPPORTED; #else if (s_display.initialized) { return ESP_OK; } if (CONFIG_TQ_SCREEN_BACKLIGHT_PIN >= 0) { ESP_RETURN_ON_ERROR(gpio_set_level(CONFIG_TQ_SCREEN_BACKLIGHT_PIN, ST7789_BACKLIGHT_OFF_LEVEL), TAG, "set backlight off failed"); } const size_t max_transfer_size = (size_t)CONFIG_TQ_SCREEN_H_RES * (size_t)CONFIG_TQ_SCREEN_DRAW_LINES * ST7789_COLOR_BYTES_PER_PIXEL + 8; spi_bus_config_t bus_cfg = { .sclk_io_num = CONFIG_TQ_SPI_SHARED_CLK_PIN, .mosi_io_num = CONFIG_TQ_SPI_SHARED_MOSI_PIN, .miso_io_num = -1, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = (int)max_transfer_size, }; esp_err_t err = spi_bus_initialize(ST7789_SPI_HOST, &bus_cfg, SPI_DMA_CH_AUTO); if (err == ESP_OK) { s_display.bus_owned = true; } else if (err == ESP_ERR_INVALID_STATE) { s_display.bus_owned = false; ESP_LOGW(TAG, "SPI host already initialized, reuse host=%d", ST7789_SPI_HOST); } else { return err; } esp_lcd_panel_io_spi_config_t io_cfg = { .cs_gpio_num = CONFIG_TQ_SCREEN_CS_PIN, .dc_gpio_num = CONFIG_TQ_SCREEN_DC_PIN, .spi_mode = CONFIG_TQ_SCREEN_SPI_MODE, .pclk_hz = CONFIG_TQ_SCREEN_PIXEL_CLOCK_HZ, .trans_queue_depth = 1, .lcd_cmd_bits = 8, .lcd_param_bits = 8, }; err = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)ST7789_SPI_HOST, &io_cfg, &s_display.io_handle); if (err != ESP_OK) { platform_display_deinit(); return err; } esp_lcd_panel_dev_config_t panel_cfg = { .reset_gpio_num = CONFIG_TQ_SCREEN_RESET_PIN, .rgb_ele_order = ST7789_COLOR_ORDER, .bits_per_pixel = 16, }; err = esp_lcd_new_panel_st7789(s_display.io_handle, &panel_cfg, &s_display.panel_handle); if (err != ESP_OK) { platform_display_deinit(); return err; } err = esp_lcd_panel_reset(s_display.panel_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "panel reset failed: %s", esp_err_to_name(err)); platform_display_deinit(); return err; } err = esp_lcd_panel_init(s_display.panel_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "panel init failed: %s", esp_err_to_name(err)); platform_display_deinit(); return err; } err = esp_lcd_panel_swap_xy(s_display.panel_handle, ST7789_SWAP_XY); if (err != ESP_OK) { ESP_LOGE(TAG, "swap xy failed: %s", esp_err_to_name(err)); platform_display_deinit(); return err; } err = esp_lcd_panel_mirror(s_display.panel_handle, ST7789_MIRROR_X, ST7789_MIRROR_Y); if (err != ESP_OK) { ESP_LOGE(TAG, "mirror failed: %s", esp_err_to_name(err)); platform_display_deinit(); return err; } err = esp_lcd_panel_set_gap(s_display.panel_handle, CONFIG_TQ_SCREEN_X_GAP, CONFIG_TQ_SCREEN_Y_GAP); if (err != ESP_OK) { ESP_LOGE(TAG, "set gap failed: %s", esp_err_to_name(err)); platform_display_deinit(); return err; } err = esp_lcd_panel_invert_color(s_display.panel_handle, false); if (err != ESP_OK) { ESP_LOGE(TAG, "invert control failed: %s", esp_err_to_name(err)); platform_display_deinit(); return err; } err = esp_lcd_panel_disp_on_off(s_display.panel_handle, true); if (err != ESP_OK) { ESP_LOGE(TAG, "display on failed: %s", esp_err_to_name(err)); platform_display_deinit(); return err; } if (CONFIG_TQ_SCREEN_BACKLIGHT_PIN >= 0) { err = gpio_set_level(CONFIG_TQ_SCREEN_BACKLIGHT_PIN, ST7789_BACKLIGHT_ON_LEVEL); if (err != ESP_OK) { ESP_LOGE(TAG, "set backlight on failed: %s", esp_err_to_name(err)); platform_display_deinit(); return err; } } s_display.initialized = true; ESP_LOGI(TAG, "st7789 ready: spi_host=%d sclk=%d mosi=%d dc=%d cs=%d rst=%d bk=%d bk_on=%d spi_mode=%d size=%dx%d pclk=%u", ST7789_SPI_HOST, CONFIG_TQ_SPI_SHARED_CLK_PIN, CONFIG_TQ_SPI_SHARED_MOSI_PIN, CONFIG_TQ_SCREEN_DC_PIN, CONFIG_TQ_SCREEN_CS_PIN, CONFIG_TQ_SCREEN_RESET_PIN, CONFIG_TQ_SCREEN_BACKLIGHT_PIN, ST7789_BACKLIGHT_ON_LEVEL, CONFIG_TQ_SCREEN_SPI_MODE, CONFIG_TQ_SCREEN_H_RES, CONFIG_TQ_SCREEN_V_RES, (unsigned)CONFIG_TQ_SCREEN_PIXEL_CLOCK_HZ); return ESP_OK; #endif } esp_err_t platform_display_show_test_pattern(void) { #if !CONFIG_TQ_SCREEN_ENABLE return ESP_ERR_NOT_SUPPORTED; #else ESP_RETURN_ON_ERROR(platform_display_init(), TAG, "display init failed"); const int h_res = CONFIG_TQ_SCREEN_H_RES; const int v_res = CONFIG_TQ_SCREEN_V_RES; const int draw_lines = CONFIG_TQ_SCREEN_DRAW_LINES > 0 ? CONFIG_TQ_SCREEN_DRAW_LINES : 1; const size_t buf_pixels = (size_t)h_res * (size_t)draw_lines; const size_t buf_size = buf_pixels * ST7789_COLOR_BYTES_PER_PIXEL; uint16_t *draw_buf = (uint16_t *)heap_caps_malloc(buf_size, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); if (draw_buf == NULL) { draw_buf = (uint16_t *)heap_caps_malloc(buf_size, MALLOC_CAP_8BIT); } ESP_RETURN_ON_FALSE(draw_buf != NULL, ESP_ERR_NO_MEM, TAG, "alloc draw buffer failed"); ESP_LOGI(TAG, "draw test pattern start"); esp_err_t err = ESP_OK; for (int y = 0; y < v_res && err == ESP_OK; y += draw_lines) { int chunk_lines = draw_lines; if (y + chunk_lines > v_res) { chunk_lines = v_res - y; } fill_test_pattern_chunk(draw_buf, y, chunk_lines, h_res, v_res); err = esp_lcd_panel_draw_bitmap(s_display.panel_handle, 0, y, h_res, y + chunk_lines, draw_buf); if (err != ESP_OK) { break; } // Wait for queued color transfer to finish before reusing draw_buf. err = esp_lcd_panel_io_tx_param(s_display.io_handle, -1, NULL, 0); } heap_caps_free(draw_buf); if (err == ESP_OK) { ESP_LOGI(TAG, "draw test pattern done"); } return err; #endif }