Files

216 lines
6.2 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
****************************************************************************************************
* @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 <string.h>
#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);
}