From fafb0ccd947e9bc206d4d65309abcfeed2136d99 Mon Sep 17 00:00:00 2001 From: Enygma <94997734+EnygmaINC@users.noreply.github.com> Date: Mon, 9 Nov 2026 14:40:37 -0500 Subject: [PATCH 1/2] Add Arduino_ST77922_1L4 QSPI driver (id 52, bus 6) for 3.5" ES3C35P ST77922 wants register writes on 1 line and pixel data on 4 lines, which Arduino_ESP32QSPI cannot do (it hardcodes 4-4-4). Adds an own-bus raw ESP-IDF spi_master driver modelled on the vendor example, using the vendor's active init table verbatim, plus the DisplayConfig / ardgfx wiring for TFT_DATABUS_N=6 and TFT_DISPLAY_DRIVER_N=52. Co-Authored-By: Claude Sonnet 5.5 --- src/DisplayConfig.h | 6 + src/backends/ardgfx.cpp | 12 + src/backends/ardgfx.h | 23 +- src/backends/ardgfx_driver.inc | 4 + src/backends/ardgfx_driver_class.inc | 2 + src/panels/Arduino_ST77922_1L4.h | 249 +++++++++++ src/panels/st77922_1l4/esp_lcd_st77922.h | 283 ++++++++++++ .../st77922_1l4/esp_lcd_st77922_general.c | 423 ++++++++++++++++++ .../st77922_1l4/st77922_1l4_init_table.h | 69 +++ src/panels/st77922_1l4/st77922_interface.h | 69 +++ 10 files changed, 1138 insertions(+), 2 deletions(-) create mode 100644 src/panels/Arduino_ST77922_1L4.h create mode 100644 src/panels/st77922_1l4/esp_lcd_st77922.h create mode 100644 src/panels/st77922_1l4/esp_lcd_st77922_general.c create mode 100644 src/panels/st77922_1l4/st77922_1l4_init_table.h create mode 100644 src/panels/st77922_1l4/st77922_interface.h diff --git a/src/DisplayConfig.h b/src/DisplayConfig.h index ab46f37..a63a340 100644 --- a/src/DisplayConfig.h +++ b/src/DisplayConfig.h @@ -33,6 +33,12 @@ enum DisplayBus : uint8_t { DISPLAY_BUS_RGB = 3, // Arduino_ESP32RGBPanel DISPLAY_BUS_DSI = 4, // Arduino_ESP32DSIPanel DISPLAY_BUS_PAR8 = 5, // Arduino_ESP32PAR8 + // Own-bus QSPI, like RGB/DSI above: the panel and its bus are one class + // (Arduino_ST77922_1L4), not an Arduino_DataBus, because this panel's + // command phase is single-line while Arduino_ESP32QSPI (bus 1) hardcodes + // every phase to 4 lines -- a real protocol mismatch, not a config gap. + // See Arduino_ST77922_1L4.h. + DISPLAY_BUS_QSPI_1L4 = 6, // LovyanGFX bus families, kept clear of the ArduinoGFX range. DISPLAY_BUS_LGFX_SPI = 16, DISPLAY_BUS_LGFX_I2C = 17, diff --git a/src/backends/ardgfx.cpp b/src/backends/ardgfx.cpp index 88c295c..d2db341 100644 --- a/src/backends/ardgfx.cpp +++ b/src/backends/ardgfx.cpp @@ -80,6 +80,18 @@ void tft_display::buildDisplay() { cfg.width, cfg.height, bus, 0, true, cfg.rst, static_cast(cfg.initOps), cfg.initOpsLen ); +#elif TFT_DATABUS_N == 6 + // Own-bus QSPI, same shape as the RGB/DSI cases above: this panel's + // command phase is single-line while Arduino_ESP32QSPI (bus 1) hardcodes + // every phase to 4 lines, so there is no generic Arduino_DataBus here -- + // Arduino_ST77922_1L4 drives its own ESP-IDF SPI bus and panel driver + // together. See Arduino_ST77922_1L4.h for why. + #if TFT_DISPLAY_DRIVER_N != 52 + #error "TFT_DATABUS_N=6 requires TFT_DISPLAY_DRIVER_N=52 (Arduino_ST77922_1L4)" + #endif + _gfx = new Arduino_ST77922_1L4( + cfg.width, cfg.height, cfg.cs, cfg.sclk, cfg.d[0], cfg.d[1], cfg.d[2], cfg.d[3], cfg.rst, drvRot + ); #else #if TFT_DATABUS_N == 0 bus = new TFT_DATABUS(cfg.dc, cfg.cs, cfg.sclk, cfg.mosi, cfg.miso, &SPI); diff --git a/src/backends/ardgfx.h b/src/backends/ardgfx.h index f8ccb55..5a27251 100644 --- a/src/backends/ardgfx.h +++ b/src/backends/ardgfx.h @@ -14,6 +14,9 @@ class tft_logger; #include "tft_defines.h" #include #include "../panels/Arduino_NV3031B.h" +#if defined(TFT_DATABUS_N) && TFT_DATABUS_N == 6 +#include "../panels/Arduino_ST77922_1L4.h" +#endif #include #include #include @@ -21,7 +24,7 @@ class tft_logger; #include // clang-format off // Check Data bus -#if !defined(TFT_DATABUS_N) || TFT_DATABUS_N > 5 +#if !defined(TFT_DATABUS_N) || TFT_DATABUS_N > 6 #warning "Please define the Data bus used for this board:\n \ - 0: Arduino_HWSPI: Shared SPI with other devices;\n \ - 1: Arduino_ESP32QSPI: Quad SPI, used in AMOLED and some new displays;\n \ @@ -29,6 +32,7 @@ class tft_logger; - 3: Arduino_ESP32RGBPanel: Parallel 16 bit;\n \ - 4: Arduino_ESP32DSIPanel: DSI Panel for P4 devices;\n \ - 5: Arduino_ESP32PAR8: Parallel 8 bit, D0-D7 spread over both GPIO banks;\n \ + - 6: Arduino_ST77922_1L4: QSPI with a single-line command phase (ST77916/ST77922 family);\n \ \n \ Using Arduino_HWSPI as default." #define TFT_DATABUS_N 0 @@ -141,6 +145,19 @@ class tft_logger; TFT_HEIGHT, TFT_COL_OFS1, TFT_ROW_OFS1, TFT_COL_OFS2,TFT_ROW_OFS2 " #endif +#elif TFT_DATABUS_N == 6 +// Own-bus QSPI (see DisplayConfig.h's DISPLAY_BUS_QSPI_1L4): no +// Arduino_DataBus here, Arduino_ST77922_1L4 owns its ESP-IDF SPI bus and +// panel driver together. No TFT_IPS/offset macros either -- this class +// doesn't use Arduino_GFX's generic MADCTL/window-offset mechanism at all. + #ifndef TFT_DISPLAY_DRIVER_N + #define TFT_DISPLAY_DRIVER_N 52 + #endif + #if !defined(TFT_CS) || !defined(TFT_SCLK) || !defined(TFT_D0) || !defined(TFT_D1) || !defined(TFT_D2) || \ + !defined(TFT_D3) || !defined(TFT_RST) || !defined(TFT_ROTATION) || !defined(TFT_WIDTH) || !defined(TFT_HEIGHT) + #error "Missing Definitions for: TFT_CS, TFT_SCLK, TFT_D0, TFT_D1, TFT_D2, TFT_D3, \ + TFT_RST, TFT_ROTATION, TFT_WIDTH, TFT_HEIGHT" + #endif #else #error "Invalid TFT_DATABUS_N\n Please define the Data bus used for this board:\n \ - 0: Arduino_HWSPI: Shared SPI with other devices;\n \ @@ -148,7 +165,8 @@ class tft_logger; - 2: Arduino_ESP32PAR8Q: Parallel 8 bit, D0-D7 on the same GPIO bank;\n \ - 3: Arduino_ESP32RGBPanel: Parallel 16 bit;\n \ - 4: Arduino_ESP32DSIPanel: DSI Panel for P4 devices;\n \ - - 5: Arduino_ESP32PAR8: Parallel 8 bit, D0-D7 spread over both GPIO banks;" + - 5: Arduino_ESP32PAR8: Parallel 8 bit, D0-D7 spread over both GPIO banks;\n \ + - 6: Arduino_ST77922_1L4: QSPI with a single-line command phase (ST77916/ST77922 family);" #endif // Set Databus Type for constructor @@ -224,6 +242,7 @@ class tft_logger; [bus,rst,w,h] 47 SSD1306; 48 SH1106;\n \ [RGB] 49 Arduino_RGB_Display (Arduino_ESP32RGBPanel: ST7262/ST7701);\n \ [DSI] 50 Arduino_DSI_Display;\n \ + [own bus, TFT_DATABUS_N=6 only] 52 Arduino_ST77922_1L4 (ST77916/ST77922 family, QSPI with a single-line command phase);\n \ " #endif diff --git a/src/backends/ardgfx_driver.inc b/src/backends/ardgfx_driver.inc index 287a588..21e54f1 100644 --- a/src/backends/ardgfx_driver.inc +++ b/src/backends/ardgfx_driver.inc @@ -32,6 +32,10 @@ #error "Arduino_DSI_Display (50) needs the compile-time TFT_DSI_INIT table and is built\n \ next to its bus, so it is not part of the runtime driver factory; selecting\n \ TFT_DATABUS_N=4 already pins it. Do not name 50 in TFT_DISPLAY_DRIVER_N_ALT*." +#elif DD_DRV_ID == 52 + #error "Arduino_ST77922_1L4 (52) owns its ESP-IDF SPI bus and panel driver together, so it\n \ + is not part of the runtime driver factory; selecting TFT_DATABUS_N=6 already pins it.\n \ + Do not name 52 in TFT_DISPLAY_DRIVER_N_ALT*." #endif #include "ardgfx_driver_class.inc" diff --git a/src/backends/ardgfx_driver_class.inc b/src/backends/ardgfx_driver_class.inc index a48ebad..45a7467 100644 --- a/src/backends/ardgfx_driver_class.inc +++ b/src/backends/ardgfx_driver_class.inc @@ -121,6 +121,8 @@ #define DD_DRV_CLASS Arduino_DSI_Display #elif DD_DRV_ID == 51 #define DD_DRV_CLASS Arduino_NV3031B // src/panels/, not part of Arduino_GFX +#elif DD_DRV_ID == 52 + #define DD_DRV_CLASS Arduino_ST77922_1L4 // src/panels/, not part of Arduino_GFX #else #error "Invalid driver id in TFT_DISPLAY_DRIVER_N / TFT_DISPLAY_DRIVER_N_ALT*" #endif diff --git a/src/panels/Arduino_ST77922_1L4.h b/src/panels/Arduino_ST77922_1L4.h new file mode 100644 index 0000000..9b8b223 --- /dev/null +++ b/src/panels/Arduino_ST77922_1L4.h @@ -0,0 +1,249 @@ +// SPDX-FileCopyrightText: 2026 bmorcelli +// +// SPDX-License-Identifier: MIT + +// Arduino_GFX driver for the ST77922 over QSPI, id 52 +// (TFT_DATABUS_N=6 / TFT_DISPLAY_DRIVER_N=52). +// +// Arduino_GFX's own Arduino_ESP32QSPI sends every command on all 4 QSPI lines +// ("4-4-4": see its writeCommand(), which sets SPI_TRANS_MULTILINE_CMD | +// SPI_TRANS_MULTILINE_ADDR unconditionally). This controller wants register +// writes on a single line and only the pixel data on four, so that bus cannot +// drive it. Like Arduino_RGB_Display/Arduino_DSI_Display (ids 49/50, see +// ardgfx.cpp), this class therefore owns its bus instead of taking an +// Arduino_DataBus. +// +// The transfer code below follows the board vendor's own Arduino example for +// this panel (LCDwiki ES3C35P, github.com/ydedox/st77922, Example_01) closely, +// because that is the known-good reference: +// - register write : opcode 0x02, 24-bit address = (register << 8), params on +// one line; +// - pixel write : opcode 0x32, address 0x003C00 ("write memory continue"), +// data on four lines, after the window is set with +// 0x2A / 0x2B; +// - no software reset, no RAMWR (0x2C) per write; +// - the window's X start and width are multiples of 4. +// +// The panel itself does not rotate (its MADCTL does not remap addressing), so +// rotation is done in software: Arduino_Canvas hands this driver native +// portrait frames, and the per-pixel/per-rect paths transform coordinates. +#ifndef ARDUINO_ST77922_1L4_H +#define ARDUINO_ST77922_1L4_H + +#include + +#include + +#include "st77922_1l4/esp_lcd_st77922.h" // only for st77922_lcd_init_cmd_t +#include "st77922_1l4/st77922_1l4_init_table.h" + +class Arduino_ST77922_1L4 : public Arduino_GFX { +public: + Arduino_ST77922_1L4( + int16_t w, int16_t h, int8_t cs, int8_t sclk, int8_t d0, int8_t d1, int8_t d2, int8_t d3, int8_t rst, + uint8_t rotation + ) + : Arduino_GFX(w, h), _cs(cs), _sclk(sclk), _d0(d0), _d1(d1), _d2(d2), _d3(d3) { + (void)rst; // the panel's reset line is tied to the chip's own reset on this board + setRotation(rotation); + } + + // Pixels per SPI transaction; the vendor's own value. + static const int32_t CHUNK_PIXELS = 0x4000; + + bool begin(int32_t speed = 80000000) override { + // Callers reach this through an Arduino_GFX*, so the default above is + // never applied: they pass the base class's "not defined" (-1). + if (speed <= 0) speed = 80000000; + + // Internal, DMA-capable RAM for pixel data and for register parameters. + _buf = (uint16_t *)heap_caps_malloc(CHUNK_PIXELS * 2, MALLOC_CAP_DMA); + _cmd = (uint8_t *)heap_caps_malloc(64, MALLOC_CAP_DMA); + if (!_buf || !_cmd) return false; + + spi_bus_config_t buscfg = {}; + buscfg.data0_io_num = _d0; + buscfg.data1_io_num = _d1; + buscfg.sclk_io_num = _sclk; + buscfg.data2_io_num = _d2; + buscfg.data3_io_num = _d3; + buscfg.max_transfer_sz = CHUNK_PIXELS * 2 + 8; + buscfg.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_IOMUX_PINS | SPICOMMON_BUSFLAG_QUAD; + Serial.printf("[ST77922] pins cs=%d sclk=%d d0=%d d1=%d d2=%d d3=%d speed=%d\n", _cs, _sclk, _d0, _d1, _d2, _d3, + (int)speed); + esp_err_t bus_err = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); + Serial.printf("[ST77922] spi_bus_initialize -> 0x%x\n", (unsigned)bus_err); + if (bus_err != ESP_OK) return false; + + spi_device_interface_config_t devcfg = {}; + devcfg.command_bits = 0; + devcfg.address_bits = 0; + devcfg.mode = 0; + devcfg.clock_speed_hz = speed; + devcfg.spics_io_num = _cs; + devcfg.flags = SPI_DEVICE_HALFDUPLEX; + devcfg.queue_size = 17; + esp_err_t dev_err = spi_bus_add_device(SPI2_HOST, &devcfg, &_dev); + Serial.printf("[ST77922] spi_bus_add_device -> 0x%x\n", (unsigned)dev_err); + if (dev_err != ESP_OK) return false; + + const size_t n = sizeof(es3c35p_lcd_init_cmds) / sizeof(es3c35p_lcd_init_cmds[0]); + for (size_t i = 0; i < n; i++) { + writeReg((uint8_t)es3c35p_lcd_init_cmds[i].cmd, es3c35p_lcd_init_cmds[i].data, + es3c35p_lcd_init_cmds[i].data_bytes); + if (es3c35p_lcd_init_cmds[i].delay_ms) delay(es3c35p_lcd_init_cmds[i].delay_ms); + } + Serial.printf("[ST77922] init sent: %u commands\n", (unsigned)n); + +#ifdef ST77922_BRINGUP_TEST + // Straight to the panel, bypassing Launcher: full white for 3 s, then + // red / green / blue thirds for 3 s. + Serial.println("[ST77922] test: full white 3s"); + fillNative(0, 0, WIDTH, HEIGHT, 0xFFFF); + delay(3000); + Serial.println("[ST77922] test: R/G/B bars 3s"); + fillNative(0, 0, WIDTH, 160, 0xF800); + fillNative(0, 160, WIDTH, 320, 0x07E0); + fillNative(0, 320, WIDTH, 480, 0x001F); + delay(3000); + Serial.println("[ST77922] test: done"); +#endif + return true; + } + + void writePixelPreclipped(int16_t x, int16_t y, uint16_t color) override { + writeFillRectPreclipped(x, y, 1, 1, color); + } + + // Fills a rectangle given in rotated (logical) coordinates. The panel needs + // X start and width in multiples of 4, so the rectangle is widened to the + // next multiple (the vendor's own point-drawing does the same). Whole-frame + // output goes through draw16bitRGBBitmap() instead. + void writeFillRectPreclipped(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override { + if (!_dev) return; + int32_t nx, ny, nw, nh; + toNative(x, y, w, h, nx, ny, nw, nh); + fillNative(nx, ny, nx + nw, ny + nh, color); + } + + // Whole-bitmap push for Arduino_Canvas::flush(). The canvas owns rotation + // and hands over a native-portrait bitmap; anything this path can't take + // (rotated driver, X or width not a multiple of 4) returns false so the + // caller falls back to the generic per-pixel path. + bool pushBitmap(int16_t x, int16_t y, const uint16_t *bitmap, int16_t w, int16_t h) { + if (!_dev || (_rotation & 3) != 0) return false; + if (x < 0 || y < 0 || w <= 0 || h <= 0 || x + w > WIDTH || y + h > HEIGHT) return false; + if ((x & 3) || (w & 3)) return false; + setWindow(x, y, x + w, y + h); + size_t total = (size_t)w * h; + const uint16_t *src = bitmap; + while (total) { + size_t n = total > CHUNK_PIXELS ? CHUNK_PIXELS : total; + for (size_t i = 0; i < n; i++) _buf[i] = swap565(src[i]); + writePixels(n); + src += n; + total -= n; + } + return true; + } + + void draw16bitRGBBitmap(int16_t x, int16_t y, uint16_t *bitmap, int16_t w, int16_t h) override { + if (!pushBitmap(x, y, bitmap, w, h)) Arduino_GFX::draw16bitRGBBitmap(x, y, bitmap, w, h); + } + + void draw16bitRGBBitmap(int16_t x, int16_t y, const uint16_t bitmap[], int16_t w, int16_t h) override { + if (!pushBitmap(x, y, bitmap, w, h)) Arduino_GFX::draw16bitRGBBitmap(x, y, bitmap, w, h); + } + +private: + // Logical (rotated) rect -> native portrait rect. A solid rectangle rotated + // by a multiple of 90 degrees is still a solid rectangle, so this closed + // form covers single pixels and whole rectangles alike. + void toNative(int16_t lx, int16_t ly, int16_t lw, int16_t lh, int32_t &nx, int32_t &ny, int32_t &nw, int32_t &nh) { + switch (_rotation & 3) { + default: // case 0 + nx = lx; ny = ly; nw = lw; nh = lh; + break; + case 1: + nx = WIDTH - ly - lh; ny = lx; nw = lh; nh = lw; + break; + case 2: + nx = WIDTH - lx - lw; ny = HEIGHT - ly - lh; nw = lw; nh = lh; + break; + case 3: + nx = ly; ny = HEIGHT - lx - lw; nw = lh; nh = lw; + break; + } + } + + // The controller is little-endian-hostile: 16bpp goes out MSB first. + static inline uint16_t swap565(uint16_t c) { return (c >> 8) | (c << 8); } + + // One register write: opcode 0x02, address (reg << 8), params on one line. + void writeReg(uint8_t reg, const void *data, size_t len) { + spi_transaction_ext_t t = {}; + t.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR; + t.base.cmd = 0x02; + t.base.addr = (uint32_t)reg << 8; + t.command_bits = 8; + t.address_bits = 24; + if (len && len <= 64) { + memcpy(_cmd, data, len); + t.base.tx_buffer = _cmd; + t.base.length = 8 * len; + } + spi_device_polling_transmit(_dev, (spi_transaction_t *)&t); + } + + // x1 / y1 are exclusive. + void setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) { + uint8_t cx[4] = {(uint8_t)(x0 >> 8), (uint8_t)x0, (uint8_t)((x1 - 1) >> 8), (uint8_t)(x1 - 1)}; + uint8_t cy[4] = {(uint8_t)(y0 >> 8), (uint8_t)y0, (uint8_t)((y1 - 1) >> 8), (uint8_t)(y1 - 1)}; + writeReg(0x2A, cx, 4); + writeReg(0x2B, cy, 4); + } + + // n pixels from _buf (already byte-swapped): opcode 0x32, address 0x003C00, + // data on four lines. + void writePixels(size_t n) { + spi_transaction_ext_t t = {}; + t.base.flags = SPI_TRANS_MODE_QIO | SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR; + t.base.cmd = 0x32; + t.base.addr = 0x3C << 8; + t.command_bits = 8; + t.address_bits = 24; + t.base.tx_buffer = _buf; + t.base.length = n * 16; + spi_device_polling_transmit(_dev, (spi_transaction_t *)&t); + } + + // Solid fill of a native-portrait rectangle (x1 / y1 exclusive), widened to + // the panel's multiple-of-4 X alignment. + void fillNative(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint16_t color) { + if (x0 < 0) x0 = 0; + if (y0 < 0) y0 = 0; + if (x1 > WIDTH) x1 = WIDTH; + if (y1 > HEIGHT) y1 = HEIGHT; + if (x1 <= x0 || y1 <= y0) return; + x0 &= ~3; + x1 = (x1 + 3) & ~3; + if (x1 > WIDTH) x1 = WIDTH; + setWindow(x0, y0, x1, y1); + size_t total = (size_t)(x1 - x0) * (y1 - y0); + size_t first = total > CHUNK_PIXELS ? CHUNK_PIXELS : total; + uint16_t sc = swap565(color); + for (size_t i = 0; i < first; i++) _buf[i] = sc; + while (total) { + size_t n = total > CHUNK_PIXELS ? CHUNK_PIXELS : total; + writePixels(n); + total -= n; + } + } + + int8_t _cs, _sclk, _d0, _d1, _d2, _d3; + spi_device_handle_t _dev = nullptr; + uint16_t *_buf = nullptr; + uint8_t *_cmd = nullptr; +}; + +#endif // ARDUINO_ST77922_1L4_H diff --git a/src/panels/st77922_1l4/esp_lcd_st77922.h b/src/panels/st77922_1l4/esp_lcd_st77922.h new file mode 100644 index 0000000..e47e71c --- /dev/null +++ b/src/panels/st77922_1l4/esp_lcd_st77922.h @@ -0,0 +1,283 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#include "hal/lcd_types.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_idf_version.h" +#if SOC_LCD_RGB_SUPPORTED +#include "esp_lcd_panel_rgb.h" +#endif + +#if SOC_MIPI_DSI_SUPPORTED +#include "esp_lcd_mipi_dsi.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LCD panel initialization commands. + * + */ +typedef struct { + int cmd; /* +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_check.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_commands.h" +#include "esp_log.h" +#include "esp_lcd_st77922.h" +#include "st77922_interface.h" + +// Normally auto-generated by ESP-IDF's component manager from +// idf_component.yml's "version: 2.0.2" when this is pulled in as a real +// managed component. Vendored flat into an Arduino sketch instead, so that +// generation step never runs — restated by hand, values only used for a +// log line at panel creation. +#ifndef ESP_LCD_ST77922_VER_MAJOR +#define ESP_LCD_ST77922_VER_MAJOR 2 +#define ESP_LCD_ST77922_VER_MINOR 0 +#define ESP_LCD_ST77922_VER_PATCH 2 +#endif + +static const char *TAG = "st77922_general"; + +static esp_err_t panel_st77922_del(esp_lcd_panel_t *panel); +static esp_err_t panel_st77922_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_st77922_init(esp_lcd_panel_t *panel); +static esp_err_t panel_st77922_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); +static esp_err_t panel_st77922_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_st77922_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_st77922_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); +static esp_err_t panel_st77922_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); +static esp_err_t panel_st77922_disp_on_off(esp_lcd_panel_t *panel, bool off); + +typedef struct { + esp_lcd_panel_t base; + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + int x_gap; + int y_gap; + uint8_t fb_bits_per_pixel; + uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register + uint8_t colmod_val; // save surrent value of LCD_CMD_COLMOD register + const st77922_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; + struct { + unsigned int use_qspi_interface: 1; + unsigned int reset_level: 1; + } flags; +} st77922_panel_t; + +esp_err_t esp_lcd_new_panel_st77922_general(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel) +{ + ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + esp_err_t ret = ESP_OK; + st77922_panel_t *st77922 = NULL; + st77922 = calloc(1, sizeof(st77922_panel_t)); + ESP_GOTO_ON_FALSE(st77922, ESP_ERR_NO_MEM, err, TAG, "no mem for st77922 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_config_t io_conf = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, + }; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + + switch (panel_dev_config->rgb_ele_order) { + case LCD_RGB_ELEMENT_ORDER_RGB: + st77922->madctl_val = 0; + break; + case LCD_RGB_ELEMENT_ORDER_BGR: + st77922->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order"); + break; + } + + switch (panel_dev_config->bits_per_pixel) { + case 16: // RGB565 + st77922->colmod_val = 0x55; + st77922->fb_bits_per_pixel = 16; + break; + case 18: // RGB666 + st77922->colmod_val = 0x66; + // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel + st77922->fb_bits_per_pixel = 24; + break; + case 24: // RGB888 + st77922->colmod_val = 0x77; + st77922->fb_bits_per_pixel = 24; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + st77922->io = io; + st77922->reset_gpio_num = panel_dev_config->reset_gpio_num; + st77922->flags.reset_level = panel_dev_config->flags.reset_active_high; + st77922_vendor_config_t *vendor_config = (st77922_vendor_config_t *)panel_dev_config->vendor_config; + if (vendor_config) { + st77922->init_cmds = vendor_config->init_cmds; + st77922->init_cmds_size = vendor_config->init_cmds_size; + st77922->flags.use_qspi_interface = vendor_config->flags.use_qspi_interface; + } + st77922->base.del = panel_st77922_del; + st77922->base.reset = panel_st77922_reset; + st77922->base.init = panel_st77922_init; + st77922->base.draw_bitmap = panel_st77922_draw_bitmap; + st77922->base.invert_color = panel_st77922_invert_color; + st77922->base.set_gap = panel_st77922_set_gap; + st77922->base.mirror = panel_st77922_mirror; + st77922->base.swap_xy = panel_st77922_swap_xy; + st77922->base.disp_on_off = panel_st77922_disp_on_off; + *ret_panel = &(st77922->base); + ESP_LOGD(TAG, "new st77922 panel @%p", st77922); + + ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_ST77922_VER_MAJOR, ESP_LCD_ST77922_VER_MINOR, + ESP_LCD_ST77922_VER_PATCH); + + return ESP_OK; + +err: + if (st77922) { + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(st77922); + } + return ret; +} + +static esp_err_t tx_param(st77922_panel_t *st77922, esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size) +{ + if (st77922->flags.use_qspi_interface) { + lcd_cmd &= 0xff; + lcd_cmd <<= 8; + lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24; + } + return esp_lcd_panel_io_tx_param(io, lcd_cmd, param, param_size); +} + +static esp_err_t tx_color(st77922_panel_t *st77922, esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size) +{ + if (st77922->flags.use_qspi_interface) { + lcd_cmd &= 0xff; + lcd_cmd <<= 8; + lcd_cmd |= LCD_OPCODE_WRITE_COLOR << 24; + } + return esp_lcd_panel_io_tx_color(io, lcd_cmd, param, param_size); +} + +static esp_err_t panel_st77922_del(esp_lcd_panel_t *panel) +{ + st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); + + if (st77922->reset_gpio_num >= 0) { + gpio_reset_pin(st77922->reset_gpio_num); + } + ESP_LOGD(TAG, "del st77922 panel @%p", st77922); + free(st77922); + return ESP_OK; +} + +static esp_err_t panel_st77922_reset(esp_lcd_panel_t *panel) +{ + st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); + esp_lcd_panel_io_handle_t io = st77922->io; + + // Perform hardware reset + if (st77922->reset_gpio_num >= 0) { + gpio_set_level(st77922->reset_gpio_num, st77922->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(st77922->reset_gpio_num, !st77922->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(120)); + } else { // Perform software reset + ESP_RETURN_ON_ERROR(tx_param(st77922, io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(120)); + } + + return ESP_OK; +} + +static const st77922_lcd_init_cmd_t vendor_specific_init_default[] = { + {0x28, (uint8_t []){0x00}, 0, 0}, + {0x10, (uint8_t []){0x00}, 0, 0}, + {0x2A, (uint8_t []){0x00, 0x00, 0x02, 0x13}, 4, 0}, + {0x2B, (uint8_t []){0x00, 0x00, 0x01, 0x2B}, 4, 0}, + {0xD0, (uint8_t []){0x80}, 1, 0}, + // ======================CMD2====================== + {0xF1, (uint8_t []){0x00}, 0, 0}, + {0x60, (uint8_t []){0x00, 0x00, 0x00}, 3, 0}, + {0x65, (uint8_t []){0x00}, 1, 0}, + {0x66, (uint8_t []){0x00, 0x3F}, 2, 0}, + {0xBE, (uint8_t []){0x1E, 0x01, 0x06}, 3, 0}, // VCM SSI + {0x70, (uint8_t []){0x02, 0x7D, 0x12, 0x14, 0x30, 0x00, 0x07, 0x52, 0x01, 0x00, 0x00, 0x1A}, 12, 0}, // VFP, VBP, Gate line + {0x71, (uint8_t []){0xD0}, 1, 0}, // MIPI CMD Mode + {0x7B, (uint8_t []){0x00, 0x08, 0x08}, 3, 0}, + {0x80, (uint8_t []){0x55, 0x62, 0x2F, 0x17, 0xF0, 0x52, 0x70, 0xD2, 0x52, 0x62, 0xEA}, 11, 0}, + {0x81, (uint8_t []){0x26, 0x52, 0x72, 0x27}, 4, 0}, + {0x84, (uint8_t []){0x92, 0x25}, 2, 0}, + {0x86, (uint8_t []){0xC6, 0x04, 0xB1, 0x02, 0x58, 0x12, 0x58, 0x10, 0x13, 0x01, 0xAA, 0x00, 0xAA, 0xAA}, 14, 0}, + {0x87, (uint8_t []){0x10, 0x10, 0x58, 0x00, 0x02, 0x3A}, 6, 0}, + {0x88, (uint8_t []){0x00, 0x00, 0x2C, 0x10, 0x04, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x06}, 15, 0}, + {0x89, (uint8_t []){0x00, 0x00, 0x00}, 3, 0}, + {0x8A, (uint8_t []){0x13, 0x00, 0x2C, 0x00, 0x00, 0x2C, 0x10, 0x10, 0x00, 0x3E, 0x19}, 11, 0}, + {0x8B, (uint8_t []){0x15, 0xB1, 0xB1, 0x44, 0x96, 0x2C, 0x10, 0x97, 0x8E}, 9, 0}, // VGL Pump + {0x8C, (uint8_t []){0x1D, 0xB1, 0xB1, 0x44, 0x96, 0x2C, 0x10, 0x50, 0x0F, 0x01, 0xC5, 0x12, 0x09}, 13, 0}, // VGH Pump + {0x8D, (uint8_t []){0x0C}, 1, 0}, + {0x8E, (uint8_t []){0x33, 0x01, 0x0C, 0x13, 0x01, 0x01}, 6, 0}, + {0x90, (uint8_t []){0x00, 0x44, 0x33, 0x36, 0x00, 0x79, 0x40, 0xB6, 0xB6}, 9, 0}, + {0x91, (uint8_t []){0x00, 0x44, 0x33, 0x37, 0x00, 0x78, 0x40, 0xB6, 0xB6}, 9, 0}, + {0x92, (uint8_t []){0x02, 0x44, 0x55, 0x82, 0x86, 0x2F, 0x00, 0x04, 0x73, 0xB6}, 10, 0}, + {0x93, (uint8_t []){0x0C, 0x00, 0x11, 0x81, 0x87, 0x3F, 0x00, 0x00, 0x73, 0x73}, 10, 0}, + {0x94, (uint8_t []){0x08, 0x00, 0x00, 0x00, 0x00, 0x00}, 6, 0}, + {0x95, (uint8_t []){0x1A, 0x1A, 0x00, 0x00, 0xFF}, 5, 0}, + {0x96, (uint8_t []){0x44, 0x35, 0x07, 0x16, 0x20, 0x21, 0x07, 0x06, 0xB6, 0xB6, 0x00, 0x40}, 12, 0}, + {0x97, (uint8_t []){0x44, 0x35, 0x25, 0x34, 0x22, 0x23, 0x05, 0x04, 0xB6, 0xB6, 0x00, 0x40}, 12, 0}, + {0xBA, (uint8_t []){0x55, 0xB6, 0xB6, 0xB6, 0xB6}, 5, 0}, + {0x9A, (uint8_t []){0x40, 0x0C, 0x56, 0x01, 0x08, 0xB6, 0xB6}, 7, 0}, + {0x9B, (uint8_t []){0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00}, 7, 0}, + {0x9C, (uint8_t []){0x00, 0x12, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00}, 13, 0}, + {0x9D, (uint8_t []){0x80, 0x15, 0x00, 0x07, 0x01, 0x80, 0x73, 0x73}, 8, 0}, + {0x9E, (uint8_t []){0x00, 0x00, 0x00, 0x00, 0x80, 0x1E, 0x01}, 7, 0}, + {0x9F, (uint8_t []){0xA0, 0x09, 0x00, 0x57}, 4, 0}, + {0xB3, (uint8_t []){0x00, 0x30, 0x0F, 0x00, 0x00, 0x00, 0x00}, 7, 0}, + {0xB4, (uint8_t []){0x10, 0x1C, 0x19, 0x14, 0x18, 0x01, 0x1D, 0x03, 0x12, 0x0A, 0x11, 0x08}, 12, 0}, + {0xB5, (uint8_t []){0x1C, 0x1C, 0x19, 0x14, 0x18, 0x00, 0x1D, 0x02, 0x10, 0x0B, 0x13, 0x09}, 12, 0}, + {0xB6, (uint8_t []){0xFF, 0xFF, 0x00, 0x0F, 0xFE, 0x0F, 0xFE}, 7, 0}, + {0xB7, (uint8_t []){0x00, 0x09, 0x10, 0x0B, 0x0A, 0x06, 0x38, 0x04, 0x04, 0x4F, 0x09, 0x15, 0x15, 0x30, 0x37, 0x0F}, 16, 0}, // GammaP + {0xB8, (uint8_t []){0x00, 0x09, 0x0F, 0x0A, 0x09, 0x05, 0x37, 0x03, 0x03, 0x4F, 0x09, 0x15, 0x15, 0x31, 0x36, 0x0F}, 16, 0}, // GammaN + {0xB9, (uint8_t []){0x23, 0x23}, 2, 0}, + {0xBF, (uint8_t []){0x0F, 0x13, 0x13, 0x09, 0x09, 0x09}, 6, 0}, // VGHP/VGLP + // ======================CMD3====================== + {0xF2, (uint8_t []){0x00}, 0, 0}, + {0x73, (uint8_t []){0x04, 0xBA, 0x1A, 0x58, 0x5B}, 5, 0}, // VOP= 5v + {0x77, (uint8_t []){0x6B, 0x5B, 0xFB, 0xC3, 0xC5}, 5, 0}, + {0x7A, (uint8_t []){0x15, 0x27}, 2, 0}, + {0x7B, (uint8_t []){0x04, 0x57}, 2, 0}, + {0x7E, (uint8_t []){0x01, 0x0E}, 2, 0}, + {0xBF, (uint8_t []){0x36}, 1, 0}, + {0xE3, (uint8_t []){0x43, 0x43}, 2, 0}, // VMF + // ======================CMD1====================== + {0xF0, (uint8_t []){0x00}, 0, 0}, + {0x21, (uint8_t []){0x00}, 1, 0}, + {0x11, (uint8_t []){0x00}, 1, 120}, + {0x35, (uint8_t []){0x00}, 1, 20}, +}; + +static esp_err_t panel_st77922_init(esp_lcd_panel_t *panel) +{ + st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); + esp_lcd_panel_io_handle_t io = st77922->io; + const st77922_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + bool is_command1_enable = true; + bool is_cmd_overwritten = false; + + ESP_RETURN_ON_ERROR(tx_param(st77922, io, LCD_CMD_MADCTL, (uint8_t[]) { + st77922->madctl_val, + }, 1), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(tx_param(st77922, io, LCD_CMD_COLMOD, (uint8_t[]) { + st77922->colmod_val, + }, 1), TAG, "send command failed"); + + // vendor specific initialization, it can be different between manufacturers + // should consult the LCD supplier for initialization sequence code + if (st77922->init_cmds) { + init_cmds = st77922->init_cmds; + init_cmds_size = st77922->init_cmds_size; + } else { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st77922_lcd_init_cmd_t); + } + + for (int i = 0; i < init_cmds_size; i++) { + // Check if the command has been used or conflicts with the internal + if (is_command1_enable && (init_cmds[i].data_bytes > 0)) { + switch (init_cmds[i].cmd) { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + st77922->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + st77922->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) { + is_cmd_overwritten = false; + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); + } + } + + // Send command + ESP_RETURN_ON_ERROR(tx_param(st77922, io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + + // Check if the current cmd is the command1 enable cmd + if ((init_cmds[i].cmd == ST77922_PAGE_CMD2 || init_cmds[i].cmd == ST77922_PAGE_CMD3) && init_cmds[i].data_bytes > 0) { + is_command1_enable = false; + } else if (init_cmds[i].cmd == ST77922_PAGE_CMD1 && init_cmds[i].data_bytes > 0) { + is_command1_enable = true; + } + } + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_st77922_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) +{ + st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); + assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); + esp_lcd_panel_io_handle_t io = st77922->io; + + x_start += st77922->x_gap; + x_end += st77922->x_gap; + y_start += st77922->y_gap; + y_end += st77922->y_gap; + + // define an area of frame memory where MCU can access + ESP_RETURN_ON_ERROR(tx_param(st77922, io, LCD_CMD_CASET, (uint8_t[]) { + (x_start >> 8) & 0xFF, + x_start & 0xFF, + ((x_end - 1) >> 8) & 0xFF, + (x_end - 1) & 0xFF, + }, 4), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(tx_param(st77922, io, LCD_CMD_RASET, (uint8_t[]) { + (y_start >> 8) & 0xFF, + y_start & 0xFF, + ((y_end - 1) >> 8) & 0xFF, + (y_end - 1) & 0xFF, + }, 4), TAG, "send command failed"); + // transfer frame buffer + size_t len = (x_end - x_start) * (y_end - y_start) * st77922->fb_bits_per_pixel / 8; + ESP_RETURN_ON_ERROR(tx_color(st77922, io, LCD_CMD_RAMWR, color_data, len), TAG, "send color data failed"); + + return ESP_OK; +} + +static esp_err_t panel_st77922_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); + esp_lcd_panel_io_handle_t io = st77922->io; + int command = 0; + if (invert_color_data) { + command = LCD_CMD_INVON; + } else { + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(tx_param(st77922, io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_st77922_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); + esp_lcd_panel_io_handle_t io = st77922->io; + esp_err_t ret = ESP_OK; + + if (mirror_x) { + st77922->madctl_val |= BIT(6); + } else { + st77922->madctl_val &= ~BIT(6); + } + if (mirror_y) { + st77922->madctl_val |= BIT(7); + } else { + st77922->madctl_val &= ~BIT(7); + } + ESP_RETURN_ON_ERROR(tx_param(st77922, io, LCD_CMD_MADCTL, (uint8_t[]) { + st77922->madctl_val + }, 1), TAG, "send command failed"); + return ret; +} + +static esp_err_t panel_st77922_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) +{ + ESP_LOGW(TAG, "swap_xy is not supported by this panel"); + return ESP_ERR_NOT_SUPPORTED; +} + +static esp_err_t panel_st77922_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) +{ + st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); + st77922->x_gap = x_gap; + st77922->y_gap = y_gap; + return ESP_OK; +} + +static esp_err_t panel_st77922_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); + esp_lcd_panel_io_handle_t io = st77922->io; + int command = 0; + + if (on_off) { + command = LCD_CMD_DISPON; + } else { + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(tx_param(st77922, io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} diff --git a/src/panels/st77922_1l4/st77922_1l4_init_table.h b/src/panels/st77922_1l4/st77922_1l4_init_table.h new file mode 100644 index 0000000..838edb6 --- /dev/null +++ b/src/panels/st77922_1l4/st77922_1l4_init_table.h @@ -0,0 +1,69 @@ +// Verbatim from the board vendor's own example (LCDwiki ES3C35P, +// github.com/ydedox/st77922 Example_01, the ACTIVE st77922_lcd_init[] table -- +// not the commented-out one above it), renamed only. Confirmed working on this +// board with the vendor's transport. Format: st77922_lcd_init_cmd_t. +static const st77922_lcd_init_cmd_t es3c35p_lcd_init_cmds[] = { + {0xF1, (uint8_t[]){0x00}, 1, 0}, + {0x60, (uint8_t[]){0x00, 0x00, 0x00}, 3, 0}, + {0x65, (uint8_t[]){0x80}, 1, 0}, + {0x79, (uint8_t[]){0x06}, 1, 0}, + {0x7B, (uint8_t[]){0x00, 0x08, 0x08}, 3, 0}, + {0x80, (uint8_t[]){0x55, 0x62, 0x2F, 0x17, 0xF0, 0x52, 0x70, 0xD2, 0x52, 0x62, 0xEA}, 11, 0}, + {0x81, (uint8_t[]){0x26, 0x52, 0x72, 0x27}, 4, 0}, + {0x84, (uint8_t[]){0x92, 0x25}, 2, 0}, + {0x87, (uint8_t[]){0x10, 0x10, 0x58, 0x00, 0x02, 0x3A}, 6, 0}, + {0x88, (uint8_t[]){0x00, 0x00, 0x2C, 0x10, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x06}, 15, 0}, + {0x89, (uint8_t[]){0x00, 0x00, 0x00}, 3, 0}, + {0x8A, (uint8_t[]){0x13, 0x00, 0x2C, 0x00, 0x00, 0x2C, 0x10, 0x10, 0x00, 0x3E, 0x19}, 11, 0}, + {0x8B, (uint8_t[]){0x15, 0xB1, 0xB1, 0x44, 0x96, 0x2C, 0x10, 0x97, 0x8E}, 9, 0}, + {0x8C, (uint8_t[]){0x1D, 0xB1, 0xB1, 0x44, 0x96, 0x2C, 0x10, 0x50, 0x0F, 0x01, 0xC5, 0x12, 0x09}, 13, 0}, + {0x8D, (uint8_t[]){0x0C}, 1, 0}, + {0x8E, (uint8_t[]){0x33, 0x01, 0x0C, 0x13, 0x01, 0x01}, 6, 0}, + {0xB3, (uint8_t[]){0x00, 0x30}, 2, 0}, + {0xF1, (uint8_t[]){0x00}, 1, 0}, + {0x71, (uint8_t[]){0xD0}, 1, 0}, + {0x66, (uint8_t[]){0x02, 0x3F}, 2, 0}, + {0xBE, (uint8_t[]){0x26, 0x00, 0x9D}, 3, 0}, + {0x70, (uint8_t[]){0x01, 0xA0, 0x11, 0x40, 0xE0, 0x00, 0x11, 0x69, 0x11, 0x00, 0x00, 0x1A}, 12, 0}, + {0x90, (uint8_t[]){0x04, 0x04, 0x55, 0x74, 0x00, 0x40, 0x43, 0x27, 0x27}, 9, 0}, + {0x91, (uint8_t[]){0x04, 0x04, 0x55, 0x75, 0x00, 0x40, 0x42, 0x27, 0x27}, 9, 0}, + {0x92, (uint8_t[]){0x04, 0x44, 0x55, 0xC0, 0x06, 0x00, 0x07, 0x05, 0x90, 0x27}, 10, 0}, + {0x93, (uint8_t[]){0x04, 0x43, 0x11, 0x00, 0x00, 0x00, 0x00, 0x05, 0x90, 0x27}, 10, 0}, + {0x94, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 6, 0}, + {0x95, (uint8_t[]){0x96, 0x16, 0x00, 0x00, 0xFF}, 5, 0}, + {0x96, (uint8_t[]){0x44, 0x53, 0x03, 0x12, 0x23, 0x24, 0x06, 0x05, 0x94, 0x27, 0x00, 0x44}, 12, 0}, + {0x97, (uint8_t[]){0x44, 0x53, 0x47, 0x56, 0x20, 0x20, 0x02, 0x01, 0x94, 0x27, 0x00, 0x44}, 12, 0}, + {0xBA, (uint8_t[]){0x55, 0x94, 0x2D, 0x94, 0x27}, 5, 0}, + {0x9A, (uint8_t[]){0x40, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00}, 7, 0}, + {0x9B, (uint8_t[]){0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00}, 7, 0}, + {0x9C, (uint8_t[]){0x5C, 0x12, 0x00, 0x00, 0x10, 0x12, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00}, 13, 0}, + {0x9D, (uint8_t[]){0x8A, 0x51, 0x00, 0x00, 0x00, 0x80, 0x1E, 0x01}, 8, 0}, + {0x9E, (uint8_t[]){0x51, 0x00, 0x00, 0x00, 0x80, 0x1E, 0x01}, 7, 0}, + {0xB4, (uint8_t[]){0x1D, 0x1C, 0x1E, 0x0B, 0x14, 0x02, 0x13, 0x09, 0x1E, 0x00, 0x1E, 0x10}, 12, 0}, + {0xB5, (uint8_t[]){0x1D, 0x1C, 0x1E, 0x0A, 0x15, 0x03, 0x11, 0x08, 0x1E, 0x01, 0x1E, 0x12}, 12, 0}, + {0xB6, (uint8_t[]){0x77, 0x77, 0x00, 0x0A, 0xFF, 0x0A, 0xFF}, 7, 0}, + {0x86, (uint8_t[]){0xCD, 0x04, 0xB1, 0x02, 0x58, 0x12, 0x58, 0x0C, 0x13, 0x01, 0xA5, 0x00, 0xA5, 0xA5}, 14, 0}, + {0xB7, (uint8_t[]){0x07, 0x0A, 0x0E, 0x06, 0x05, 0x03, 0x2B, 0x03, 0x03, 0x42, 0x07, 0x10, 0x10, 0x2E, 0x3F, 0x0D}, 16, 0}, + {0xB8, (uint8_t[]){0x07, 0x0A, 0x0D, 0x05, 0x05, 0x02, 0x2B, 0x02, 0x03, 0x42, 0x06, 0x10, 0x0F, 0x2E, 0x3F, 0x0D}, 16, 0}, + {0xB9, (uint8_t[]){0x23, 0x23}, 2, 0}, + {0xBF, (uint8_t[]){0x10, 0x14, 0x14, 0x0B, 0x0B, 0x0B}, 6, 0}, + {0xF2, (uint8_t[]){0x00}, 1, 0}, + {0x73, (uint8_t[]){0x04, 0xDA, 0x12, 0x54, 0x47}, 5, 0}, + {0x77, (uint8_t[]){0x6B, 0x5B, 0xFD, 0xC3, 0xC5}, 5, 0}, + {0x7A, (uint8_t[]){0x15, 0x27}, 2, 0}, + {0x7B, (uint8_t[]){0x04, 0x57}, 2, 0}, + {0x7E, (uint8_t[]){0x01, 0x0E}, 2, 0}, + {0xBF, (uint8_t[]){0x36}, 1, 0}, + {0xE3, (uint8_t[]){0x40, 0x40}, 2, 0}, + {0xF0, (uint8_t[]){0x00}, 1, 0}, + {0xD0, (uint8_t[]){0x00}, 1, 0}, + {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x3F}, 4, 0}, + {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xDF}, 4, 0}, + {0x21, (uint8_t[]){0x00}, 0, 0}, + {0x11, (uint8_t[]){0x00}, 0, 120}, + {0x29, (uint8_t[]){0x00}, 0, 0}, + {0x2C, (uint8_t[]){0x00}, 0, 0}, + {0x3A, (uint8_t[]){0x01}, 1, 0}, + {0x36, (uint8_t[]){0x00}, 1, 0}, + {0x35, (uint8_t[]){0x01}, 1, 20}, +}; diff --git a/src/panels/st77922_1l4/st77922_interface.h b/src/panels/st77922_1l4/st77922_interface.h new file mode 100644 index 0000000..3ac0417 --- /dev/null +++ b/src/panels/st77922_1l4/st77922_interface.h @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_err.h" +#include "esp_lcd_types.h" +#include "esp_lcd_panel_vendor.h" +#include "soc/soc_caps.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define LCD_OPCODE_WRITE_CMD (0x02ULL) +#define LCD_OPCODE_READ_CMD (0x0BULL) +#define LCD_OPCODE_WRITE_COLOR (0x32ULL) + +#define ST77922_PAGE_CMD1 (0xF0) +#define ST77922_PAGE_CMD2 (0xF1) +#define ST77922_PAGE_CMD3 (0xF2) +#define ST77922_PARAM_SET (0x00) + +/** + * @brief Initialize ST77922 LCD panel with SPI/QSPI interface + * + * @param[in] io LCD panel IO handle + * @param[in] panel_dev_config LCD panel device configuration + * @param[out] ret_panel LCD panel handle + * @return + * - ESP_OK: Success + * - Otherwise: Fail + */ +esp_err_t esp_lcd_new_panel_st77922_general(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel); + +#if SOC_LCD_RGB_SUPPORTED +/** + * @brief Initialize ST77922 LCD panel with RGB interface + * + * @param[in] io LCD panel IO handle + * @param[in] panel_dev_config LCD panel device configuration + * @param[out] ret_panel LCD panel handle + * @return + * - ESP_OK: Success + * - Otherwise: Fail + */ +esp_err_t esp_lcd_new_panel_st77922_rgb(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel); +#endif + +#if SOC_MIPI_DSI_SUPPORTED +/** + * @brief Initialize ST77922 LCD panel with MIPI interface + * + * @param[in] io LCD panel IO handle + * @param[in] panel_dev_config LCD panel device configuration + * @param[out] ret_panel LCD panel handle + * @return + * - ESP_OK: Success + * - Otherwise: Fail + */ +esp_err_t esp_lcd_new_panel_st77922_mipi(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, + esp_lcd_panel_handle_t *ret_panel); +#endif + +#ifdef __cplusplus +} +#endif From 95c1db9003a3b3de541ca148328f3c23e6056269 Mon Sep 17 00:00:00 2001 From: Enygma <94997734+EnygmaINC@users.noreply.github.com> Date: Mon, 9 Nov 2026 15:07:26 -0500 Subject: [PATCH 2/2] ST77922_1L4: remove bring-up code and unused vendor files Drop the debug prints and the ST77922_BRINGUP_TEST fill routine, and the Espressif esp_lcd_st77922 files that nothing compiled against (only the 4-field init-command struct was used; it now lives in the init table header). Co-Authored-By: Claude Sonnet 5.5 --- src/panels/Arduino_ST77922_1L4.h | 27 +- src/panels/st77922_1l4/esp_lcd_st77922.h | 283 ------------ .../st77922_1l4/esp_lcd_st77922_general.c | 423 ------------------ .../st77922_1l4/st77922_1l4_init_table.h | 12 + src/panels/st77922_1l4/st77922_interface.h | 69 --- 5 files changed, 15 insertions(+), 799 deletions(-) delete mode 100644 src/panels/st77922_1l4/esp_lcd_st77922.h delete mode 100644 src/panels/st77922_1l4/esp_lcd_st77922_general.c delete mode 100644 src/panels/st77922_1l4/st77922_interface.h diff --git a/src/panels/Arduino_ST77922_1L4.h b/src/panels/Arduino_ST77922_1L4.h index 9b8b223..299975c 100644 --- a/src/panels/Arduino_ST77922_1L4.h +++ b/src/panels/Arduino_ST77922_1L4.h @@ -34,7 +34,6 @@ #include -#include "st77922_1l4/esp_lcd_st77922.h" // only for st77922_lcd_init_cmd_t #include "st77922_1l4/st77922_1l4_init_table.h" class Arduino_ST77922_1L4 : public Arduino_GFX { @@ -69,11 +68,7 @@ class Arduino_ST77922_1L4 : public Arduino_GFX { buscfg.data3_io_num = _d3; buscfg.max_transfer_sz = CHUNK_PIXELS * 2 + 8; buscfg.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_IOMUX_PINS | SPICOMMON_BUSFLAG_QUAD; - Serial.printf("[ST77922] pins cs=%d sclk=%d d0=%d d1=%d d2=%d d3=%d speed=%d\n", _cs, _sclk, _d0, _d1, _d2, _d3, - (int)speed); - esp_err_t bus_err = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); - Serial.printf("[ST77922] spi_bus_initialize -> 0x%x\n", (unsigned)bus_err); - if (bus_err != ESP_OK) return false; + if (spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO) != ESP_OK) return false; spi_device_interface_config_t devcfg = {}; devcfg.command_bits = 0; @@ -83,9 +78,7 @@ class Arduino_ST77922_1L4 : public Arduino_GFX { devcfg.spics_io_num = _cs; devcfg.flags = SPI_DEVICE_HALFDUPLEX; devcfg.queue_size = 17; - esp_err_t dev_err = spi_bus_add_device(SPI2_HOST, &devcfg, &_dev); - Serial.printf("[ST77922] spi_bus_add_device -> 0x%x\n", (unsigned)dev_err); - if (dev_err != ESP_OK) return false; + if (spi_bus_add_device(SPI2_HOST, &devcfg, &_dev) != ESP_OK) return false; const size_t n = sizeof(es3c35p_lcd_init_cmds) / sizeof(es3c35p_lcd_init_cmds[0]); for (size_t i = 0; i < n; i++) { @@ -93,21 +86,7 @@ class Arduino_ST77922_1L4 : public Arduino_GFX { es3c35p_lcd_init_cmds[i].data_bytes); if (es3c35p_lcd_init_cmds[i].delay_ms) delay(es3c35p_lcd_init_cmds[i].delay_ms); } - Serial.printf("[ST77922] init sent: %u commands\n", (unsigned)n); - -#ifdef ST77922_BRINGUP_TEST - // Straight to the panel, bypassing Launcher: full white for 3 s, then - // red / green / blue thirds for 3 s. - Serial.println("[ST77922] test: full white 3s"); - fillNative(0, 0, WIDTH, HEIGHT, 0xFFFF); - delay(3000); - Serial.println("[ST77922] test: R/G/B bars 3s"); - fillNative(0, 0, WIDTH, 160, 0xF800); - fillNative(0, 160, WIDTH, 320, 0x07E0); - fillNative(0, 320, WIDTH, 480, 0x001F); - delay(3000); - Serial.println("[ST77922] test: done"); -#endif + return true; } diff --git a/src/panels/st77922_1l4/esp_lcd_st77922.h b/src/panels/st77922_1l4/esp_lcd_st77922.h deleted file mode 100644 index e47e71c..0000000 --- a/src/panels/st77922_1l4/esp_lcd_st77922.h +++ /dev/null @@ -1,283 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include - -#include "hal/lcd_types.h" -#include "esp_lcd_panel_vendor.h" -#include "esp_idf_version.h" -#if SOC_LCD_RGB_SUPPORTED -#include "esp_lcd_panel_rgb.h" -#endif - -#if SOC_MIPI_DSI_SUPPORTED -#include "esp_lcd_mipi_dsi.h" -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief LCD panel initialization commands. - * - */ -typedef struct { - int cmd; /* -#include - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "driver/gpio.h" -#include "esp_check.h" -#include "esp_lcd_panel_interface.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_vendor.h" -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_panel_commands.h" -#include "esp_log.h" -#include "esp_lcd_st77922.h" -#include "st77922_interface.h" - -// Normally auto-generated by ESP-IDF's component manager from -// idf_component.yml's "version: 2.0.2" when this is pulled in as a real -// managed component. Vendored flat into an Arduino sketch instead, so that -// generation step never runs — restated by hand, values only used for a -// log line at panel creation. -#ifndef ESP_LCD_ST77922_VER_MAJOR -#define ESP_LCD_ST77922_VER_MAJOR 2 -#define ESP_LCD_ST77922_VER_MINOR 0 -#define ESP_LCD_ST77922_VER_PATCH 2 -#endif - -static const char *TAG = "st77922_general"; - -static esp_err_t panel_st77922_del(esp_lcd_panel_t *panel); -static esp_err_t panel_st77922_reset(esp_lcd_panel_t *panel); -static esp_err_t panel_st77922_init(esp_lcd_panel_t *panel); -static esp_err_t panel_st77922_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); -static esp_err_t panel_st77922_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); -static esp_err_t panel_st77922_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); -static esp_err_t panel_st77922_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); -static esp_err_t panel_st77922_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); -static esp_err_t panel_st77922_disp_on_off(esp_lcd_panel_t *panel, bool off); - -typedef struct { - esp_lcd_panel_t base; - esp_lcd_panel_io_handle_t io; - int reset_gpio_num; - int x_gap; - int y_gap; - uint8_t fb_bits_per_pixel; - uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register - uint8_t colmod_val; // save surrent value of LCD_CMD_COLMOD register - const st77922_lcd_init_cmd_t *init_cmds; - uint16_t init_cmds_size; - struct { - unsigned int use_qspi_interface: 1; - unsigned int reset_level: 1; - } flags; -} st77922_panel_t; - -esp_err_t esp_lcd_new_panel_st77922_general(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel) -{ - ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); - - esp_err_t ret = ESP_OK; - st77922_panel_t *st77922 = NULL; - st77922 = calloc(1, sizeof(st77922_panel_t)); - ESP_GOTO_ON_FALSE(st77922, ESP_ERR_NO_MEM, err, TAG, "no mem for st77922 panel"); - - if (panel_dev_config->reset_gpio_num >= 0) { - gpio_config_t io_conf = { - .mode = GPIO_MODE_OUTPUT, - .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, - }; - ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); - } - - switch (panel_dev_config->rgb_ele_order) { - case LCD_RGB_ELEMENT_ORDER_RGB: - st77922->madctl_val = 0; - break; - case LCD_RGB_ELEMENT_ORDER_BGR: - st77922->madctl_val |= LCD_CMD_BGR_BIT; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order"); - break; - } - - switch (panel_dev_config->bits_per_pixel) { - case 16: // RGB565 - st77922->colmod_val = 0x55; - st77922->fb_bits_per_pixel = 16; - break; - case 18: // RGB666 - st77922->colmod_val = 0x66; - // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel - st77922->fb_bits_per_pixel = 24; - break; - case 24: // RGB888 - st77922->colmod_val = 0x77; - st77922->fb_bits_per_pixel = 24; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); - break; - } - - st77922->io = io; - st77922->reset_gpio_num = panel_dev_config->reset_gpio_num; - st77922->flags.reset_level = panel_dev_config->flags.reset_active_high; - st77922_vendor_config_t *vendor_config = (st77922_vendor_config_t *)panel_dev_config->vendor_config; - if (vendor_config) { - st77922->init_cmds = vendor_config->init_cmds; - st77922->init_cmds_size = vendor_config->init_cmds_size; - st77922->flags.use_qspi_interface = vendor_config->flags.use_qspi_interface; - } - st77922->base.del = panel_st77922_del; - st77922->base.reset = panel_st77922_reset; - st77922->base.init = panel_st77922_init; - st77922->base.draw_bitmap = panel_st77922_draw_bitmap; - st77922->base.invert_color = panel_st77922_invert_color; - st77922->base.set_gap = panel_st77922_set_gap; - st77922->base.mirror = panel_st77922_mirror; - st77922->base.swap_xy = panel_st77922_swap_xy; - st77922->base.disp_on_off = panel_st77922_disp_on_off; - *ret_panel = &(st77922->base); - ESP_LOGD(TAG, "new st77922 panel @%p", st77922); - - ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_ST77922_VER_MAJOR, ESP_LCD_ST77922_VER_MINOR, - ESP_LCD_ST77922_VER_PATCH); - - return ESP_OK; - -err: - if (st77922) { - if (panel_dev_config->reset_gpio_num >= 0) { - gpio_reset_pin(panel_dev_config->reset_gpio_num); - } - free(st77922); - } - return ret; -} - -static esp_err_t tx_param(st77922_panel_t *st77922, esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size) -{ - if (st77922->flags.use_qspi_interface) { - lcd_cmd &= 0xff; - lcd_cmd <<= 8; - lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24; - } - return esp_lcd_panel_io_tx_param(io, lcd_cmd, param, param_size); -} - -static esp_err_t tx_color(st77922_panel_t *st77922, esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size) -{ - if (st77922->flags.use_qspi_interface) { - lcd_cmd &= 0xff; - lcd_cmd <<= 8; - lcd_cmd |= LCD_OPCODE_WRITE_COLOR << 24; - } - return esp_lcd_panel_io_tx_color(io, lcd_cmd, param, param_size); -} - -static esp_err_t panel_st77922_del(esp_lcd_panel_t *panel) -{ - st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); - - if (st77922->reset_gpio_num >= 0) { - gpio_reset_pin(st77922->reset_gpio_num); - } - ESP_LOGD(TAG, "del st77922 panel @%p", st77922); - free(st77922); - return ESP_OK; -} - -static esp_err_t panel_st77922_reset(esp_lcd_panel_t *panel) -{ - st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); - esp_lcd_panel_io_handle_t io = st77922->io; - - // Perform hardware reset - if (st77922->reset_gpio_num >= 0) { - gpio_set_level(st77922->reset_gpio_num, st77922->flags.reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(st77922->reset_gpio_num, !st77922->flags.reset_level); - vTaskDelay(pdMS_TO_TICKS(120)); - } else { // Perform software reset - ESP_RETURN_ON_ERROR(tx_param(st77922, io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(120)); - } - - return ESP_OK; -} - -static const st77922_lcd_init_cmd_t vendor_specific_init_default[] = { - {0x28, (uint8_t []){0x00}, 0, 0}, - {0x10, (uint8_t []){0x00}, 0, 0}, - {0x2A, (uint8_t []){0x00, 0x00, 0x02, 0x13}, 4, 0}, - {0x2B, (uint8_t []){0x00, 0x00, 0x01, 0x2B}, 4, 0}, - {0xD0, (uint8_t []){0x80}, 1, 0}, - // ======================CMD2====================== - {0xF1, (uint8_t []){0x00}, 0, 0}, - {0x60, (uint8_t []){0x00, 0x00, 0x00}, 3, 0}, - {0x65, (uint8_t []){0x00}, 1, 0}, - {0x66, (uint8_t []){0x00, 0x3F}, 2, 0}, - {0xBE, (uint8_t []){0x1E, 0x01, 0x06}, 3, 0}, // VCM SSI - {0x70, (uint8_t []){0x02, 0x7D, 0x12, 0x14, 0x30, 0x00, 0x07, 0x52, 0x01, 0x00, 0x00, 0x1A}, 12, 0}, // VFP, VBP, Gate line - {0x71, (uint8_t []){0xD0}, 1, 0}, // MIPI CMD Mode - {0x7B, (uint8_t []){0x00, 0x08, 0x08}, 3, 0}, - {0x80, (uint8_t []){0x55, 0x62, 0x2F, 0x17, 0xF0, 0x52, 0x70, 0xD2, 0x52, 0x62, 0xEA}, 11, 0}, - {0x81, (uint8_t []){0x26, 0x52, 0x72, 0x27}, 4, 0}, - {0x84, (uint8_t []){0x92, 0x25}, 2, 0}, - {0x86, (uint8_t []){0xC6, 0x04, 0xB1, 0x02, 0x58, 0x12, 0x58, 0x10, 0x13, 0x01, 0xAA, 0x00, 0xAA, 0xAA}, 14, 0}, - {0x87, (uint8_t []){0x10, 0x10, 0x58, 0x00, 0x02, 0x3A}, 6, 0}, - {0x88, (uint8_t []){0x00, 0x00, 0x2C, 0x10, 0x04, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x06}, 15, 0}, - {0x89, (uint8_t []){0x00, 0x00, 0x00}, 3, 0}, - {0x8A, (uint8_t []){0x13, 0x00, 0x2C, 0x00, 0x00, 0x2C, 0x10, 0x10, 0x00, 0x3E, 0x19}, 11, 0}, - {0x8B, (uint8_t []){0x15, 0xB1, 0xB1, 0x44, 0x96, 0x2C, 0x10, 0x97, 0x8E}, 9, 0}, // VGL Pump - {0x8C, (uint8_t []){0x1D, 0xB1, 0xB1, 0x44, 0x96, 0x2C, 0x10, 0x50, 0x0F, 0x01, 0xC5, 0x12, 0x09}, 13, 0}, // VGH Pump - {0x8D, (uint8_t []){0x0C}, 1, 0}, - {0x8E, (uint8_t []){0x33, 0x01, 0x0C, 0x13, 0x01, 0x01}, 6, 0}, - {0x90, (uint8_t []){0x00, 0x44, 0x33, 0x36, 0x00, 0x79, 0x40, 0xB6, 0xB6}, 9, 0}, - {0x91, (uint8_t []){0x00, 0x44, 0x33, 0x37, 0x00, 0x78, 0x40, 0xB6, 0xB6}, 9, 0}, - {0x92, (uint8_t []){0x02, 0x44, 0x55, 0x82, 0x86, 0x2F, 0x00, 0x04, 0x73, 0xB6}, 10, 0}, - {0x93, (uint8_t []){0x0C, 0x00, 0x11, 0x81, 0x87, 0x3F, 0x00, 0x00, 0x73, 0x73}, 10, 0}, - {0x94, (uint8_t []){0x08, 0x00, 0x00, 0x00, 0x00, 0x00}, 6, 0}, - {0x95, (uint8_t []){0x1A, 0x1A, 0x00, 0x00, 0xFF}, 5, 0}, - {0x96, (uint8_t []){0x44, 0x35, 0x07, 0x16, 0x20, 0x21, 0x07, 0x06, 0xB6, 0xB6, 0x00, 0x40}, 12, 0}, - {0x97, (uint8_t []){0x44, 0x35, 0x25, 0x34, 0x22, 0x23, 0x05, 0x04, 0xB6, 0xB6, 0x00, 0x40}, 12, 0}, - {0xBA, (uint8_t []){0x55, 0xB6, 0xB6, 0xB6, 0xB6}, 5, 0}, - {0x9A, (uint8_t []){0x40, 0x0C, 0x56, 0x01, 0x08, 0xB6, 0xB6}, 7, 0}, - {0x9B, (uint8_t []){0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00}, 7, 0}, - {0x9C, (uint8_t []){0x00, 0x12, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00}, 13, 0}, - {0x9D, (uint8_t []){0x80, 0x15, 0x00, 0x07, 0x01, 0x80, 0x73, 0x73}, 8, 0}, - {0x9E, (uint8_t []){0x00, 0x00, 0x00, 0x00, 0x80, 0x1E, 0x01}, 7, 0}, - {0x9F, (uint8_t []){0xA0, 0x09, 0x00, 0x57}, 4, 0}, - {0xB3, (uint8_t []){0x00, 0x30, 0x0F, 0x00, 0x00, 0x00, 0x00}, 7, 0}, - {0xB4, (uint8_t []){0x10, 0x1C, 0x19, 0x14, 0x18, 0x01, 0x1D, 0x03, 0x12, 0x0A, 0x11, 0x08}, 12, 0}, - {0xB5, (uint8_t []){0x1C, 0x1C, 0x19, 0x14, 0x18, 0x00, 0x1D, 0x02, 0x10, 0x0B, 0x13, 0x09}, 12, 0}, - {0xB6, (uint8_t []){0xFF, 0xFF, 0x00, 0x0F, 0xFE, 0x0F, 0xFE}, 7, 0}, - {0xB7, (uint8_t []){0x00, 0x09, 0x10, 0x0B, 0x0A, 0x06, 0x38, 0x04, 0x04, 0x4F, 0x09, 0x15, 0x15, 0x30, 0x37, 0x0F}, 16, 0}, // GammaP - {0xB8, (uint8_t []){0x00, 0x09, 0x0F, 0x0A, 0x09, 0x05, 0x37, 0x03, 0x03, 0x4F, 0x09, 0x15, 0x15, 0x31, 0x36, 0x0F}, 16, 0}, // GammaN - {0xB9, (uint8_t []){0x23, 0x23}, 2, 0}, - {0xBF, (uint8_t []){0x0F, 0x13, 0x13, 0x09, 0x09, 0x09}, 6, 0}, // VGHP/VGLP - // ======================CMD3====================== - {0xF2, (uint8_t []){0x00}, 0, 0}, - {0x73, (uint8_t []){0x04, 0xBA, 0x1A, 0x58, 0x5B}, 5, 0}, // VOP= 5v - {0x77, (uint8_t []){0x6B, 0x5B, 0xFB, 0xC3, 0xC5}, 5, 0}, - {0x7A, (uint8_t []){0x15, 0x27}, 2, 0}, - {0x7B, (uint8_t []){0x04, 0x57}, 2, 0}, - {0x7E, (uint8_t []){0x01, 0x0E}, 2, 0}, - {0xBF, (uint8_t []){0x36}, 1, 0}, - {0xE3, (uint8_t []){0x43, 0x43}, 2, 0}, // VMF - // ======================CMD1====================== - {0xF0, (uint8_t []){0x00}, 0, 0}, - {0x21, (uint8_t []){0x00}, 1, 0}, - {0x11, (uint8_t []){0x00}, 1, 120}, - {0x35, (uint8_t []){0x00}, 1, 20}, -}; - -static esp_err_t panel_st77922_init(esp_lcd_panel_t *panel) -{ - st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); - esp_lcd_panel_io_handle_t io = st77922->io; - const st77922_lcd_init_cmd_t *init_cmds = NULL; - uint16_t init_cmds_size = 0; - bool is_command1_enable = true; - bool is_cmd_overwritten = false; - - ESP_RETURN_ON_ERROR(tx_param(st77922, io, LCD_CMD_MADCTL, (uint8_t[]) { - st77922->madctl_val, - }, 1), TAG, "send command failed"); - ESP_RETURN_ON_ERROR(tx_param(st77922, io, LCD_CMD_COLMOD, (uint8_t[]) { - st77922->colmod_val, - }, 1), TAG, "send command failed"); - - // vendor specific initialization, it can be different between manufacturers - // should consult the LCD supplier for initialization sequence code - if (st77922->init_cmds) { - init_cmds = st77922->init_cmds; - init_cmds_size = st77922->init_cmds_size; - } else { - init_cmds = vendor_specific_init_default; - init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st77922_lcd_init_cmd_t); - } - - for (int i = 0; i < init_cmds_size; i++) { - // Check if the command has been used or conflicts with the internal - if (is_command1_enable && (init_cmds[i].data_bytes > 0)) { - switch (init_cmds[i].cmd) { - case LCD_CMD_MADCTL: - is_cmd_overwritten = true; - st77922->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - case LCD_CMD_COLMOD: - is_cmd_overwritten = true; - st77922->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - default: - is_cmd_overwritten = false; - break; - } - - if (is_cmd_overwritten) { - is_cmd_overwritten = false; - ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); - } - } - - // Send command - ESP_RETURN_ON_ERROR(tx_param(st77922, io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); - - // Check if the current cmd is the command1 enable cmd - if ((init_cmds[i].cmd == ST77922_PAGE_CMD2 || init_cmds[i].cmd == ST77922_PAGE_CMD3) && init_cmds[i].data_bytes > 0) { - is_command1_enable = false; - } else if (init_cmds[i].cmd == ST77922_PAGE_CMD1 && init_cmds[i].data_bytes > 0) { - is_command1_enable = true; - } - } - ESP_LOGD(TAG, "send init commands success"); - - return ESP_OK; -} - -static esp_err_t panel_st77922_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) -{ - st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); - assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); - esp_lcd_panel_io_handle_t io = st77922->io; - - x_start += st77922->x_gap; - x_end += st77922->x_gap; - y_start += st77922->y_gap; - y_end += st77922->y_gap; - - // define an area of frame memory where MCU can access - ESP_RETURN_ON_ERROR(tx_param(st77922, io, LCD_CMD_CASET, (uint8_t[]) { - (x_start >> 8) & 0xFF, - x_start & 0xFF, - ((x_end - 1) >> 8) & 0xFF, - (x_end - 1) & 0xFF, - }, 4), TAG, "send command failed"); - ESP_RETURN_ON_ERROR(tx_param(st77922, io, LCD_CMD_RASET, (uint8_t[]) { - (y_start >> 8) & 0xFF, - y_start & 0xFF, - ((y_end - 1) >> 8) & 0xFF, - (y_end - 1) & 0xFF, - }, 4), TAG, "send command failed"); - // transfer frame buffer - size_t len = (x_end - x_start) * (y_end - y_start) * st77922->fb_bits_per_pixel / 8; - ESP_RETURN_ON_ERROR(tx_color(st77922, io, LCD_CMD_RAMWR, color_data, len), TAG, "send color data failed"); - - return ESP_OK; -} - -static esp_err_t panel_st77922_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) -{ - st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); - esp_lcd_panel_io_handle_t io = st77922->io; - int command = 0; - if (invert_color_data) { - command = LCD_CMD_INVON; - } else { - command = LCD_CMD_INVOFF; - } - ESP_RETURN_ON_ERROR(tx_param(st77922, io, command, NULL, 0), TAG, "send command failed"); - return ESP_OK; -} - -static esp_err_t panel_st77922_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) -{ - st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); - esp_lcd_panel_io_handle_t io = st77922->io; - esp_err_t ret = ESP_OK; - - if (mirror_x) { - st77922->madctl_val |= BIT(6); - } else { - st77922->madctl_val &= ~BIT(6); - } - if (mirror_y) { - st77922->madctl_val |= BIT(7); - } else { - st77922->madctl_val &= ~BIT(7); - } - ESP_RETURN_ON_ERROR(tx_param(st77922, io, LCD_CMD_MADCTL, (uint8_t[]) { - st77922->madctl_val - }, 1), TAG, "send command failed"); - return ret; -} - -static esp_err_t panel_st77922_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) -{ - ESP_LOGW(TAG, "swap_xy is not supported by this panel"); - return ESP_ERR_NOT_SUPPORTED; -} - -static esp_err_t panel_st77922_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) -{ - st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); - st77922->x_gap = x_gap; - st77922->y_gap = y_gap; - return ESP_OK; -} - -static esp_err_t panel_st77922_disp_on_off(esp_lcd_panel_t *panel, bool on_off) -{ - st77922_panel_t *st77922 = __containerof(panel, st77922_panel_t, base); - esp_lcd_panel_io_handle_t io = st77922->io; - int command = 0; - - if (on_off) { - command = LCD_CMD_DISPON; - } else { - command = LCD_CMD_DISPOFF; - } - ESP_RETURN_ON_ERROR(tx_param(st77922, io, command, NULL, 0), TAG, "send command failed"); - return ESP_OK; -} diff --git a/src/panels/st77922_1l4/st77922_1l4_init_table.h b/src/panels/st77922_1l4/st77922_1l4_init_table.h index 838edb6..29f1cb2 100644 --- a/src/panels/st77922_1l4/st77922_1l4_init_table.h +++ b/src/panels/st77922_1l4/st77922_1l4_init_table.h @@ -1,3 +1,15 @@ +#pragma once + +#include +#include + +typedef struct { + int cmd; // register + const void *data; // parameter bytes + size_t data_bytes; // number of parameter bytes + unsigned int delay_ms; // delay after this register write +} st77922_lcd_init_cmd_t; + // Verbatim from the board vendor's own example (LCDwiki ES3C35P, // github.com/ydedox/st77922 Example_01, the ACTIVE st77922_lcd_init[] table -- // not the commented-out one above it), renamed only. Confirmed working on this diff --git a/src/panels/st77922_1l4/st77922_interface.h b/src/panels/st77922_1l4/st77922_interface.h deleted file mode 100644 index 3ac0417..0000000 --- a/src/panels/st77922_1l4/st77922_interface.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include "esp_err.h" -#include "esp_lcd_types.h" -#include "esp_lcd_panel_vendor.h" -#include "soc/soc_caps.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define LCD_OPCODE_WRITE_CMD (0x02ULL) -#define LCD_OPCODE_READ_CMD (0x0BULL) -#define LCD_OPCODE_WRITE_COLOR (0x32ULL) - -#define ST77922_PAGE_CMD1 (0xF0) -#define ST77922_PAGE_CMD2 (0xF1) -#define ST77922_PAGE_CMD3 (0xF2) -#define ST77922_PARAM_SET (0x00) - -/** - * @brief Initialize ST77922 LCD panel with SPI/QSPI interface - * - * @param[in] io LCD panel IO handle - * @param[in] panel_dev_config LCD panel device configuration - * @param[out] ret_panel LCD panel handle - * @return - * - ESP_OK: Success - * - Otherwise: Fail - */ -esp_err_t esp_lcd_new_panel_st77922_general(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel); - -#if SOC_LCD_RGB_SUPPORTED -/** - * @brief Initialize ST77922 LCD panel with RGB interface - * - * @param[in] io LCD panel IO handle - * @param[in] panel_dev_config LCD panel device configuration - * @param[out] ret_panel LCD panel handle - * @return - * - ESP_OK: Success - * - Otherwise: Fail - */ -esp_err_t esp_lcd_new_panel_st77922_rgb(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel); -#endif - -#if SOC_MIPI_DSI_SUPPORTED -/** - * @brief Initialize ST77922 LCD panel with MIPI interface - * - * @param[in] io LCD panel IO handle - * @param[in] panel_dev_config LCD panel device configuration - * @param[out] ret_panel LCD panel handle - * @return - * - ESP_OK: Success - * - Otherwise: Fail - */ -esp_err_t esp_lcd_new_panel_st77922_mipi(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, - esp_lcd_panel_handle_t *ret_panel); -#endif - -#ifdef __cplusplus -} -#endif