/** **************************************************************************************************** * @file spilcd.c * @author HiFi Player Project * @version V1.0 * @date 2026-01-01 * @brief SPI LCD(ST7789,320x240)驱动 * @note 初始化流程与引脚定义参考正点原子 DNESP32S3 标准例程(10_spilcd / 30_music), * 并增加了“刷新完成信号量”,方便 LVGL 在 flush 时等待 DMA 传输结束。 **************************************************************************************************** */ #include "spilcd.h" #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "esp_lcd_panel_st7789.h" #include "esp_heap_caps.h" #include "esp_log.h" #define TAG "spilcd" /* 1: 2.4 寸 320x240 SPILCD 0: 1.3 寸 240x240 SPILCD */ #define SPI_LCD_TYPE 1 esp_lcd_panel_handle_t panel_handle = NULL; static esp_lcd_panel_io_handle_t s_io_handle = NULL; /* LCD SPI IO 句柄 */ static SemaphoreHandle_t s_flush_sem = NULL; /* SPI 传输完成信号量 */ static uint16_t s_width = SPI_LCD_H_RES; static uint16_t s_height = SPI_LCD_V_RES; /** * @brief LCD 颜色传输完成回调(SPI ISR 上下文) */ static bool lcd_color_trans_done_cb(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) { BaseType_t high_task_woken = pdFALSE; if (s_flush_sem != NULL) { xSemaphoreGiveFromISR(s_flush_sem, &high_task_woken); } (void)panel_io; (void)edata; (void)user_ctx; return (high_task_woken == pdTRUE); } /** * @brief 初始化 SPI LCD */ esp_err_t spilcd_init(void) { if (s_flush_sem == NULL) { s_flush_sem = xSemaphoreCreateBinary(); if (s_flush_sem == NULL) { ESP_LOGE(TAG, "create flush semaphore failed"); return ESP_ERR_NO_MEM; } } /* 打开 LCD 电源 */ LCD_PWR(1); vTaskDelay(pdMS_TO_TICKS(50)); /* 硬件复位 */ LCD_RST(0); vTaskDelay(pdMS_TO_TICKS(20)); LCD_RST(1); vTaskDelay(pdMS_TO_TICKS(120)); /* ---------------- 挂载到 SPI 总线 ---------------- */ esp_lcd_panel_io_spi_config_t io_config = { .dc_gpio_num = LCD_DC_PIN, .cs_gpio_num = LCD_CS_PIN, .pclk_hz = 60 * 1000 * 1000, /* 60MHz */ .lcd_cmd_bits = 8, .lcd_param_bits = 8, .spi_mode = 0, .trans_queue_depth = 7, }; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &s_io_handle)); /* ---------------- 创建 ST7789 面板 ---------------- */ esp_lcd_panel_dev_config_t panel_config = { .reset_gpio_num = GPIO_NUM_NC, /* 复位由 XL9555 控制 */ .rgb_ele_order = COLOR_RGB_ELEMENT_ORDER_RGB, .bits_per_pixel = 16, .data_endian = LCD_RGB_DATA_ENDIAN_BIG, /* 屏幕要求高字节在前 */ }; ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(s_io_handle, &panel_config, &panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); /* 注册颜色传输完成回调 */ const esp_lcd_panel_io_callbacks_t cbs = { .on_color_trans_done = lcd_color_trans_done_cb, }; ESP_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(s_io_handle, &cbs, NULL)); spilcd_display_dir(1); /* 横屏 */ spilcd_clear(0x0000); /* 上电先刷黑,避免白屏闪烁 */ return ESP_OK; } /** * @brief 设置屏幕方向 */ void spilcd_display_dir(uint8_t dir) { if (dir == 0) /* 竖屏 */ { s_width = SPI_LCD_V_RES; s_height = SPI_LCD_H_RES; esp_lcd_panel_swap_xy(panel_handle, false); esp_lcd_panel_mirror(panel_handle, false, false); if (SPI_LCD_TYPE == 0) { esp_lcd_panel_set_gap(panel_handle, 0, 80); } } else /* 横屏 */ { s_width = SPI_LCD_H_RES; s_height = SPI_LCD_V_RES; esp_lcd_panel_swap_xy(panel_handle, true); esp_lcd_panel_mirror(panel_handle, true, false); if (SPI_LCD_TYPE == 0) { esp_lcd_panel_set_gap(panel_handle, 80, 0); } } } /** * @brief 将一块数据刷到屏幕,并等待 DMA 传输完成 */ void spilcd_flush(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, const void *data) { if ((panel_handle == NULL) || (data == NULL) || (s_flush_sem == NULL)) { return; } if (x2 > s_width) x2 = s_width; if (y2 > s_height) y2 = s_height; if ((x1 >= x2) || (y1 >= y2)) { return; } /* 丢弃上一次传输残留的信号 */ while (xSemaphoreTake(s_flush_sem, 0) == pdTRUE) { /* nothing */ } esp_err_t ret = esp_lcd_panel_draw_bitmap(panel_handle, x1, y1, x2, y2, data); if (ret != ESP_OK) { ESP_LOGE(TAG, "draw bitmap failed: %s", esp_err_to_name(ret)); return; } if (xSemaphoreTake(s_flush_sem, pdMS_TO_TICKS(200)) != pdTRUE) { ESP_LOGW(TAG, "wait lcd flush timeout"); } } /** * @brief 清屏 */ void spilcd_clear(uint16_t color) { const uint16_t lines = 24; size_t px_count = (size_t)s_width * lines; uint16_t *buffer = heap_caps_malloc(px_count * sizeof(uint16_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); if (buffer == NULL) { ESP_LOGE(TAG, "no memory for clear buffer"); return; } /* 转换成大端字节序 */ uint16_t color_be = (uint16_t)((color << 8) | (color >> 8)); for (size_t i = 0; i < px_count; i++) { buffer[i] = color_be; } for (uint16_t y = 0; y < s_height; y += lines) { uint16_t y_end = (uint16_t)((y + lines) > s_height ? s_height : (y + lines)); spilcd_flush(0, y, s_width, y_end, buffer); } heap_caps_free(buffer); }