From df9325228141d46464065cafcadb694e63d64cdf Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Thu, 9 Jul 2026 21:44:49 -0400 Subject: [PATCH] feat: add support for waveshare esp32-s3-rlcd board --- .../waveshare-esp32-s3-rlcd/CMakeLists.txt | 8 + .../Source/Configuration.cpp | 88 ++++++ .../Source/devices/Display.cpp | 23 ++ .../Source/devices/Display.h | 6 + .../waveshare-esp32-s3-rlcd/Source/module.cpp | 21 ++ .../waveshare-esp32-s3-rlcd/device.properties | 24 ++ .../waveshare-esp32-s3-rlcd/devicetree.yaml | 3 + .../waveshare,esp32-s3-rlcd.dts | 61 ++++ Drivers/ST7305/CMakeLists.txt | 5 + Drivers/ST7305/Source/St7305Display.cpp | 99 ++++++ Drivers/ST7305/Source/St7305Display.h | 99 ++++++ Drivers/ST7305/Source/esp_lcd_st7305.c | 286 ++++++++++++++++++ Drivers/ST7305/Source/esp_lcd_st7305.h | 49 +++ 13 files changed, 772 insertions(+) create mode 100644 Devices/waveshare-esp32-s3-rlcd/CMakeLists.txt create mode 100644 Devices/waveshare-esp32-s3-rlcd/Source/Configuration.cpp create mode 100644 Devices/waveshare-esp32-s3-rlcd/Source/devices/Display.cpp create mode 100644 Devices/waveshare-esp32-s3-rlcd/Source/devices/Display.h create mode 100644 Devices/waveshare-esp32-s3-rlcd/Source/module.cpp create mode 100644 Devices/waveshare-esp32-s3-rlcd/device.properties create mode 100644 Devices/waveshare-esp32-s3-rlcd/devicetree.yaml create mode 100644 Devices/waveshare-esp32-s3-rlcd/waveshare,esp32-s3-rlcd.dts create mode 100644 Drivers/ST7305/CMakeLists.txt create mode 100644 Drivers/ST7305/Source/St7305Display.cpp create mode 100644 Drivers/ST7305/Source/St7305Display.h create mode 100644 Drivers/ST7305/Source/esp_lcd_st7305.c create mode 100644 Drivers/ST7305/Source/esp_lcd_st7305.h diff --git a/Devices/waveshare-esp32-s3-rlcd/CMakeLists.txt b/Devices/waveshare-esp32-s3-rlcd/CMakeLists.txt new file mode 100644 index 00000000..06dffd04 --- /dev/null +++ b/Devices/waveshare-esp32-s3-rlcd/CMakeLists.txt @@ -0,0 +1,8 @@ +# Force CMake reload to detect new files module.cpp and Configuration.cpp +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) + +idf_component_register( + SRCS ${SOURCE_FILES} + INCLUDE_DIRS "Source" + REQUIRES Tactility ButtonControl ST7305 PwmBacklight driver +) diff --git a/Devices/waveshare-esp32-s3-rlcd/Source/Configuration.cpp b/Devices/waveshare-esp32-s3-rlcd/Source/Configuration.cpp new file mode 100644 index 00000000..3cbf0dae --- /dev/null +++ b/Devices/waveshare-esp32-s3-rlcd/Source/Configuration.cpp @@ -0,0 +1,88 @@ +#include "devices/Display.h" +#include + +#include +#include +#include +#include +#include + +#include +#include + +using namespace tt::hal; + +static constexpr auto* TAG = "WaveshareRLCD"; +static constexpr uint8_t ES8311_I2C_ADDR = 0x18; + +static error_t initCodec(::Device* i2c_controller) { + static constexpr uint8_t ENABLED_BULK_DATA[] = { + 0x00, 0x80, // RESET/CSM POWER ON + 0x01, 0x3F, // CLOCK_MANAGER/ use MCLK pin (external), all clocks on + 0x02, 0x00, // CLOCK_MANAGER/ pre_div=1 pre_multi=1 (256x MCLK is correct ratio) + 0x03, 0x10, // CLOCK_MANAGER/ fs_mode=0, adc_osr=16 + 0x04, 0x10, // CLOCK_MANAGER/ dac_osr=16 + 0x05, 0x00, // CLOCK_MANAGER/ adc_div=1, dac_div=1 + 0x06, 0x03, // CLOCK_MANAGER/ bclk_div=4 + 0x07, 0x00, // CLOCK_MANAGER/ lrck_h=0 + 0x08, 0xFF, // CLOCK_MANAGER/ lrck_l=255 (div=256) + 0x09, 0x0C, // SDPIN_REG09/ DAC SDP format = standard I2S, word length = 16-bit + 0x0A, 0x0C, // SDPOUT_REG0A/ ADC SDP format = standard I2S, word length = 16-bit + 0x0D, 0x01, // SYSTEM/ Power up analog circuitry + 0x0E, 0x02, // SYSTEM/ Enable analog PGA, enable ADC modulator + 0x12, 0x00, // SYSTEM/ power-up DAC + 0x13, 0x10, // SYSTEM/ Enable output to HP drive (headphone/speaker) + 0x14, 0x1A, // SYSTEM/ Select Mic1p-Mic1n / max PGA gain (+30dB) + 0x17, 0xFF, // ADC_REG17/ ADC Volume (MAXGAIN) + 0x1C, 0x6A, // ADC_REG1C/ ADC Equalizer bypass, cancel DC offset in digital + 0x32, 0xBF, // DAC_REG32/ DAC Volume (0xBF = 191) + 0x37, 0x08, // DAC_REG37/ Bypass DAC equalizer + }; + + return i2c_controller_write_register_array( + i2c_controller, + ES8311_I2C_ADDR, + ENABLED_BULK_DATA, + sizeof(ENABLED_BULK_DATA), + pdMS_TO_TICKS(1000) + ); +} + +static bool initBoot() { + // 1. Initialize and power on the Speaker Amplifier (GPIO 46, Active HIGH) + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << GPIO_NUM_46); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + // Drive HIGH to enable the speaker amplifier + gpio_set_level(GPIO_NUM_46, 1); + + // 2. Configure the ES8311 Codec over the I2C bus + auto* i2c_bus = device_find_by_name("i2c0"); + if (i2c_bus != nullptr) { + error_t error = initCodec(i2c_bus); + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to initialize ES8311 codec: %s", error_to_string(error)); + } + } else { + LOG_E(TAG, "i2c0 bus not found, skipping ES8311 initialization"); + } + + return true; +} + +static DeviceVector createDevices() { + return { + createDisplay(), + ButtonControl::createTwoButtonControl(GPIO_NUM_18, GPIO_NUM_0) // KEY=18 (primary), BOOT=0 (secondary) + }; +} + +extern const Configuration hardwareConfiguration = { + .initBoot = initBoot, + .createDevices = createDevices +}; diff --git a/Devices/waveshare-esp32-s3-rlcd/Source/devices/Display.cpp b/Devices/waveshare-esp32-s3-rlcd/Source/devices/Display.cpp new file mode 100644 index 00000000..238f73b7 --- /dev/null +++ b/Devices/waveshare-esp32-s3-rlcd/Source/devices/Display.cpp @@ -0,0 +1,23 @@ +#include "Display.h" +#include + +std::shared_ptr createDisplay() { + auto configuration = std::make_unique( + SPI2_HOST, + GPIO_NUM_40, // CS + GPIO_NUM_5, // DC + 400, // Width + 300, // Height + nullptr, // Touch device + false, // swapXY + false, // mirrorX + false, // mirrorY + false, // invertColor + 0 // bufferSize (0 = default, which is full screen = 120,000 pixels) + ); + + configuration->resetPin = GPIO_NUM_41; + + auto display = std::make_shared(std::move(configuration)); + return std::reinterpret_pointer_cast(display); +} diff --git a/Devices/waveshare-esp32-s3-rlcd/Source/devices/Display.h b/Devices/waveshare-esp32-s3-rlcd/Source/devices/Display.h new file mode 100644 index 00000000..344191d6 --- /dev/null +++ b/Devices/waveshare-esp32-s3-rlcd/Source/devices/Display.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +std::shared_ptr createDisplay(); diff --git a/Devices/waveshare-esp32-s3-rlcd/Source/module.cpp b/Devices/waveshare-esp32-s3-rlcd/Source/module.cpp new file mode 100644 index 00000000..320161af --- /dev/null +++ b/Devices/waveshare-esp32-s3-rlcd/Source/module.cpp @@ -0,0 +1,21 @@ +#include + +extern "C" { + +static error_t start() { + return ERROR_NONE; +} + +static error_t stop() { + return ERROR_NONE; +} + +struct Module waveshare_esp32_s3_rlcd_module = { + .name = "waveshare-esp32-s3-rlcd", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Devices/waveshare-esp32-s3-rlcd/device.properties b/Devices/waveshare-esp32-s3-rlcd/device.properties new file mode 100644 index 00000000..87197452 --- /dev/null +++ b/Devices/waveshare-esp32-s3-rlcd/device.properties @@ -0,0 +1,24 @@ +general.vendor=WaveShare +general.name=ESP32-S3-RLCD-4.2 +general.incubating=true + +apps.launcherAppId=Launcher +apps.autoStartAppId=ApWebServer + +hardware.target=ESP32S3 +hardware.flashSize=16MB +hardware.spiRam=true +hardware.spiRamMode=OCT +hardware.spiRamSpeed=120M +hardware.tinyUsb=true +hardware.esptoolFlashFreq=120M +hardware.bluetooth=true + +storage.userDataLocation=SD + +display.size=4.2" +display.shape=rectangle +display.dpi=120 + +lvgl.colorDepth=16 +lvgl.uiDensity=compact diff --git a/Devices/waveshare-esp32-s3-rlcd/devicetree.yaml b/Devices/waveshare-esp32-s3-rlcd/devicetree.yaml new file mode 100644 index 00000000..03cb8326 --- /dev/null +++ b/Devices/waveshare-esp32-s3-rlcd/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: +- Platforms/platform-esp32 +dts: waveshare,esp32-s3-rlcd.dts diff --git a/Devices/waveshare-esp32-s3-rlcd/waveshare,esp32-s3-rlcd.dts b/Devices/waveshare-esp32-s3-rlcd/waveshare,esp32-s3-rlcd.dts new file mode 100644 index 00000000..646be565 --- /dev/null +++ b/Devices/waveshare-esp32-s3-rlcd/waveshare,esp32-s3-rlcd.dts @@ -0,0 +1,61 @@ +/dts-v1/; + +#include +#include +#include +#include +#include +#include +#include +#include + +/ { + compatible = "root"; + model = "Waveshare ESP32-S3-RLCD-4.2"; + + ble0 { + compatible = "espressif,esp32-ble"; + status = "disabled"; + }; + + gpio0 { + compatible = "espressif,esp32-gpio"; + gpio-count = <49>; + }; + + i2c0 { + compatible = "espressif,esp32-i2c"; + port = ; + clock-frequency = <400000>; + pin-sda = <&gpio0 13 GPIO_FLAG_NONE>; + pin-scl = <&gpio0 14 GPIO_FLAG_NONE>; + }; + + spi0 { + compatible = "espressif,esp32-spi"; + host = ; + cs-gpios = <&gpio0 40 GPIO_FLAG_NONE>; + pin-mosi = <&gpio0 12 GPIO_FLAG_NONE>; + pin-sclk = <&gpio0 11 GPIO_FLAG_NONE>; + + display { + compatible = "display-placeholder"; + }; + }; + + sdmmc0 { + compatible = "espressif,esp32-sdmmc"; + pin-clk = <&gpio0 36 GPIO_FLAG_NONE>; + pin-cmd = <&gpio0 35 GPIO_FLAG_NONE>; + pin-d0 = <&gpio0 37 GPIO_FLAG_NONE>; + slot = ; + bus-width = <1>; + }; + + uart0 { + compatible = "espressif,esp32-uart"; + port = ; + pin-tx = <&gpio0 43 GPIO_FLAG_NONE>; + pin-rx = <&gpio0 44 GPIO_FLAG_NONE>; + }; +}; diff --git a/Drivers/ST7305/CMakeLists.txt b/Drivers/ST7305/CMakeLists.txt new file mode 100644 index 00000000..7cd677c7 --- /dev/null +++ b/Drivers/ST7305/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "Source" + INCLUDE_DIRS "Source" + REQUIRES Tactility driver EspLcdCompat +) diff --git a/Drivers/ST7305/Source/St7305Display.cpp b/Drivers/ST7305/Source/St7305Display.cpp new file mode 100644 index 00000000..e1223712 --- /dev/null +++ b/Drivers/ST7305/Source/St7305Display.cpp @@ -0,0 +1,99 @@ +#include "St7305Display.h" +#include "esp_lcd_st7305.h" + +#include +#include +#include + +static const auto LOGGER = tt::Logger("ST7305"); + +bool St7305Display::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) { + LOGGER.info("Starting ST7305 SPI panel IO creation"); + + const esp_lcd_panel_io_spi_config_t panel_io_config = { + .cs_gpio_num = configuration->csPin, + .dc_gpio_num = configuration->dcPin, + .spi_mode = 0, + .pclk_hz = configuration->pixelClockFrequency, + .trans_queue_depth = configuration->transactionQueueDepth, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_high_on_cmd = 0, + .dc_low_on_data = 0, + .dc_low_on_param = 0, + .octal_mode = 0, + .quad_mode = 0, + .sio_mode = 0, + .lsb_first = 0, + .cs_high_active = 0 + } + }; + + if (esp_lcd_new_panel_io_spi(configuration->spiHostDevice, &panel_io_config, &outHandle) != ESP_OK) { + LOGGER.error("Failed to create panel SPI IO"); + return false; + } + + return true; +} + +bool St7305Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) { + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = configuration->resetPin, + .color_space = ESP_LCD_COLOR_SPACE_MONOCHROME, + .data_endian = LCD_RGB_DATA_ENDIAN_BIG, + .bits_per_pixel = 1, + .flags = { + .reset_active_high = false + }, + .vendor_config = nullptr + }; + + if (esp_lcd_new_panel_st7305(ioHandle, &panel_config, &panelHandle) != ESP_OK) { + LOGGER.error("Failed to create st7305 panel"); + return false; + } + + if (esp_lcd_panel_reset(panelHandle) != ESP_OK) { + LOGGER.error("Failed to reset st7305 panel"); + return false; + } + + if (esp_lcd_panel_init(panelHandle) != ESP_OK) { + LOGGER.error("Failed to init st7305 panel"); + return false; + } + + return true; +} + +lvgl_port_display_cfg_t St7305Display::getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) { + return lvgl_port_display_cfg_t { + .io_handle = ioHandle, + .panel_handle = panelHandle, + .control_handle = nullptr, + .buffer_size = configuration->bufferSize, + .double_buffer = false, // Monochrome displays usually use single buffering to save RAM + .trans_size = 0, + .hres = configuration->horizontalResolution, + .vres = configuration->verticalResolution, + .monochrome = true, // Enables esp_lvgl_port monochrome converter + .rotation = { + .swap_xy = false, + .mirror_x = false, + .mirror_y = false, + }, + .color_format = LV_COLOR_FORMAT_RGB565, // Must be RGB565 for monochrome mode to trigger converter + .flags = { + .buff_dma = false, + .buff_spiram = false, + .sw_rotate = false, + .swap_bytes = false, + .full_refresh = true, // We want full refresh to rewrite the converted block format to ST7305 + .direct_mode = false + } + }; +} diff --git a/Drivers/ST7305/Source/St7305Display.h b/Drivers/ST7305/Source/St7305Display.h new file mode 100644 index 00000000..8be4fd11 --- /dev/null +++ b/Drivers/ST7305/Source/St7305Display.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +class St7305Display final : public EspLcdDisplay { + +public: + + class Configuration { + + public: + + Configuration( + spi_host_device_t spiHostDevice, + gpio_num_t csPin, + gpio_num_t dcPin, + unsigned int horizontalResolution, + unsigned int verticalResolution, + std::shared_ptr touch = nullptr, + bool swapXY = false, + bool mirrorX = false, + bool mirrorY = false, + bool invertColor = false, + uint32_t bufferSize = 0 + ) : spiHostDevice(spiHostDevice), + csPin(csPin), + dcPin(dcPin), + horizontalResolution(horizontalResolution), + verticalResolution(verticalResolution), + swapXY(swapXY), + mirrorX(mirrorX), + mirrorY(mirrorY), + invertColor(invertColor), + bufferSize(bufferSize), + touch(std::move(touch)) + { + if (this->bufferSize == 0) { + // For monochrome display, full pixel count is expected for buffer size + this->bufferSize = horizontalResolution * verticalResolution; + } + } + + spi_host_device_t spiHostDevice; + gpio_num_t csPin; + gpio_num_t dcPin; + gpio_num_t resetPin = GPIO_NUM_NC; + unsigned int pixelClockFrequency = 10'000'000; // 10MHz SPI clock for ST7305 + size_t transactionQueueDepth = 10; + unsigned int horizontalResolution; + unsigned int verticalResolution; + bool swapXY = false; + bool mirrorX = false; + bool mirrorY = false; + bool invertColor = false; + uint32_t bufferSize = 0; + std::shared_ptr touch; + std::function _Nullable backlightDutyFunction = nullptr; + }; + +private: + + std::unique_ptr configuration; + + bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) override; + + bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) override; + + lvgl_port_display_cfg_t getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) override; + +public: + + explicit St7305Display(std::unique_ptr inConfiguration) : + configuration(std::move(inConfiguration)) + {} + + std::string getName() const override { return "ST7305"; } + + std::string getDescription() const override { return "ST7305 monochrome reflective LCD display"; } + + std::shared_ptr _Nullable getTouchDevice() override { return configuration->touch; } + + void setBacklightDuty(uint8_t backlightDuty) override { + if (configuration->backlightDutyFunction != nullptr) { + configuration->backlightDutyFunction(backlightDuty); + } + } + + bool supportsBacklightDuty() const override { return configuration->backlightDutyFunction != nullptr; } +}; + +std::shared_ptr createDisplay(); diff --git a/Drivers/ST7305/Source/esp_lcd_st7305.c b/Drivers/ST7305/Source/esp_lcd_st7305.c new file mode 100644 index 00000000..6e2a67d1 --- /dev/null +++ b/Drivers/ST7305/Source/esp_lcd_st7305.c @@ -0,0 +1,286 @@ +#include "esp_lcd_st7305.h" +#include "soc/soc_caps.h" +#include "esp_check.h" +#include "esp_lcd_types.h" +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.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 "driver/gpio.h" +#include +#include "esp_log.h" + +static const char *TAG = "st7305"; + +static esp_err_t panel_st7305_del(esp_lcd_panel_t *panel); +static esp_err_t panel_st7305_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_st7305_init(esp_lcd_panel_t *panel); +static esp_err_t panel_st7305_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_st7305_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_st7305_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_st7305_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); +static esp_err_t panel_st7305_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); +static esp_err_t panel_st7305_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; + bool reset_level; + int x_gap; + int y_gap; + int width; + int height; + uint8_t rotation; + uint8_t madctl_val; + const st7305_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; +} st7305_panel_t; + +static const st7305_lcd_init_cmd_t st7305_init_cmds[] = { + {0xD6, (uint8_t[]){0x17, 0x02}, 2, 0}, // NVM Load Control + {0xD1, (uint8_t[]){0x01}, 1, 0}, // Booster Enable + {0xC0, (uint8_t[]){0x11, 0x04}, 2, 0}, // Gate Voltage Setting + {0xC1, (uint8_t[]){0x69, 0x69, 0x69, 0x69}, 4, 0}, // VSHP Setting + {0xC2, (uint8_t[]){0x19, 0x19, 0x19, 0x19}, 4, 0}, // VSLP Setting + {0xC4, (uint8_t[]){0x4B, 0x4B, 0x4B, 0x4B}, 4, 0}, // VSHN Setting + {0xC5, (uint8_t[]){0x19, 0x19, 0x19, 0x19}, 4, 0}, // VSLN Setting + {0xD8, (uint8_t[]){0x80, 0xE9}, 2, 0}, // OSC Setting + {0xB2, (uint8_t[]){0x02}, 1, 0}, // Frame Rate Control + {0xB3, (uint8_t[]){0xE5, 0xF6, 0x05, 0x46, 0x77, 0x77, 0x77, 0x77, 0x76, 0x45}, 10, 0}, // Gate EQ HPM + {0xB4, (uint8_t[]){0x05, 0x46, 0x77, 0x77, 0x77, 0x77, 0x76, 0x45}, 8, 0}, // Gate EQ LPM + {0x62, (uint8_t[]){0x32, 0x03, 0x1F}, 3, 0}, // Gate Timing Control + {0xB7, (uint8_t[]){0x13}, 1, 0}, // Source EQ Enable + {0xB0, (uint8_t[]){0x64}, 1, 0}, // Gate Line Setting: 300 line (0x64 = 100 * 3) + {0x11, NULL, 0, 200}, // Sleep out + {0xC9, (uint8_t[]){0x00}, 1, 0}, // Source Voltage Select + {0x36, (uint8_t[]){0x48}, 1, 0}, // Memory Data Access Control (MX=1, DO=1) + {0x3A, (uint8_t[]){0x11}, 1, 0}, // Data Format Select: 1bpp + {0xB9, (uint8_t[]){0x20}, 1, 0}, // Gamma Mode Setting + {0xB8, (uint8_t[]){0x29}, 1, 0}, // Panel Setting + {0x21, NULL, 0, 0}, // Display Inversion On + {0x2A, (uint8_t[]){0x12, 0x2A}, 2, 0}, // Column Address Setting + {0x2B, (uint8_t[]){0x00, 0xC7}, 2, 0}, // Row Address Setting + {0x35, (uint8_t[]){0x00}, 1, 0}, // TE Line + {0xD0, (uint8_t[]){0xFF}, 1, 0}, // Auto power down ON + {0x38, NULL, 0, 0}, // High Power Mode ON + {0x29, NULL, 0, 100}, // Display ON +}; + +esp_err_t esp_lcd_new_panel_st7305(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_err_t ret = ESP_OK; + st7305_panel_t *st7305 = NULL; + gpio_config_t io_conf = { 0 }; + + ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + st7305 = (st7305_panel_t *)calloc(1, sizeof(st7305_panel_t)); + ESP_GOTO_ON_FALSE(st7305, ESP_ERR_NO_MEM, err, TAG, "no mem for st7305 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) { + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.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"); + } + st7305->width = ST7305_WIDTH; + st7305->height = ST7305_HEIGHT; + st7305->madctl_val = 0x48; // MX=1, DO=1 + st7305->rotation = 0; + + st7305->io = io; + st7305->reset_gpio_num = panel_dev_config->reset_gpio_num; + st7305->reset_level = panel_dev_config->flags.reset_active_high; + if (panel_dev_config->vendor_config) { + st7305->init_cmds = ((st7305_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds; + st7305->init_cmds_size = ((st7305_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size; + } else { + st7305->init_cmds = st7305_init_cmds; + st7305->init_cmds_size = sizeof(st7305_init_cmds) / sizeof(st7305_lcd_init_cmd_t); + } + st7305->base.del = panel_st7305_del; + st7305->base.reset = panel_st7305_reset; + st7305->base.init = panel_st7305_init; + st7305->base.draw_bitmap = panel_st7305_draw_bitmap; + st7305->base.invert_color = panel_st7305_invert_color; + st7305->base.set_gap = panel_st7305_set_gap; + st7305->base.mirror = panel_st7305_mirror; + st7305->base.swap_xy = panel_st7305_swap_xy; +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + st7305->base.disp_off = panel_st7305_disp_on_off; +#else + st7305->base.disp_on_off = panel_st7305_disp_on_off; +#endif + *ret_panel = &(st7305->base); + ESP_LOGD(TAG, "new st7305 panel @%p", st7305); + + return ESP_OK; + +err: + if (st7305) { + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(st7305); + } + return ret; +} + +static esp_err_t panel_st7305_del(esp_lcd_panel_t *panel) +{ + st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base); + if (st7305->reset_gpio_num >= 0) { + gpio_reset_pin(st7305->reset_gpio_num); + } + free(st7305); + return ESP_OK; +} + +static esp_err_t panel_st7305_reset(esp_lcd_panel_t *panel) +{ + st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base); + if (st7305->reset_gpio_num >= 0) { + gpio_set_level(st7305->reset_gpio_num, st7305->reset_level); + vTaskDelay(pdMS_TO_TICKS(50)); + gpio_set_level(st7305->reset_gpio_num, !st7305->reset_level); + vTaskDelay(pdMS_TO_TICKS(20)); + gpio_set_level(st7305->reset_gpio_num, st7305->reset_level); + vTaskDelay(pdMS_TO_TICKS(50)); + } + return ESP_OK; +} + +static esp_err_t panel_st7305_init(esp_lcd_panel_t *panel) +{ + st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base); + esp_lcd_panel_io_handle_t io = st7305->io; + + for (size_t i = 0; i < st7305->init_cmds_size; i++) { + if (st7305->init_cmds[i].data_bytes > 0) { + esp_lcd_panel_io_tx_param(io, st7305->init_cmds[i].cmd, + st7305->init_cmds[i].data, + st7305->init_cmds[i].data_bytes); + } else { + esp_lcd_panel_io_tx_param(io, st7305->init_cmds[i].cmd, NULL, 0); + } + if (st7305->init_cmds[i].delay_ms > 0) { + vTaskDelay(pdMS_TO_TICKS(st7305->init_cmds[i].delay_ms)); + } + } + return ESP_OK; +} + +static esp_err_t panel_st7305_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) +{ + st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base); + esp_lcd_panel_io_handle_t io = st7305->io; + + // Output buffer size is 15000 bytes (400 * 300 / 8) + size_t lcd_buf_size = 15000; + uint8_t *temp_buffer = heap_caps_malloc(lcd_buf_size, MALLOC_CAP_DMA); + if (!temp_buffer) { + return ESP_ERR_NO_MEM; + } + memset(temp_buffer, 0, lcd_buf_size); + + const uint8_t *src = (const uint8_t *)color_data; + + // Convert from vertical-page format (SSD1306-style) to ST7305 landscape 2x4 block format + for (int y = 0; y < 300; y++) { + int inv_y = 299 - y; + int block_y = inv_y >> 2; + int local_y = inv_y & 3; + + int src_y_div_8 = y >> 3; + int src_y_mod_8 = y & 7; + uint8_t src_bit_mask = 1 << src_y_mod_8; + + for (int x = 0; x < 400; x++) { + int byte_x = x >> 1; + int local_x = x & 1; + + int dest_byte_idx = byte_x * 75 + block_y; + int dest_bit_pos = 7 - ((local_y << 1) | local_x); + + int src_byte_idx = 400 * src_y_div_8 + x; + + // Read standard vertical page byte bit + bool is_pixel_set = (src[src_byte_idx] & src_bit_mask) != 0; + + // Invert the pixel logic if required, standard: + // esp_lvgl_port monochrome transform clears the bit (0) for light/chroma colors + // and sets the bit (1) for dark/black. + // In ST7305 display RAM, White/Light is 1, Black/Dark is 0. + // So we write: 1 (White) if is_pixel_set is false (light), and 0 (Black) if is_pixel_set is true (dark). + if (!is_pixel_set) { + temp_buffer[dest_byte_idx] |= (1 << dest_bit_pos); + } else { + temp_buffer[dest_byte_idx] &= ~(1 << dest_bit_pos); + } + } + } + + uint8_t caset[] = {0x12, 0x2A}; + uint8_t raset[] = {0x00, 0xC7}; + + esp_lcd_panel_io_tx_param(io, ST7305_CMD_CASET, caset, sizeof(caset)); + esp_lcd_panel_io_tx_param(io, ST7305_CMD_RASET, raset, sizeof(raset)); + esp_lcd_panel_io_tx_color(io, ST7305_CMD_RAMWR, temp_buffer, lcd_buf_size); + + free(temp_buffer); + return ESP_OK; +} + +static esp_err_t panel_st7305_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base); + return esp_lcd_panel_io_tx_param(st7305->io, invert_color_data ? ST7305_CMD_INVON : ST7305_CMD_INVOFF, NULL, 0); +} + +static esp_err_t panel_st7305_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base); + esp_lcd_panel_io_handle_t io = st7305->io; + if (mirror_x) { + st7305->madctl_val |= ST7305_MADCTL_MY; + } else { + st7305->madctl_val &= ~ST7305_MADCTL_MY; + } + if (mirror_y) { + st7305->madctl_val |= ST7305_MADCTL_MX; + } else { + st7305->madctl_val &= ~ST7305_MADCTL_MX; + } + return esp_lcd_panel_io_tx_param(io, ST7305_CMD_MADCTL, (uint8_t[]) { st7305->madctl_val }, 1); +} + +static esp_err_t panel_st7305_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) +{ + st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base); + esp_lcd_panel_io_handle_t io = st7305->io; + if (swap_axes) { + st7305->madctl_val |= ST7305_MADCTL_MV; + } else { + st7305->madctl_val &= ~ST7305_MADCTL_MV; + } + return esp_lcd_panel_io_tx_param(io, ST7305_CMD_MADCTL, (uint8_t[]) { st7305->madctl_val }, 1); +} + +static esp_err_t panel_st7305_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) +{ + st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base); + st7305->x_gap = x_gap; + st7305->y_gap = y_gap; + return ESP_OK; +} + +static esp_err_t panel_st7305_disp_on_off(esp_lcd_panel_t *panel, bool off) +{ + st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base); + return esp_lcd_panel_io_tx_param(st7305->io, off ? ST7305_CMD_DISPOFF : ST7305_CMD_DISPON, NULL, 0); +} diff --git a/Drivers/ST7305/Source/esp_lcd_st7305.h b/Drivers/ST7305/Source/esp_lcd_st7305.h new file mode 100644 index 00000000..857c2a89 --- /dev/null +++ b/Drivers/ST7305/Source/esp_lcd_st7305.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include "esp_lcd_types.h" +#include "esp_lcd_panel_vendor.h" +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + int cmd; /*