Rdzleo 4547e9e732 适配ESP32-C3开发板成功
1、原有ESP32-S3功能基本实现,有些小Bug需要修复;
2、RAM内存不够导致Img界面图片显示画质不完整,并且当前未开启蓝牙功能,开机蓝牙功能后RAM内存压力加剧;
2026-02-12 15:45:36 +08:00

406 lines
14 KiB
C
Raw 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.

#include "gpio.h"
#include "esp_lvgl_port.h"
#include "esp_lcd_st77916.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lcd.h"
#include "esp_lcd_touch_cst816s.h"
#include <string.h>
#include "esp_heap_caps.h"
static lv_disp_t * disp_handle = NULL;
static esp_lcd_panel_handle_t panel_handle = NULL;
static esp_lcd_panel_io_handle_t io_handle = NULL;
static esp_lcd_touch_handle_t touch_handle;
static esp_lcd_panel_io_handle_t tp_io_handle = NULL;
// 从参考项目 spi_lcd 提取的正确LCD初始化命令匹配当前LCD面板硬件
static const st77916_lcd_init_cmd_t lcd_init_cmds[] = {
{0xF0, (uint8_t[]){0x28}, 1, 0},
{0xF2, (uint8_t[]){0x28}, 1, 0},
{0x73, (uint8_t[]){0xF0}, 1, 0},
{0x7C, (uint8_t[]){0xD1}, 1, 0},
{0x83, (uint8_t[]){0xE0}, 1, 0},
{0x84, (uint8_t[]){0x61}, 1, 0},
{0xF2, (uint8_t[]){0x82}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0xF0, (uint8_t[]){0x01}, 1, 0},
{0xF1, (uint8_t[]){0x01}, 1, 0},
{0xB0, (uint8_t[]){0x5E}, 1, 0},
{0xB1, (uint8_t[]){0x55}, 1, 0},
{0xB2, (uint8_t[]){0x24}, 1, 0},
{0xB3, (uint8_t[]){0x01}, 1, 0},
{0xB4, (uint8_t[]){0x87}, 1, 0},
{0xB5, (uint8_t[]){0x44}, 1, 0},
{0xB6, (uint8_t[]){0x8B}, 1, 0},
{0xB7, (uint8_t[]){0x40}, 1, 0},
{0xB8, (uint8_t[]){0x86}, 1, 0},
{0xB9, (uint8_t[]){0x15}, 1, 0},
{0xBA, (uint8_t[]){0x00}, 1, 0},
{0xBB, (uint8_t[]){0x08}, 1, 0},
{0xBC, (uint8_t[]){0x08}, 1, 0},
{0xBD, (uint8_t[]){0x00}, 1, 0},
{0xBE, (uint8_t[]){0x00}, 1, 0},
{0xBF, (uint8_t[]){0x07}, 1, 0},
{0xC0, (uint8_t[]){0x80}, 1, 0},
{0xC1, (uint8_t[]){0x10}, 1, 0},
{0xC2, (uint8_t[]){0x37}, 1, 0},
{0xC3, (uint8_t[]){0x80}, 1, 0},
{0xC4, (uint8_t[]){0x10}, 1, 0},
{0xC5, (uint8_t[]){0x37}, 1, 0},
{0xC6, (uint8_t[]){0xA9}, 1, 0},
{0xC7, (uint8_t[]){0x41}, 1, 0},
{0xC8, (uint8_t[]){0x01}, 1, 0},
{0xC9, (uint8_t[]){0xA9}, 1, 0},
{0xCA, (uint8_t[]){0x41}, 1, 0},
{0xCB, (uint8_t[]){0x01}, 1, 0},
{0xCC, (uint8_t[]){0x7F}, 1, 0},
{0xCD, (uint8_t[]){0x7F}, 1, 0},
{0xCE, (uint8_t[]){0xFF}, 1, 0},
{0xD0, (uint8_t[]){0x91}, 1, 0},
{0xD1, (uint8_t[]){0x68}, 1, 0},
{0xD2, (uint8_t[]){0x68}, 1, 0},
{0xF5, (uint8_t[]){0x00, 0xA5}, 2, 0},
{0xDD, (uint8_t[]){0x40}, 1, 0},
{0xDE, (uint8_t[]){0x40}, 1, 0},
{0xF1, (uint8_t[]){0x10}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0xF0, (uint8_t[]){0x02}, 1, 0},
{0xE0, (uint8_t[]){0xF0, 0x10, 0x18, 0x0D, 0x0C, 0x38, 0x3E, 0x44, 0x51, 0x39, 0x15, 0x15, 0x30, 0x34}, 14, 0},
{0xE1, (uint8_t[]){0xF0, 0x0F, 0x17, 0x0D, 0x0B, 0x07, 0x3E, 0x33, 0x51, 0x39, 0x15, 0x15, 0x30, 0x34}, 14, 0},
{0xF0, (uint8_t[]){0x10}, 1, 0},
{0xF3, (uint8_t[]){0x10}, 1, 0},
{0xE0, (uint8_t[]){0x08}, 1, 0},
{0xE1, (uint8_t[]){0x00}, 1, 0},
{0xE2, (uint8_t[]){0x00}, 1, 0},
{0xE3, (uint8_t[]){0x00}, 1, 0},
{0xE4, (uint8_t[]){0xE0}, 1, 0},
{0xE5, (uint8_t[]){0x06}, 1, 0},
{0xE6, (uint8_t[]){0x21}, 1, 0},
{0xE7, (uint8_t[]){0x03}, 1, 0},
{0xE8, (uint8_t[]){0x05}, 1, 0},
{0xE9, (uint8_t[]){0x02}, 1, 0},
{0xEA, (uint8_t[]){0xE9}, 1, 0},
{0xEB, (uint8_t[]){0x00}, 1, 0},
{0xEC, (uint8_t[]){0x00}, 1, 0},
{0xED, (uint8_t[]){0x14}, 1, 0},
{0xEE, (uint8_t[]){0xFF}, 1, 0},
{0xEF, (uint8_t[]){0x00}, 1, 0},
{0xF8, (uint8_t[]){0xFF}, 1, 0},
{0xF9, (uint8_t[]){0x00}, 1, 0},
{0xFA, (uint8_t[]){0x00}, 1, 0},
{0xFB, (uint8_t[]){0x30}, 1, 0},
{0xFC, (uint8_t[]){0x00}, 1, 0},
{0xFD, (uint8_t[]){0x00}, 1, 0},
{0xFE, (uint8_t[]){0x00}, 1, 0},
{0xFF, (uint8_t[]){0x00}, 1, 0},
{0x60, (uint8_t[]){0x40}, 1, 0},
{0x61, (uint8_t[]){0x05}, 1, 0},
{0x62, (uint8_t[]){0x00}, 1, 0},
{0x63, (uint8_t[]){0x42}, 1, 0},
{0x64, (uint8_t[]){0xDA}, 1, 0},
{0x65, (uint8_t[]){0x00}, 1, 0},
{0x66, (uint8_t[]){0x00}, 1, 0},
{0x67, (uint8_t[]){0x00}, 1, 0},
{0x68, (uint8_t[]){0x00}, 1, 0},
{0x69, (uint8_t[]){0x00}, 1, 0},
{0x6A, (uint8_t[]){0x00}, 1, 0},
{0x6B, (uint8_t[]){0x00}, 1, 0},
{0x70, (uint8_t[]){0x40}, 1, 0},
{0x71, (uint8_t[]){0x04}, 1, 0},
{0x72, (uint8_t[]){0x00}, 1, 0},
{0x73, (uint8_t[]){0x42}, 1, 0},
{0x74, (uint8_t[]){0xD9}, 1, 0},
{0x75, (uint8_t[]){0x00}, 1, 0},
{0x76, (uint8_t[]){0x00}, 1, 0},
{0x77, (uint8_t[]){0x00}, 1, 0},
{0x78, (uint8_t[]){0x00}, 1, 0},
{0x79, (uint8_t[]){0x00}, 1, 0},
{0x7A, (uint8_t[]){0x00}, 1, 0},
{0x7B, (uint8_t[]){0x00}, 1, 0},
{0x80, (uint8_t[]){0x48}, 1, 0},
{0x81, (uint8_t[]){0x00}, 1, 0},
{0x82, (uint8_t[]){0x07}, 1, 0},
{0x83, (uint8_t[]){0x02}, 1, 0},
{0x84, (uint8_t[]){0xD7}, 1, 0},
{0x85, (uint8_t[]){0x04}, 1, 0},
{0x86, (uint8_t[]){0x00}, 1, 0},
{0x87, (uint8_t[]){0x00}, 1, 0},
{0x88, (uint8_t[]){0x48}, 1, 0},
{0x89, (uint8_t[]){0x00}, 1, 0},
{0x8A, (uint8_t[]){0x09}, 1, 0},
{0x8B, (uint8_t[]){0x02}, 1, 0},
{0x8C, (uint8_t[]){0xD9}, 1, 0},
{0x8D, (uint8_t[]){0x04}, 1, 0},
{0x8E, (uint8_t[]){0x00}, 1, 0},
{0x8F, (uint8_t[]){0x00}, 1, 0},
{0x90, (uint8_t[]){0x48}, 1, 0},
{0x91, (uint8_t[]){0x00}, 1, 0},
{0x92, (uint8_t[]){0x0B}, 1, 0},
{0x93, (uint8_t[]){0x02}, 1, 0},
{0x94, (uint8_t[]){0xDB}, 1, 0},
{0x95, (uint8_t[]){0x04}, 1, 0},
{0x96, (uint8_t[]){0x00}, 1, 0},
{0x97, (uint8_t[]){0x00}, 1, 0},
{0x98, (uint8_t[]){0x48}, 1, 0},
{0x99, (uint8_t[]){0x00}, 1, 0},
{0x9A, (uint8_t[]){0x0D}, 1, 0},
{0x9B, (uint8_t[]){0x02}, 1, 0},
{0x9C, (uint8_t[]){0xDD}, 1, 0},
{0x9D, (uint8_t[]){0x04}, 1, 0},
{0x9E, (uint8_t[]){0x00}, 1, 0},
{0x9F, (uint8_t[]){0x00}, 1, 0},
{0xA0, (uint8_t[]){0x48}, 1, 0},
{0xA1, (uint8_t[]){0x00}, 1, 0},
{0xA2, (uint8_t[]){0x06}, 1, 0},
{0xA3, (uint8_t[]){0x02}, 1, 0},
{0xA4, (uint8_t[]){0xD6}, 1, 0},
{0xA5, (uint8_t[]){0x04}, 1, 0},
{0xA6, (uint8_t[]){0x00}, 1, 0},
{0xA7, (uint8_t[]){0x00}, 1, 0},
{0xA8, (uint8_t[]){0x48}, 1, 0},
{0xA9, (uint8_t[]){0x00}, 1, 0},
{0xAA, (uint8_t[]){0x08}, 1, 0},
{0xAB, (uint8_t[]){0x02}, 1, 0},
{0xAC, (uint8_t[]){0xD8}, 1, 0},
{0xAD, (uint8_t[]){0x04}, 1, 0},
{0xAE, (uint8_t[]){0x00}, 1, 0},
{0xAF, (uint8_t[]){0x00}, 1, 0},
{0xB0, (uint8_t[]){0x48}, 1, 0},
{0xB1, (uint8_t[]){0x00}, 1, 0},
{0xB2, (uint8_t[]){0x0A}, 1, 0},
{0xB3, (uint8_t[]){0x02}, 1, 0},
{0xB4, (uint8_t[]){0xDA}, 1, 0},
{0xB5, (uint8_t[]){0x04}, 1, 0},
{0xB6, (uint8_t[]){0x00}, 1, 0},
{0xB7, (uint8_t[]){0x00}, 1, 0},
{0xB8, (uint8_t[]){0x48}, 1, 0},
{0xB9, (uint8_t[]){0x00}, 1, 0},
{0xBA, (uint8_t[]){0x0C}, 1, 0},
{0xBB, (uint8_t[]){0x02}, 1, 0},
{0xBC, (uint8_t[]){0xDC}, 1, 0},
{0xBD, (uint8_t[]){0x04}, 1, 0},
{0xBE, (uint8_t[]){0x00}, 1, 0},
{0xBF, (uint8_t[]){0x00}, 1, 0},
{0xC0, (uint8_t[]){0x10}, 1, 0},
{0xC1, (uint8_t[]){0x47}, 1, 0},
{0xC2, (uint8_t[]){0x56}, 1, 0},
{0xC3, (uint8_t[]){0x65}, 1, 0},
{0xC4, (uint8_t[]){0x74}, 1, 0},
{0xC5, (uint8_t[]){0x88}, 1, 0},
{0xC6, (uint8_t[]){0x99}, 1, 0},
{0xC7, (uint8_t[]){0x01}, 1, 0},
{0xC8, (uint8_t[]){0xBB}, 1, 0},
{0xC9, (uint8_t[]){0xAA}, 1, 0},
{0xD0, (uint8_t[]){0x10}, 1, 0},
{0xD1, (uint8_t[]){0x47}, 1, 0},
{0xD2, (uint8_t[]){0x56}, 1, 0},
{0xD3, (uint8_t[]){0x65}, 1, 0},
{0xD4, (uint8_t[]){0x74}, 1, 0},
{0xD5, (uint8_t[]){0x88}, 1, 0},
{0xD6, (uint8_t[]){0x99}, 1, 0},
{0xD7, (uint8_t[]){0x01}, 1, 0},
{0xD8, (uint8_t[]){0xBB}, 1, 0},
{0xD9, (uint8_t[]){0xAA}, 1, 0},
{0xF3, (uint8_t[]){0x01}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0x3A, (uint8_t[]){0x55}, 1, 0},
{0x21, (uint8_t[]){0x00}, 1, 0},
{0x11, (uint8_t[]){0x00}, 1, 120},
{0x29, (uint8_t[]){0x00}, 1, 0},
};
void lcd_init(){
// 标准SPI总线配置CLK + MOSI无MISO
const spi_bus_config_t buscfg = {
.sclk_io_num = PIN_LCD_CLK,
.mosi_io_num = PIN_LCD_MOSI,
.miso_io_num = -1,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = LCD_HIGH * 80 * sizeof(uint16_t),
};
ESP_ERROR_CHECK(spi_bus_initialize(SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
// 标准SPI IO配置带DC引脚
esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = PIN_LCD_DC,
.cs_gpio_num = PIN_LCD_CS,
.pclk_hz = 60 * 1000 * 1000, // C3 SPI最大60MHz
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.spi_mode = 0,
.trans_queue_depth = 10,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI_LCD_HOST, &io_config, &io_handle));
// ST77916面板配置通过vendor_config注入正确的初始化命令
st77916_vendor_config_t vendor_cfg = {
.init_cmds = lcd_init_cmds,
.init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]),
};
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = PIN_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
.vendor_config = &vendor_cfg,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(io_handle, &panel_config, &panel_handle));
esp_lcd_panel_reset(panel_handle);
esp_lcd_panel_init(panel_handle);
esp_lcd_panel_mirror(panel_handle, false, false); // 与参考项目一致
// 清空LCD GRAM避免显示上次关机时的残留画面
size_t clear_buffer_size = LCD_WID * 40; // 每次清除40行
uint16_t *clear_buffer = heap_caps_malloc(clear_buffer_size * sizeof(uint16_t), MALLOC_CAP_DMA);
if (clear_buffer) {
memset(clear_buffer, 0, clear_buffer_size * sizeof(uint16_t));
for (int y = 0; y < LCD_HIGH; y += 40) {
int lines = (y + 40 > LCD_HIGH) ? (LCD_HIGH - y) : 40;
esp_lcd_panel_draw_bitmap(panel_handle, 0, y, LCD_WID, y + lines, clear_buffer);
}
heap_caps_free(clear_buffer);
ESP_LOGI(LCD_TAG, "LCD GRAM cleared (black filled)");
} else {
ESP_LOGE(LCD_TAG, "Failed to allocate clear buffer");
}
// 初始化时不打开显示等UI渲染完成后由main.c调用lcd_disp_on_off(true)
esp_lcd_panel_disp_on_off(panel_handle, false);
}
// 初始化触摸控制器
void touch_init(){
const esp_lcd_touch_config_t tp_cfg = {
.x_max = LCD_WID,
.y_max = LCD_HIGH,
.rst_gpio_num = PIN_TP_RST,
.int_gpio_num = PIN_TP_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0, // managed版CST816S不在驱动内变换base库会做单次变换
.mirror_x = 0, // 参考项目本地驱动+base库双重变换=恒等,实际传原始坐标
.mirror_y = 0, // 所以这里全部设0直接传原始坐标给LVGL
},
};
// 手动配置I2C IO不使用宏因为宏的scl_speed_hz与legacy I2C驱动不兼容
const esp_lcd_panel_io_i2c_config_t tp_io_config = {
.dev_addr = ESP_LCD_TOUCH_IO_I2C_CST816S_ADDRESS,
.control_phase_bytes = 1,
.dc_bit_offset = 0,
.lcd_cmd_bits = 8,
.lcd_param_bits = 0,
.flags = {
.disable_control_phase = 1,
},
};
esp_err_t err = esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)I2C_MASTER_NUM, &tp_io_config, &tp_io_handle);
if (err != ESP_OK) {
ESP_LOGE(LCD_TAG, "Failed to create I2C IO for touch: %s", esp_err_to_name(err));
return;
}
err = esp_lcd_touch_new_i2c_cst816s(tp_io_handle, &tp_cfg, &touch_handle);
if (err != ESP_OK) {
ESP_LOGE(LCD_TAG, "Failed to create touch handle: %s", esp_err_to_name(err));
return;
}
ESP_LOGI(LCD_TAG, "Touch controller initialized successfully");
}
// 初始化LVGL显示和触摸
void lvgl_lcd_init(){
const lvgl_port_cfg_t lvgl_cfg = {
.task_priority = 4,
.task_stack = 8192,
.task_affinity = -1,
.task_max_sleep_ms = 500,
.timer_period_ms = 5
};
lvgl_port_init(&lvgl_cfg);
#define LVGL_DRAW_BUF_LINES 30
size_t buffer_size = LCD_WID * LVGL_DRAW_BUF_LINES;
ESP_LOGI(LCD_TAG, "LVGL buffer size: %d bytes (W: %d, Lines: %d)",
buffer_size * 2, LCD_WID, LVGL_DRAW_BUF_LINES);
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = io_handle,
.panel_handle = panel_handle,
.buffer_size = buffer_size,
.double_buffer = true,
.hres = LCD_WID,
.vres = LCD_HIGH,
.monochrome = false,
.rotation = {
.swap_xy = false,
.mirror_x = false,
.mirror_y = false,
},
.flags = {
.buff_dma = true,
}
};
disp_handle = lvgl_port_add_disp(&disp_cfg);
if (touch_handle != NULL) {
lvgl_port_touch_cfg_t touch_cgf = {
.disp = disp_handle,
.handle = touch_handle,
};
lvgl_port_add_touch(&touch_cgf);
ESP_LOGI(LCD_TAG, "Touch controller added to LVGL");
} else {
ESP_LOGE(LCD_TAG, "Touch handle is NULL, skipping touch initialization");
}
}
void get_touch(uint16_t* touchx,uint16_t* touchy){
if (touch_handle == NULL) {
ESP_LOGE(LCD_TAG, "Touch handle is NULL, cannot get touch data");
*touchx = 0;
*touchy = 0;
return;
}
uint8_t max = 1;
max = touch_handle->data.points;
*touchx = touch_handle->data.coords[0].x;
*touchy = touch_handle->data.coords[0].y;
printf("%x\n",max);
}
// LCD显示开关封装供main.c在UI渲染完成后调用
void lcd_disp_on_off(bool on_off) {
if (panel_handle != NULL) {
esp_lcd_panel_disp_on_off(panel_handle, on_off);
}
}
// 清空LCD GRAM为黑色用于低功耗熄屏前避免残影
void lcd_clear_screen_black(void) {
if (panel_handle == NULL) {
ESP_LOGE(LCD_TAG, "Panel handle is NULL, cannot clear screen");
return;
}
size_t clear_buffer_size = LCD_WID * 40;
uint16_t *clear_buffer = heap_caps_malloc(clear_buffer_size * sizeof(uint16_t), MALLOC_CAP_DMA);
if (clear_buffer) {
memset(clear_buffer, 0, clear_buffer_size * sizeof(uint16_t));
for (int y = 0; y < LCD_HIGH; y += 40) {
int lines = (y + 40 > LCD_HIGH) ? (LCD_HIGH - y) : 40;
esp_lcd_panel_draw_bitmap(panel_handle, 0, y, LCD_WID, y + lines, clear_buffer);
}
heap_caps_free(clear_buffer);
ESP_LOGI(LCD_TAG, "LCD GRAM cleared to black (for low power mode)");
} else {
ESP_LOGE(LCD_TAG, "Failed to allocate clear buffer");
}
}