#include "dzbj_gpio.h" #include "esp_lvgl_port.h" #include "esp_lcd_st77916.h" #include "esp_err.h" #include "esp_log.h" #include "lcd.h" #if DZBJ_ENABLE_TOUCH #include "esp_lcd_touch_cst816s.h" #endif #include #include "esp_heap_caps.h" // ST77916 初始化命令(从 dzbj 项目已验证的驱动版本提取) // 主项目 managed_component 新版默认命令与本硬件面板不匹配,需手动注入 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}, }; 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; #if DZBJ_ENABLE_TOUCH static esp_lcd_touch_handle_t touch_handle = NULL; static esp_lcd_panel_io_handle_t tp_io_handle = NULL; // 外部传入的 I2C 总线句柄(与主项目共享) static i2c_master_bus_handle_t ext_i2c_bus = NULL; #endif void lcd_set_i2c_bus(i2c_master_bus_handle_t bus) { #if DZBJ_ENABLE_TOUCH ext_i2c_bus = bus; #else (void)bus; #endif } void lcd_init(){ const spi_bus_config_t buscfg = ST77916_PANEL_BUS_QSPI_CONFIG(PIN_LCD_CLK, PIN_LCD_D0, PIN_LCD_D1, PIN_LCD_D2, PIN_LCD_D3, LCD_HIGH * 80 * sizeof(uint16_t)); spi_bus_initialize(SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO); // QSPI 80MHz esp_lcd_panel_io_spi_config_t io_config = ST77916_PANEL_IO_QSPI_CONFIG(PIN_LCD_CS, NULL, NULL); io_config.pclk_hz = 80 * 1000 * 1000; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI_LCD_HOST, &io_config, &io_handle)); const st77916_vendor_config_t vendor_config = { .init_cmds = lcd_init_cmds, .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(st77916_lcd_init_cmd_t), .flags = { .use_qspi_interface = 1, }, }; 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_config, }; 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); // 清空LCD GRAM,避免显示上次关机时的残留画面 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 (black filled)"); } else { ESP_LOGE(LCD_TAG, "Failed to allocate clear buffer"); } esp_lcd_panel_disp_on_off(panel_handle, true); } #if DZBJ_ENABLE_TOUCH // 初始化触摸控制器(使用外部传入的 I2C 总线) 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 = false, .mirror_x = false, .mirror_y = false, }, }; const esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_CST816S_CONFIG(); // 使用外部传入的 I2C 总线句柄(与音频编解码器共享 I2C_NUM_1) i2c_master_bus_handle_t i2c_bus = ext_i2c_bus; if (i2c_bus == NULL) { ESP_LOGE(LCD_TAG, "I2C bus not set, call lcd_set_i2c_bus() first"); return; } // 直接传入 i2c_master_bus_handle_t,_Generic 宏分派到 V2(新驱动) // 不要强转为 esp_lcd_i2c_bus_handle_t(uint32_t),否则会触发 V1 legacy 驱动冲突 esp_err_t err = esp_lcd_new_panel_io_i2c(i2c_bus, &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"); } #endif // DZBJ_ENABLE_TOUCH // 初始化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); // 使用内部 DMA 内存,20 行单缓冲(360×20×2 = 14400 字节) // PSRAM 缓冲区与 SPI DMA 不兼容(spi transmit queue 失败) // 单缓冲 + 小行数,节省内部 SRAM 给 WiFi/BLE #define LVGL_DRAW_BUF_LINES 20 size_t buffer_size = LCD_WID * LVGL_DRAW_BUF_LINES; ESP_LOGI(LCD_TAG, "LVGL buffer: %d bytes (W:%d, Lines:%d, DMA, single)", buffer_size * sizeof(uint16_t), 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 = false, .hres = LCD_WID, .vres = LCD_HIGH, .monochrome = false, .rotation = { .swap_xy = false, .mirror_x = false, .mirror_y = false, }, .flags = { .buff_dma = true, .buff_spiram = false, } }; disp_handle = lvgl_port_add_disp(&disp_cfg); #if DZBJ_ENABLE_TOUCH 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"); } #endif } #if DZBJ_ENABLE_TOUCH void get_touch(uint16_t* touchx, uint16_t* touchy){ if (touch_handle == NULL) { *touchx = 0; *touchy = 0; return; } *touchx = touch_handle->data.coords[0].x; *touchy = touch_handle->data.coords[0].y; } #endif // DZBJ_ENABLE_TOUCH // 清空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"); } }