Compare commits

..

1 Commits

Author SHA1 Message Date
Adolfo Reyna 9a540c644e feat(rlcd): Waveshare ESP32-S3-RLCD-4.2 support on latest main
- ST7305 driver with persistent 15KB DMA buffer (fix use-after-free +
  WDT lockup that caused freeze after flash)
- Reset timing fix: 150ms after reset, 50ms after init
- DTS: i2s0 mclk 16, bclk 9, ws 45, out 8, in 10 - fixes GPIO5 conflict
- i2c0 uses esp32-i2c-master, SDMMC 1-bit
- device.properties: colorDepth 16, fontSize 20, DefaultDark
- Amp GPIO46 init in initBoot
- Buttons: two-button control GPIO18 (cycle) + GPIO0 (select)

Base: origin/main f9453d89
2026-07-21 11:20:32 -04:00
13 changed files with 768 additions and 0 deletions
@@ -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
)
@@ -0,0 +1,41 @@
#include "devices/Display.h"
#include <Tactility/hal/Configuration.h>
#include <ButtonControl.h>
#include <driver/gpio.h>
#include <tactility/log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
static constexpr auto* TAG = "WaveshareRLCD";
using namespace tt::hal;
static DeviceVector createDevices() {
return {
createDisplay(),
ButtonControl::createTwoButtonControl(GPIO_NUM_18, GPIO_NUM_0) // KEY=18 cycles, BOOT=0 selects
};
}
static bool initBoot() {
// Amp enable GPIO46 active HIGH - keep LOW during boot to avoid pop,
// then HIGH after delay so speaker works for MCP / Mp3Player
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);
gpio_set_level(GPIO_NUM_46, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(GPIO_NUM_46, 1);
LOG_I(TAG, "Speaker amp GPIO46 enabled");
return true;
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -0,0 +1,23 @@
#include "Display.h"
#include <St7305Display.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto configuration = std::make_unique<St7305Display::Configuration>(
SPI2_HOST,
GPIO_NUM_40, // CS
GPIO_NUM_5, // DC
400, // Width
300, // Height
nullptr, // Touch device
false, // swapXY
false, // mirrorX
false, // mirrorY
true, // invertColor - initial true to match existing inverted look, user can toggle via Settings->Display
0 // bufferSize (0 = default, which is full screen = 120,000 pixels)
);
configuration->resetPin = GPIO_NUM_41;
auto display = std::make_shared<St7305Display>(std::move(configuration));
return std::reinterpret_pointer_cast<tt::hal::display::DisplayDevice>(display);
}
@@ -0,0 +1,6 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <memory>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
@@ -0,0 +1,21 @@
#include <tactility/module.h>
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
};
}
@@ -0,0 +1,26 @@
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
lvgl.fontSize=20
lvgl.theme=DefaultDark
@@ -0,0 +1,3 @@
dependencies:
- Platforms/platform-esp32
dts: waveshare,esp32-s3-rlcd.dts
@@ -0,0 +1,78 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c_master.h>
#include <tactility/bindings/esp32_sdmmc.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/display_placeholder.h>
/ {
compatible = "root";
model = "Waveshare ESP32-S3-RLCD-4.2";
ble0 {
compatible = "espressif,esp32-ble";
status = "disabled";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;
};
// Audio: ES7210 mic + speaker via I2S0
// Pins verified from working MicroPython board_config.py for WAVESHARE_RLCD:
// mclk=16, bclk=9, ws=45, tx=8 (DAC out), rx=10 (ES7210 mic in)
// NOTE: Display DC is GPIO5, so I2S must NOT use GPIO5 to avoid bus corruption
i2s0 {
compatible = "espressif,esp32-i2s";
port = <I2S_NUM_0>;
pin-bclk = <&gpio0 9 GPIO_FLAG_NONE>;
pin-ws = <&gpio0 45 GPIO_FLAG_NONE>;
pin-data-out = <&gpio0 8 GPIO_FLAG_NONE>;
pin-data-in = <&gpio0 10 GPIO_FLAG_NONE>;
pin-mclk = <&gpio0 16 GPIO_FLAG_NONE>;
};
i2c0 {
compatible = "espressif,esp32-i2c-master";
port = <I2C_NUM_0>;
clock-frequency = <400000>;
pin-sda = <&gpio0 13 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 14 GPIO_FLAG_NONE>;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_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 38 GPIO_FLAG_NONE>;
pin-cmd = <&gpio0 21 GPIO_FLAG_NONE>;
pin-d0 = <&gpio0 39 GPIO_FLAG_NONE>;
pin-d3 = <&gpio0 17 GPIO_FLAG_NONE>;
slot = <SDMMC_HOST_SLOT_1>;
bus-width = <1>;
pullups;
};
uart0 {
compatible = "espressif,esp32-uart";
port = <UART_NUM_0>;
pin-tx = <&gpio0 43 GPIO_FLAG_NONE>;
pin-rx = <&gpio0 44 GPIO_FLAG_NONE>;
};
};
+5
View File
@@ -0,0 +1,5 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility driver EspLcdCompat
)
+108
View File
@@ -0,0 +1,108 @@
#include "St7305Display.h"
#include "esp_lcd_st7305.h"
#include <tactility/log.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lvgl_port.h>
static const char* TAG = "ST7305";
bool St7305Display::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
LOG_I(TAG, "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) {
LOG_E(TAG, "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) {
LOG_E(TAG, "Failed to create st7305 panel");
return false;
}
// ST7305 needs extra delay after reset before init — prevents freeze on fast boot
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to reset st7305 panel");
return false;
}
vTaskDelay(pdMS_TO_TICKS(150));
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to init st7305 panel");
return false;
}
vTaskDelay(pdMS_TO_TICKS(50));
if (configuration->invertColor) {
if (esp_lcd_panel_invert_color(panelHandle, true) != ESP_OK) {
LOG_W(TAG, "Failed to apply initial invertColor");
}
}
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 = true,
.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
}
};
}
+100
View File
@@ -0,0 +1,100 @@
#pragma once
#include <EspLcdDisplay.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <driver/gpio.h>
#include <driver/spi_common.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_types.h>
#include <functional>
#include <lvgl.h>
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<tt::hal::touch::TouchDevice> 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<tt::hal::touch::TouchDevice> touch;
std::function<void(uint8_t)> _Nullable backlightDutyFunction = nullptr;
};
private:
std::unique_ptr<Configuration> 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<Configuration> 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<tt::hal::touch::TouchDevice> _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<tt::hal::display::DisplayDevice> createDisplay();
+300
View File
@@ -0,0 +1,300 @@
#include "esp_lcd_st7305.h"
#include "soc/soc_caps.h"
#include "esp_check.h"
#include "esp_lcd_types.h"
#include <stdlib.h>
#include <sys/cdefs.h>
#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 <string.h>
#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;
uint8_t *draw_buffer;
} 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[]){0x41, 0x41, 0x41, 0x41}, 4, 0}, // VSHP Setting
{0xC2, (uint8_t[]){0x19, 0x19, 0x19, 0x19}, 4, 0}, // VSLP Setting
{0xC4, (uint8_t[]){0x41, 0x41, 0x41, 0x41}, 4, 0}, // VSHN Setting
{0xC5, (uint8_t[]){0x19, 0x13, 0x19, 0x19}, 4, 0}, // VSLN Setting
{0xD8, (uint8_t[]){0xA6, 0xE9}, 2, 0}, // OSC Setting
{0xB2, (uint8_t[]){0x05}, 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->draw_buffer = heap_caps_malloc(15000, MALLOC_CAP_DMA);
ESP_GOTO_ON_FALSE(st7305->draw_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for st7305 draw buffer");
memset(st7305->draw_buffer, 0, 15000);
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);
}
if (st7305->draw_buffer) {
free(st7305->draw_buffer);
}
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);
}
if (st7305->draw_buffer) {
free(st7305->draw_buffer);
}
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));
}
}
// Explicitly clear display RAM to white
if (st7305->draw_buffer) {
memset(st7305->draw_buffer, 0xFF, 15000); // 0xFF is White
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, st7305->draw_buffer, 15000);
vTaskDelay(pdMS_TO_TICKS(50));
}
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;
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) {
st7305->draw_buffer[dest_byte_idx] |= (1 << dest_bit_pos);
} else {
st7305->draw_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, st7305->draw_buffer, 15000);
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_MX;
} else {
st7305->madctl_val &= ~ST7305_MADCTL_MX;
}
if (mirror_y) {
st7305->madctl_val |= ST7305_MADCTL_MY;
} else {
st7305->madctl_val &= ~ST7305_MADCTL_MY;
}
return esp_lcd_panel_io_tx_param(io, ST7305_CMD_MADCTL, &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);
}
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include <stdint.h>
#include "esp_lcd_types.h"
#include "esp_lcd_panel_vendor.h"
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
int cmd; /*<! The specific LCD command */
const void *data; /*<! Buffer that holds the command specific data */
size_t data_bytes; /*<! Size of `data` in memory, in bytes */
unsigned int delay_ms; /*<! Delay in milliseconds after this command */
} st7305_lcd_init_cmd_t;
typedef struct {
const st7305_lcd_init_cmd_t *init_cmds;
uint16_t init_cmds_size;
} st7305_vendor_config_t;
#define ST7305_WIDTH 400
#define ST7305_HEIGHT 300
#define ST7305_CMD_NOP 0x00
#define ST7305_CMD_SWRESET 0x01
#define ST7305_CMD_SLPOUT 0x11
#define ST7305_CMD_NORON 0x13
#define ST7305_CMD_INVOFF 0x20
#define ST7305_CMD_INVON 0x21
#define ST7305_CMD_DISPOFF 0x28
#define ST7305_CMD_DISPON 0x29
#define ST7305_CMD_CASET 0x2A
#define ST7305_CMD_RASET 0x2B
#define ST7305_CMD_RAMWR 0x2C
#define ST7305_CMD_MADCTL 0x36
#define ST7305_MADCTL_MY 0x80
#define ST7305_MADCTL_MX 0x40
#define ST7305_MADCTL_MV 0x20
#define ST7305_MADCTL_ML 0x10
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);
#ifdef __cplusplus
}
#endif