HiFi player project, first commit.

This commit is contained in:
francis.wang committed 2026-10-26 20:07:12 +08:00
commit 4126ea13a1
49 files changed
+6333

No files matched your search

+215
View File
@@ -0,0 +1,215 @@
/**
****************************************************************************************************
* @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);
}
+67
View File
@@ -0,0 +1,67 @@
/**
****************************************************************************************************
* @file spilcd.h
* @author HiFi Player Project
* @version V1.0
* @date 2026-01-01
* @brief SPI LCD(ST7789,320x240)驱动
* @note 引脚与初始化流程参考正点原子 DNESP32S3 标准例程(10_spilcd / 30_music)
****************************************************************************************************
*/
#ifndef __SPILCD_H
#define __SPILCD_H
#include <stdint.h>
#include "esp_err.h"
#include "driver/gpio.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "xl9555.h"
/* ---------------- 管脚声明 ---------------- */
#define LCD_DC_PIN GPIO_NUM_40 /* LCD 数据/命令选择脚 */
#define LCD_CS_PIN GPIO_NUM_21 /* LCD 片选脚 */
#define LCD_HOST SPI2_HOST /* LCD 与 SD 卡共用 SPI2 */
/* 背光 / 复位 / 电源由 XL9555 扩展 IO 控制 */
#define LCD_PWR(x) do { x ? xl9555_pin_write(SLCD_PWR_IO, 1) : xl9555_pin_write(SLCD_PWR_IO, 0); } while (0)
#define LCD_RST(x) do { x ? xl9555_pin_write(SLCD_RST_IO, 1) : xl9555_pin_write(SLCD_RST_IO, 0); } while (0)
#define LCD_BL(x) do { x ? xl9555_pin_write(LCD_BL_IO, 1) : xl9555_pin_write(LCD_BL_IO, 0); } while (0)
/* ---------------- 屏幕分辨率(横屏) ---------------- */
#define SPI_LCD_H_RES 320
#define SPI_LCD_V_RES 240
/* 面板句柄,LVGL 移植层需要用到 */
extern esp_lcd_panel_handle_t panel_handle;
/**
* @brief 初始化 SPI LCD(SPI 总线必须已由 my_spi_init() 初始化)
* @retval ESP_OK 成功
*/
esp_err_t spilcd_init(void);
/**
* @brief 设置屏幕方向
* @param dir 0: 竖屏 1: 横屏
*/
void spilcd_display_dir(uint8_t dir);
/**
* @brief 用单色清屏
*/
void spilcd_clear(uint16_t color);
/**
* @brief 将一块 RGB565 图像数据刷到屏幕(阻塞,直到 SPI 传输完成)
* @param x1,y1 左上角坐标(包含)
* @param x2,y2 右下角坐标(不包含,即 x2-x1 为宽度)
* @param data RGB565 像素数据,**按大端字节序存放**(高字节在前)。
* 如果是 LVGL 的原生小端数据,需要在调用前自行做字节交换
* (参考 lvgl_port.c 中 disp_flush_cb() 的实现)。
* @note 该函数供 LVGL 的 flush_cb 调用,也可以在其它地方直接使用
*/
void spilcd_flush(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, const void *data);
#endif