Remove unused modules

This commit is contained in:
Ken Van Hoeylandt 2026-07-21 01:08:06 +02:00
parent 3ddb0d3776
commit 88aef27578
61 changed files with 13 additions and 2980 deletions

View File

@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility
)

View File

@ -1,6 +0,0 @@
# AXP2101
Power management with I2C interface: a single cell NVDC PMU with E-gauge.
- [Datasheet](http://file.whycan.com/files/members/6736/AXP2101_Datasheet_V1.0_en_3832.pdf)
- [M5Unified implementation](https://github.com/m5stack/M5Unified/blob/master/src/utility/AXP2101_Class.cpp)

View File

@ -1,58 +0,0 @@
#include "Axp2101.h"
bool Axp2101::getBatteryVoltage(float& vbatMillis) const {
return readRegister14(0x34, vbatMillis);
}
bool Axp2101::getChargeStatus(ChargeStatus& status) const {
uint8_t value;
if (readRegister8(0x01, value)) {
value = (value >> 5) & 0b11;
status = (value == 1) ? CHARGE_STATUS_CHARGING : ((value == 2) ? CHARGE_STATUS_DISCHARGING : CHARGE_STATUS_STANDBY);
return true;
} else {
return false;
}
}
bool Axp2101::isChargingEnabled(bool& enabled) const {
uint8_t value;
if (readRegister8(0x18, value)) {
enabled = value & 0b10;
return true;
} else {
return false;
}
}
bool Axp2101::setChargingEnabled(bool enabled) const {
uint8_t value;
if (readRegister8(0x18, value)) {
return writeRegister8(0x18, (value & 0xFD) | (enabled << 1));
} else {
return false;
}
}
bool Axp2101::isVBus() const {
uint8_t value;
return readRegister8(0x00, value) && (value & 0x20);
}
bool Axp2101::getVBusVoltage(float& out) const {
if (!isVBus()) {
return false;
} else {
float vbus;
if (readRegister14(0x38, vbus) && vbus < 16375) {
out = vbus / 1000.0f;
return true;
} else {
return false;
}
}
}
bool Axp2101::setRegisters(uint8_t* bytePairs, size_t bytePairsSize) const {
return i2c_controller_write_register_array(controller, address, bytePairs, bytePairsSize, DEFAULT_TIMEOUT) == ERROR_NONE;
}

View File

@ -1,36 +0,0 @@
#pragma once
#include <Tactility/hal/i2c/I2cDevice.h>
#define AXP2101_ADDRESS 0x34
/**
* References:
* - https://github.com/m5stack/M5Unified/blob/master/src/utility/AXP2101_Class.cpp
* - http://file.whycan.com/files/members/6736/AXP2101_Datasheet_V1.0_en_3832.pdf
*/
class Axp2101 final : public tt::hal::i2c::I2cDevice {
public:
enum ChargeStatus {
CHARGE_STATUS_CHARGING = 0b01,
CHARGE_STATUS_DISCHARGING = 0b10,
CHARGE_STATUS_STANDBY = 0b00
};
explicit Axp2101(::Device* controller) : I2cDevice(controller, AXP2101_ADDRESS) {}
std::string getName() const override { return "AXP2101"; }
std::string getDescription() const override { return "Power management with I2C interface."; }
bool setRegisters(uint8_t* bytePairs, size_t bytePairsSize) const;
bool getBatteryVoltage(float& vbatMillis) const;
bool getChargeStatus(ChargeStatus& status) const;
bool isChargingEnabled(bool& enabled) const;
bool setChargingEnabled(bool enabled) const;
bool isVBus() const;
bool getVBusVoltage(float& out) const;
};

View File

@ -1,71 +0,0 @@
#include "Axp2101Power.h"
bool Axp2101Power::supportsMetric(MetricType type) const {
switch (type) {
using enum MetricType;
case BatteryVoltage:
case IsCharging:
case ChargeLevel:
return true;
default:
return false;
}
return false; // Safety guard for when new enum values are introduced
}
bool Axp2101Power::getMetric(MetricType type, MetricData& data) {
switch (type) {
using enum MetricType;
case BatteryVoltage: {
float milliVolt;
if (axpDevice->getBatteryVoltage(milliVolt)) {
data.valueAsUint32 = (uint32_t)milliVolt;
return true;
} else {
return false;
}
}
case ChargeLevel: {
float vbatMillis;
if (axpDevice->getBatteryVoltage(vbatMillis)) {
float vbat = vbatMillis / 1000.f;
float max_voltage = 4.20f;
float min_voltage = 2.69f; // From M5Unified
if (vbat > 2.69f) {
float charge_factor = (vbat - min_voltage) / (max_voltage - min_voltage);
data.valueAsUint8 = (uint8_t)(charge_factor * 100.f);
} else {
data.valueAsUint8 = 0;
}
return true;
} else {
return false;
}
}
case IsCharging: {
Axp2101::ChargeStatus status;
if (axpDevice->getChargeStatus(status)) {
data.valueAsBool = (status == Axp2101::CHARGE_STATUS_CHARGING);
return true;
} else {
return false;
}
}
default:
return false;
}
}
bool Axp2101Power::isAllowedToCharge() const {
bool enabled;
if (axpDevice->isChargingEnabled(enabled)) {
return enabled;
} else {
return false;
}
}
void Axp2101Power::setAllowedToCharge(bool canCharge) {
axpDevice->setChargingEnabled(canCharge);
}

View File

@ -1,28 +0,0 @@
#pragma once
#include "Tactility/hal/power/PowerDevice.h"
#include <Axp2101.h>
#include <memory>
#include <utility>
using tt::hal::power::PowerDevice;
class Axp2101Power final : public PowerDevice {
std::shared_ptr<Axp2101> axpDevice;
public:
explicit Axp2101Power(std::shared_ptr<Axp2101> axp) : axpDevice(std::move(axp)) {}
~Axp2101Power() override = default;
std::string getName() const override { return "AXP2101 Power"; }
std::string getDescription() const override { return "Power management via AXP2101 over I2C"; }
bool supportsMetric(MetricType type) const override;
bool getMetric(MetricType type, MetricData& data) override;
bool supportsChargeControl() const override { return true; }
bool isAllowedToCharge() const override;
void setAllowedToCharge(bool canCharge) override;
};

View File

@ -1,7 +0,0 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port driver
)

View File

@ -1,3 +0,0 @@
# Button Control
A driver for navigating apps with 1 or more GPIO buttons.

View File

@ -1,222 +0,0 @@
#include "ButtonControl.h"
#include <Tactility/app/App.h>
#include <tactility/log.h>
#include <esp_lvgl_port.h>
constexpr auto* TAG = "ButtonControl";
ButtonControl::ButtonControl(const std::vector<PinConfiguration>& pinConfigurations)
: buttonQueue(20, sizeof(ButtonEvent)),
pinConfigurations(pinConfigurations) {
pinStates.resize(pinConfigurations.size());
// Build isrArgs with one entry per unique physical pin, then configure GPIO.
isrArgs.reserve(pinConfigurations.size());
for (size_t i = 0; i < pinConfigurations.size(); i++) {
const auto pin = static_cast<gpio_num_t>(pinConfigurations[i].pin);
// Skip if this physical pin was already seen.
bool seen = false;
for (const auto& arg : isrArgs) {
if (arg.pin == pin) { seen = true; break; }
}
if (seen) continue;
gpio_config_t io_conf = {
.pin_bit_mask = 1ULL << pin,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_ANYEDGE,
};
esp_err_t err = gpio_config(&io_conf);
if (err != ESP_OK) {
LOG_E(TAG, "Failed to configure GPIO %d: %s", static_cast<int>(pin), esp_err_to_name(err));
continue;
}
// isrArgs is reserved upfront; push_back will not reallocate, keeping addresses stable
// for gpio_isr_handler_add() called later in startThread().
isrArgs.push_back({ .self = this, .pin = pin });
}
}
ButtonControl::~ButtonControl() {
if (driverThread != nullptr && driverThread->getState() != tt::Thread::State::Stopped) {
stopThread();
}
}
void ButtonControl::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
// Defaults
data->enc_diff = 0;
data->state = LV_INDEV_STATE_RELEASED;
auto* self = static_cast<ButtonControl*>(lv_indev_get_driver_data(indev));
if (self->mutex.lock(100)) {
for (int i = 0; i < self->pinConfigurations.size(); i++) {
const auto& config = self->pinConfigurations[i];
std::vector<PinState>::reference state = self->pinStates[i];
const bool trigger = (config.event == Event::ShortPress && state.triggerShortPress) ||
(config.event == Event::LongPress && state.triggerLongPress);
state.triggerShortPress = false;
state.triggerLongPress = false;
if (trigger) {
switch (config.action) {
case Action::UiSelectNext:
data->enc_diff = 1;
break;
case Action::UiSelectPrevious:
data->enc_diff = -1;
break;
case Action::UiPressSelected:
data->state = LV_INDEV_STATE_PRESSED;
break;
case Action::AppClose:
tt::app::stop();
break;
}
}
}
self->mutex.unlock();
}
}
void ButtonControl::updatePin(std::vector<PinConfiguration>::const_reference configuration, std::vector<PinState>::reference state, bool pressed) {
auto now = tt::kernel::getMillis();
// Software debounce: ignore edges within 20ms of the last state change.
if ((now - state.lastChangeTime) < 20) {
return;
}
state.lastChangeTime = now;
if (pressed) {
state.pressStartTime = now;
state.pressState = true;
} else { // released
if (state.pressState) {
auto time_passed = now - state.pressStartTime;
if (time_passed < 500) {
LOG_I(TAG, "Short press (%dms)", (int)time_passed);
state.triggerShortPress = true;
} else {
LOG_I(TAG, "Long press (%dms)", (int)time_passed);
state.triggerLongPress = true;
}
state.pressState = false;
}
}
}
void IRAM_ATTR ButtonControl::gpioIsrHandler(void* arg) {
auto* isrArg = static_cast<IsrArg*>(arg);
ButtonEvent event {
.pin = isrArg->pin,
.pressed = gpio_get_level(isrArg->pin) == 0, // active-low: LOW = pressed
};
// tt::MessageQueue::put() is ISR-safe with timeout=0: it detects ISR context via
// xPortInIsrContext() and uses xQueueSendFromISR() + portYIELD_FROM_ISR() internally.
isrArg->self->buttonQueue.put(&event, 0);
}
void ButtonControl::driverThreadMain() {
ButtonEvent event;
while (buttonQueue.get(&event, portMAX_DELAY)) {
if (event.pin == GPIO_NUM_NC) {
break; // shutdown sentinel
}
LOG_I(TAG, "Pin %d %s", static_cast<int>(event.pin), event.pressed ? "down" : "up");
if (mutex.lock(portMAX_DELAY)) {
// Update ALL PinConfiguration entries that share this physical pin.
for (size_t i = 0; i < pinConfigurations.size(); i++) {
if (static_cast<gpio_num_t>(pinConfigurations[i].pin) == event.pin) {
updatePin(pinConfigurations[i], pinStates[i], event.pressed);
}
}
mutex.unlock();
}
}
}
bool ButtonControl::startThread() {
LOG_I(TAG, "Start");
esp_err_t err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
LOG_E(TAG, "Failed to install GPIO ISR service: %s", esp_err_to_name(err));
return false;
}
// isrArgs has one entry per unique physical pin — no duplicate registrations.
// Addresses are stable: vector was reserved in constructor and is not modified after that.
int handlersAdded = 0;
for (auto& arg : isrArgs) {
err = gpio_isr_handler_add(arg.pin, gpioIsrHandler, &arg);
if (err != ESP_OK) {
LOG_E(TAG, "Failed to add ISR for GPIO %d: %s", static_cast<int>(arg.pin), esp_err_to_name(err));
for (int i = 0; i < handlersAdded; i++) {
gpio_isr_handler_remove(isrArgs[i].pin);
}
return false;
}
handlersAdded++;
}
driverThread = std::make_shared<tt::Thread>("ButtonControl", 4096, [this] {
driverThreadMain();
return 0;
});
driverThread->start();
return true;
}
void ButtonControl::stopThread() {
LOG_I(TAG, "Stop");
for (const auto& arg : isrArgs) {
gpio_isr_handler_remove(arg.pin);
}
ButtonEvent sentinel { .pin = GPIO_NUM_NC, .pressed = false };
buttonQueue.put(&sentinel, portMAX_DELAY);
driverThread->join();
driverThread = nullptr;
}
bool ButtonControl::startLvgl(lv_display_t* display) {
if (deviceHandle != nullptr) {
return false;
}
if (!startThread()) {
return false;
}
deviceHandle = lv_indev_create();
lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_ENCODER);
lv_indev_set_driver_data(deviceHandle, this);
lv_indev_set_read_cb(deviceHandle, readCallback);
return true;
}
bool ButtonControl::stopLvgl() {
if (deviceHandle == nullptr) {
return false;
}
lv_indev_delete(deviceHandle);
deviceHandle = nullptr;
stopThread();
return true;
}

View File

@ -1,127 +0,0 @@
#pragma once
#include <Tactility/hal/encoder/EncoderDevice.h>
#include <Tactility/hal/gpio/Gpio.h>
#include <Tactility/MessageQueue.h>
#include <Tactility/Thread.h>
#include <driver/gpio.h>
class ButtonControl final : public tt::hal::encoder::EncoderDevice {
public:
enum class Event {
ShortPress,
LongPress
};
enum class Action {
UiSelectNext,
UiSelectPrevious,
UiPressSelected,
AppClose,
};
struct PinConfiguration {
tt::hal::gpio::Pin pin;
Event event;
Action action;
};
private:
struct PinState {
long pressStartTime = 0;
long lastChangeTime = 0;
bool pressState = false;
bool triggerShortPress = false;
bool triggerLongPress = false;
};
/** Queued from ISR to worker thread. pin == GPIO_NUM_NC is a shutdown sentinel. */
struct ButtonEvent {
gpio_num_t pin;
bool pressed;
};
/** One entry per unique physical pin; addresses must remain stable after construction. */
struct IsrArg {
ButtonControl* self;
gpio_num_t pin;
};
lv_indev_t* deviceHandle = nullptr;
std::shared_ptr<tt::Thread> driverThread;
tt::Mutex mutex;
tt::MessageQueue buttonQueue;
std::vector<PinConfiguration> pinConfigurations;
std::vector<PinState> pinStates;
std::vector<IsrArg> isrArgs; // one entry per unique physical pin
static void updatePin(std::vector<PinConfiguration>::const_reference config, std::vector<PinState>::reference state, bool pressed);
void driverThreadMain();
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
static void IRAM_ATTR gpioIsrHandler(void* arg);
bool startThread();
void stopThread();
public:
explicit ButtonControl(const std::vector<PinConfiguration>& pinConfigurations);
~ButtonControl() override;
std::string getName() const override { return "ButtonControl"; }
std::string getDescription() const override { return "ButtonControl input driver"; }
bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override;
/** Could return nullptr if not started */
lv_indev_t* getLvglIndev() override { return deviceHandle; }
static std::shared_ptr<ButtonControl> createOneButtonControl(tt::hal::gpio::Pin pin) {
return std::make_shared<ButtonControl>(std::vector {
PinConfiguration {
.pin = pin,
.event = Event::ShortPress,
.action = Action::UiSelectNext
},
PinConfiguration {
.pin = pin,
.event = Event::LongPress,
.action = Action::UiPressSelected
}
});
}
static std::shared_ptr<ButtonControl> createTwoButtonControl(tt::hal::gpio::Pin primaryPin, tt::hal::gpio::Pin secondaryPin) {
return std::make_shared<ButtonControl>(std::vector {
PinConfiguration {
.pin = primaryPin,
.event = Event::ShortPress,
.action = Action::UiPressSelected
},
PinConfiguration {
.pin = primaryPin,
.event = Event::LongPress,
.action = Action::AppClose
},
PinConfiguration {
.pin = secondaryPin,
.event = Event::ShortPress,
.action = Action::UiSelectNext
},
PinConfiguration {
.pin = secondaryPin,
.event = Event::LongPress,
.action = Action::UiSelectPrevious
}
});
}
};

View File

@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility EspLcdCompat esp_lcd_touch_cst816s driver
)

View File

@ -1,3 +0,0 @@
# CST816S
I2C touch driver for Tactility

View File

@ -1,70 +0,0 @@
#pragma once
#include <EspLcdTouch.h>
class Cst816sTouch final : public EspLcdTouch {
public:
class Configuration {
public:
Configuration(
i2c_port_t port,
uint16_t xMax,
uint16_t yMax,
bool swapXy = false,
bool mirrorX = false,
bool mirrorY = false,
gpio_num_t pinReset = GPIO_NUM_NC,
gpio_num_t pinInterrupt = GPIO_NUM_NC,
unsigned int pinResetLevel = 0,
unsigned int pinInterruptLevel = 0
) : port(port),
xMax(xMax),
yMax(yMax),
swapXY(swapXy),
mirrorX(mirrorX),
mirrorY(mirrorY),
pinReset(pinReset),
pinInterrupt(pinInterrupt),
pinResetLevel(pinResetLevel),
pinInterruptLevel(pinInterruptLevel)
{}
i2c_port_t port;
uint16_t xMax;
uint16_t yMax;
bool swapXY;
bool mirrorX;
bool mirrorY;
gpio_num_t pinReset;
gpio_num_t pinInterrupt;
unsigned int pinResetLevel;
unsigned int pinInterruptLevel;
};
private:
std::unique_ptr<Configuration> configuration;
esp_lcd_panel_io_handle_t ioHandle = nullptr;
esp_lcd_touch_handle_t touchHandle = nullptr;
lv_indev_t* deviceHandle = nullptr;
void cleanup();
bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) override;
bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& touchHandle) override;
esp_lcd_touch_config_t createEspLcdTouchConfig() override;
public:
explicit Cst816sTouch(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
assert(configuration != nullptr);
}
std::string getName() const override { return "CST816S"; }
std::string getDescription() const override { return "CST816S I2C touch driver"; }
};

View File

@ -1,34 +0,0 @@
#include "Cst816Touch.h"
#include <esp_lcd_touch_cst816s.h>
bool Cst816sTouch::createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) {
constexpr esp_lcd_panel_io_i2c_config_t touch_io_config = ESP_LCD_TOUCH_IO_I2C_CST816S_CONFIG();
return esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)configuration->port, &touch_io_config, &ioHandle) == ESP_OK;
}
bool Cst816sTouch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& touchConfiguration, esp_lcd_touch_handle_t& touchHandle) {
return esp_lcd_touch_new_i2c_cst816s(ioHandle, &touchConfiguration, &touchHandle) == ESP_OK;
}
esp_lcd_touch_config_t Cst816sTouch::createEspLcdTouchConfig() {
return {
.x_max = configuration->xMax,
.y_max = configuration->yMax,
.rst_gpio_num = configuration->pinReset,
.int_gpio_num = configuration->pinInterrupt,
.levels = {
.reset = configuration->pinResetLevel,
.interrupt = configuration->pinInterruptLevel,
},
.flags = {
.swap_xy = configuration->swapXY,
.mirror_x = configuration->mirrorX,
.mirror_y = configuration->mirrorY,
},
.process_coordinates = nullptr,
.interrupt_callback = nullptr,
.user_data = nullptr,
.driver_data = nullptr
};
}

View File

@ -1,6 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility epdiy esp_lvgl_port
PRIV_REQUIRES esp_timer
)

View File

@ -1,3 +0,0 @@
# EPDiy Display Driver
A display driver for e-paper/e-ink displays using the EPDiy library. This driver provides LVGL integration and high-level display management for EPD panels.

View File

@ -1,472 +0,0 @@
#include "EpdiyDisplay.h"
#include <tactility/check.h>
#include <tactility/log.h>
#include <esp_heap_caps.h>
#include <esp_timer.h>
constexpr const char* TAG = "EpdiyDisplay";
bool EpdiyDisplay::s_hlInitialized = false;
EpdiyHighlevelState EpdiyDisplay::s_hlState = {};
EpdiyDisplay::EpdiyDisplay(std::unique_ptr<Configuration> inConfiguration)
: configuration(std::move(inConfiguration)) {
check(configuration != nullptr);
check(configuration->board != nullptr);
check(configuration->display != nullptr);
}
EpdiyDisplay::~EpdiyDisplay() {
if (lvglDisplay != nullptr) {
stopLvgl();
}
if (initialized) {
stop();
}
}
bool EpdiyDisplay::start() {
if (initialized) {
LOG_W(TAG, "Already initialized");
return true;
}
// Initialize EPDiy low-level hardware
epd_init(
configuration->board,
configuration->display,
configuration->initOptions
);
// Set rotation BEFORE initializing highlevel state
epd_set_rotation(configuration->rotation);
LOG_I(TAG, "Display rotation set to %d", configuration->rotation);
// Initialize the high-level API only once — epd_hl_init() sets a static flag internally
// and there is no matching epd_hl_deinit(). Reuse the existing state on subsequent starts.
if (!s_hlInitialized) {
s_hlState = epd_hl_init(configuration->waveform);
if (s_hlState.front_fb == nullptr) {
LOG_E(TAG, "Failed to initialize EPDiy highlevel state");
epd_deinit();
return false;
}
s_hlInitialized = true;
LOG_I(TAG, "EPDiy highlevel state initialized");
} else {
LOG_I(TAG, "Reusing existing EPDiy highlevel state");
}
highlevelState = s_hlState;
framebuffer = epd_hl_get_framebuffer(&highlevelState);
initialized = true;
LOG_I(TAG, "EPDiy initialized successfully (%dx%d native, %dx%d rotated)", epd_width(), epd_height(), epd_rotated_display_width(), epd_rotated_display_height());
// Perform initial clear to ensure clean state
LOG_I(TAG, "Performing initial screen clear...");
clearScreen();
LOG_I(TAG, "Screen cleared");
return true;
}
bool EpdiyDisplay::stop() {
if (!initialized) {
return true;
}
if (lvglDisplay != nullptr) {
stopLvgl();
}
// Power off the display
if (powered) {
setPowerOn(false);
}
// Deinitialize EPDiy low-level hardware.
// The HL framebuffers (s_hlState) are intentionally kept alive: epd_hl_init() has no
// matching deinit and sets an internal already_initialized flag, so the HL state must
// persist across stop()/start() cycles and be reused on the next start().
epd_deinit();
// Clear instance references to HL state (the static s_hlState still owns the memory)
highlevelState = {};
framebuffer = nullptr;
initialized = false;
LOG_I(TAG, "EPDiy deinitialized (HL state preserved for restart)");
return true;
}
void EpdiyDisplay::setPowerOn(bool turnOn) {
if (!initialized) {
LOG_W(TAG, "Cannot change power state - EPD not initialized");
return;
}
if (powered == turnOn) {
return;
}
if (turnOn) {
epd_poweron();
powered = true;
LOG_D(TAG, "EPD power on");
} else {
epd_poweroff();
powered = false;
LOG_D(TAG, "EPD power off");
}
}
// LVGL functions
bool EpdiyDisplay::startLvgl() {
if (lvglDisplay != nullptr) {
LOG_W(TAG, "LVGL already initialized");
return true;
}
if (!initialized) {
LOG_E(TAG, "EPD not initialized, call start() first");
return false;
}
// Get the native display dimensions
uint16_t width = epd_width();
uint16_t height = epd_height();
LOG_I(TAG, "Creating LVGL display: %dx%d (EPDiy rotation: %d)", width, height, configuration->rotation);
// Create LVGL display with native dimensions
lvglDisplay = lv_display_create(width, height);
if (lvglDisplay == nullptr) {
LOG_E(TAG, "Failed to create LVGL display");
return false;
}
// EPD uses 4-bit grayscale (16 levels)
// Map to LVGL's L8 format (8-bit grayscale)
lv_display_set_color_format(lvglDisplay, LV_COLOR_FORMAT_L8);
auto lv_rotation = epdRotationToLvgl(configuration->rotation);
lv_display_set_rotation(lvglDisplay, lv_rotation);
// Allocate LVGL draw buffer (L8 format: 1 byte per pixel)
size_t draw_buffer_size = static_cast<size_t>(width) * height;
lvglDrawBuffer = static_cast<uint8_t*>(heap_caps_malloc(draw_buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
if (lvglDrawBuffer == nullptr) {
LOG_W(TAG, "PSRAM allocation failed for draw buffer, falling back to internal memory");
lvglDrawBuffer = static_cast<uint8_t*>(heap_caps_malloc(draw_buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL));
}
if (lvglDrawBuffer == nullptr) {
LOG_E(TAG, "Failed to allocate draw buffer");
lv_display_delete(lvglDisplay);
lvglDisplay = nullptr;
return false;
}
// Pre-allocate 4-bit packed pixel buffer used in flushInternal (avoids per-flush heap allocation)
// Row stride with odd-width padding: (width + 1) / 2 bytes per row
size_t packed_buffer_size = static_cast<size_t>((width + 1) / 2) * static_cast<size_t>(height);
packedBuffer = static_cast<uint8_t*>(heap_caps_malloc(packed_buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
if (packedBuffer == nullptr) {
LOG_W(TAG, "PSRAM allocation failed for packed buffer, falling back to internal memory");
packedBuffer = static_cast<uint8_t*>(heap_caps_malloc(packed_buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL));
}
if (packedBuffer == nullptr) {
LOG_E(TAG, "Failed to allocate packed pixel buffer");
heap_caps_free(lvglDrawBuffer);
lvglDrawBuffer = nullptr;
lv_display_delete(lvglDisplay);
lvglDisplay = nullptr;
return false;
}
// For EPD, we want full refresh mode based on configuration
lv_display_render_mode_t render_mode = configuration->fullRefresh
? LV_DISPLAY_RENDER_MODE_FULL
: LV_DISPLAY_RENDER_MODE_PARTIAL;
lv_display_set_buffers(lvglDisplay, lvglDrawBuffer, NULL, draw_buffer_size, render_mode);
// Set flush callback
lv_display_set_flush_cb(lvglDisplay, flushCallback);
lv_display_set_user_data(lvglDisplay, this);
// Register rotation change event callback
lv_display_add_event_cb(lvglDisplay, rotationEventCallback, LV_EVENT_RESOLUTION_CHANGED, this);
LOG_D(TAG, "Registered rotation change event callback");
// Start touch device if present
auto touch_device = getTouchDevice();
if (touch_device != nullptr && touch_device->supportsLvgl()) {
LOG_D(TAG, "Starting touch device for LVGL");
if (!touch_device->startLvgl(lvglDisplay)) {
LOG_W(TAG, "Failed to start touch device for LVGL");
}
}
LOG_I(TAG, "LVGL display initialized");
return true;
}
bool EpdiyDisplay::stopLvgl() {
if (lvglDisplay == nullptr) {
return true;
}
LOG_I(TAG, "Stopping LVGL display");
// Stop touch device
auto touch_device = getTouchDevice();
if (touch_device != nullptr) {
touch_device->stopLvgl();
}
if (lvglDrawBuffer != nullptr) {
heap_caps_free(lvglDrawBuffer);
lvglDrawBuffer = nullptr;
}
if (packedBuffer != nullptr) {
heap_caps_free(packedBuffer);
packedBuffer = nullptr;
}
// Delete the LVGL display object
lv_display_delete(lvglDisplay);
lvglDisplay = nullptr;
LOG_I(TAG, "LVGL display stopped");
return true;
}
void EpdiyDisplay::flushCallback(lv_display_t* display, const lv_area_t* area, uint8_t* pixelMap) {
auto* instance = static_cast<EpdiyDisplay*>(lv_display_get_user_data(display));
if (instance != nullptr) {
uint64_t t0 = esp_timer_get_time();
const bool isLast = lv_display_flush_is_last(display);
instance->flushInternal(area, pixelMap, isLast);
LOG_D(TAG, "flush took %llu us", (unsigned long long)(esp_timer_get_time() - t0));
} else {
LOG_W(TAG, "flush callback called with null instance");
}
lv_display_flush_ready(display);
}
// EPD functions
void EpdiyDisplay::clearScreen() {
if (!initialized) {
LOG_E(TAG, "EPD not initialized");
return;
}
if (!powered) {
setPowerOn(true);
}
epd_clear();
// Also clear the framebuffer
epd_hl_set_all_white(&highlevelState);
}
void EpdiyDisplay::clearArea(EpdRect area) {
if (!initialized) {
LOG_E(TAG, "EPD not initialized");
return;
}
if (!powered) {
setPowerOn(true);
}
epd_clear_area(area);
}
enum EpdDrawError EpdiyDisplay::updateScreen(enum EpdDrawMode mode, int temperature) {
if (!initialized) {
LOG_E(TAG, "EPD not initialized");
return EPD_DRAW_FAILED_ALLOC;
}
if (!powered) {
setPowerOn(true);
}
// Use defaults if not specified
if (mode == MODE_UNKNOWN_WAVEFORM) {
mode = configuration->defaultDrawMode;
}
if (temperature == -1) {
temperature = configuration->defaultTemperature;
}
return epd_hl_update_screen(&highlevelState, mode, temperature);
}
enum EpdDrawError EpdiyDisplay::updateArea(EpdRect area, enum EpdDrawMode mode, int temperature) {
if (!initialized) {
LOG_E(TAG, "EPD not initialized");
return EPD_DRAW_FAILED_ALLOC;
}
if (!powered) {
setPowerOn(true);
}
// Use defaults if not specified
if (mode == MODE_UNKNOWN_WAVEFORM) {
mode = configuration->defaultDrawMode;
}
if (temperature == -1) {
temperature = configuration->defaultTemperature;
}
return epd_hl_update_area(&highlevelState, mode, temperature, area);
}
void EpdiyDisplay::setAllWhite() {
if (!initialized) {
LOG_E(TAG, "EPD not initialized");
return;
}
epd_hl_set_all_white(&highlevelState);
}
// Internal functions
void EpdiyDisplay::flushInternal(const lv_area_t* area, uint8_t* pixelMap, bool isLast) {
if (!initialized) {
LOG_E(TAG, "Cannot flush - EPD not initialized");
return;
}
if (!powered) {
setPowerOn(true);
}
const int x = area->x1;
const int y = area->y1;
const int width = lv_area_get_width(area);
const int height = lv_area_get_height(area);
LOG_D(TAG, "Flushing area: x=%d, y=%d, w=%d, h=%d isLast=%d", x, y, width, height, (int)isLast);
// Convert L8 (8-bit grayscale, 0=black/255=white) to EPDiy 4-bit (0=black/15=white).
// Pack 2 pixels per byte: lower nibble = even column, upper nibble = odd column.
// Row stride includes one padding nibble for odd widths to keep rows aligned.
// Threshold at 128 (matching FastEPD BB_MODE_1BPP): pixels > 127 → full white (15),
// pixels ≤ 127 → full black (0). Maximum contrast for the Mono theme and correct for
// MODE_DU which only drives two levels. For greyscale content / MODE_GL16, replace
// the threshold with `src[col] >> 4` to preserve intermediate grey levels.
const int row_stride = (width + 1) / 2;
for (int row = 0; row < height; ++row) {
const uint8_t* src = pixelMap + static_cast<size_t>(row) * width;
uint8_t* dst = packedBuffer + static_cast<size_t>(row) * row_stride;
for (int col = 0; col < width; col += 2) {
const uint8_t p0 = (src[col] > 127) ? 15u : 0u;
const uint8_t p1 = (col + 1 < width) ? ((src[col + 1] > 127) ? 15u : 0u) : 0u;
dst[col / 2] = static_cast<uint8_t>((p1 << 4) | p0);
}
}
const EpdRect update_area = {
.x = x,
.y = y,
.width = static_cast<uint16_t>(width),
.height = static_cast<uint16_t>(height)
};
// Write pixels into EPDiy's framebuffer (no hardware I/O, just memory)
epd_draw_rotated_image(update_area, packedBuffer, framebuffer);
// Only trigger EPD hardware update on the last flush of this render cycle.
// EPDiy's epd_prep tasks run at configMAX_PRIORITIES-1 with busy-wait loops; calling
// epd_hl_update_area on every partial flush starves IDLE and triggers the task watchdog
// during scroll animations. Batching to one hardware update per LVGL render cycle fixes this.
if (isLast) {
epd_hl_update_screen(
&highlevelState,
static_cast<EpdDrawMode>(configuration->defaultDrawMode | MODE_PACKING_2PPB),
configuration->defaultTemperature
);
}
}
lv_display_rotation_t EpdiyDisplay::epdRotationToLvgl(enum EpdRotation epdRotation) {
// Static lookup table for EPD -> LVGL rotation mapping
// EPDiy: LANDSCAPE = 0°, PORTRAIT = 90° CW, INVERTED_LANDSCAPE = 180°, INVERTED_PORTRAIT = 270° CW
// LVGL: 0 = 0°, 90 = 90° CW, 180 = 180°, 270 = 270° CW
static const lv_display_rotation_t rotationMap[] = {
LV_DISPLAY_ROTATION_0, // EPD_ROT_LANDSCAPE (0)
LV_DISPLAY_ROTATION_270, // EPD_ROT_PORTRAIT (1) - 90° CW in EPD is 270° in LVGL
LV_DISPLAY_ROTATION_180, // EPD_ROT_INVERTED_LANDSCAPE (2)
LV_DISPLAY_ROTATION_90 // EPD_ROT_INVERTED_PORTRAIT (3) - 270° CW in EPD is 90° in LVGL
};
// Validate input and return mapped value
if (epdRotation >= 0 && epdRotation < 4) {
return rotationMap[epdRotation];
}
// Default to landscape if invalid
return LV_DISPLAY_ROTATION_0;
}
enum EpdRotation EpdiyDisplay::lvglRotationToEpd(lv_display_rotation_t lvglRotation) {
// Static lookup table for LVGL -> EPD rotation mapping
static const enum EpdRotation rotationMap[] = {
EPD_ROT_LANDSCAPE, // LV_DISPLAY_ROTATION_0 (0)
EPD_ROT_INVERTED_PORTRAIT, // LV_DISPLAY_ROTATION_90 (1)
EPD_ROT_INVERTED_LANDSCAPE, // LV_DISPLAY_ROTATION_180 (2)
EPD_ROT_PORTRAIT // LV_DISPLAY_ROTATION_270 (3)
};
// Validate input and return mapped value
if (lvglRotation >= LV_DISPLAY_ROTATION_0 && lvglRotation <= LV_DISPLAY_ROTATION_270) {
return rotationMap[lvglRotation];
}
// Default to landscape if invalid
return EPD_ROT_LANDSCAPE;
}
void EpdiyDisplay::rotationEventCallback(lv_event_t* event) {
auto* display = static_cast<EpdiyDisplay*>(lv_event_get_user_data(event));
if (display == nullptr) {
return;
}
lv_display_t* lvgl_display = static_cast<lv_display_t*>(lv_event_get_target(event));
if (lvgl_display == nullptr) {
return;
}
lv_display_rotation_t rotation = lv_display_get_rotation(lvgl_display);
display->handleRotationChange(rotation);
}
void EpdiyDisplay::handleRotationChange(lv_display_rotation_t lvgl_rotation) {
// Map LVGL rotation to EPDiy rotation using lookup table
enum EpdRotation epd_rotation = lvglRotationToEpd(lvgl_rotation);
// Update EPDiy rotation
LOG_I(TAG, "LVGL rotation changed to %d, setting EPDiy rotation to %d", lvgl_rotation, epd_rotation);
epd_set_rotation(epd_rotation);
// Update configuration to keep it in sync
configuration->rotation = epd_rotation;
// Log the new dimensions
LOG_I(TAG, "Display dimensions after rotation: %dx%d", epd_rotated_display_width(), epd_rotated_display_height());
}

View File

@ -1,163 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/touch/TouchDevice.h>
#include <epd_highlevel.h>
#include <epdiy.h>
#include <lvgl.h>
#include <memory>
#include <cassert>
#include <cstdlib>
class EpdiyDisplay final : public tt::hal::display::DisplayDevice {
public:
class Configuration {
public:
Configuration(
const EpdBoardDefinition* board,
const EpdDisplay_t* display,
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr,
enum EpdInitOptions initOptions = EPD_OPTIONS_DEFAULT,
const EpdWaveform* waveform = EPD_BUILTIN_WAVEFORM,
int defaultTemperature = 25,
enum EpdDrawMode defaultDrawMode = MODE_GL16,
bool fullRefresh = false,
enum EpdRotation rotation = EPD_ROT_LANDSCAPE
) : board(board),
display(display),
touch(std::move(touch)),
initOptions(initOptions),
waveform(waveform),
defaultTemperature(defaultTemperature),
defaultDrawMode(defaultDrawMode),
fullRefresh(fullRefresh),
rotation(rotation) {
check(board != nullptr);
check(display != nullptr);
}
const EpdBoardDefinition* board;
const EpdDisplay_t* display;
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
enum EpdInitOptions initOptions;
const EpdWaveform* waveform;
int defaultTemperature;
enum EpdDrawMode defaultDrawMode;
bool fullRefresh;
enum EpdRotation rotation;
};
private:
std::unique_ptr<Configuration> configuration;
lv_display_t* _Nullable lvglDisplay = nullptr;
EpdiyHighlevelState highlevelState = {};
uint8_t* framebuffer = nullptr;
uint8_t* lvglDrawBuffer = nullptr;
uint8_t* packedBuffer = nullptr; // Pre-allocated 4-bit packed pixel buffer for flushInternal
bool initialized = false;
bool powered = false;
// epd_hl_init() sets an internal already_initialized flag and has no matching deinit.
// We track first-time init statically and keep the HL state alive across stop()/start() cycles.
static bool s_hlInitialized;
static EpdiyHighlevelState s_hlState;
static void flushCallback(lv_display_t* display, const lv_area_t* area, uint8_t* pixelMap);
void flushInternal(const lv_area_t* area, uint8_t* pixelMap, bool isLast);
static void rotationEventCallback(lv_event_t* event);
void handleRotationChange(lv_display_rotation_t rotation);
// Rotation mapping helpers
static lv_display_rotation_t epdRotationToLvgl(enum EpdRotation epdRotation);
static enum EpdRotation lvglRotationToEpd(lv_display_rotation_t lvglRotation);
public:
explicit EpdiyDisplay(std::unique_ptr<Configuration> inConfiguration);
~EpdiyDisplay() override;
std::string getName() const override { return "EPDiy"; }
std::string getDescription() const override {
return "E-Ink display powered by EPDiy library";
}
// Device lifecycle
bool start() override;
bool stop() override;
// Power control
void setPowerOn(bool turnOn) override;
bool isPoweredOn() const override { return powered; }
bool supportsPowerControl() const override { return true; }
// Touch device
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable getTouchDevice() override {
return configuration->touch;
}
// LVGL support
bool supportsLvgl() const override { return true; }
bool startLvgl() override;
bool stopLvgl() override;
lv_display_t* _Nullable getLvglDisplay() const override { return lvglDisplay; }
// DisplayDriver (not supported for EPD)
bool supportsDisplayDriver() const override { return false; }
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable getDisplayDriver() override {
return nullptr;
}
// EPD specific functions
/**
* Get a reference to the framebuffer
*/
uint8_t* getFramebuffer() {
return epd_hl_get_framebuffer(&highlevelState);
}
/**
* Clear the screen by flashing it
*/
void clearScreen();
/**
* Clear an area by flashing it
*/
void clearArea(EpdRect area);
/**
* Manually trigger a screen update
* @param mode The draw mode to use (defaults to configuration default)
* @param temperature Temperature in °C (defaults to configuration default)
*/
enum EpdDrawError updateScreen(
enum EpdDrawMode mode = MODE_UNKNOWN_WAVEFORM,
int temperature = -1
);
/**
* Update a specific area of the screen
* @param area The area to update
* @param mode The draw mode to use (defaults to configuration default)
* @param temperature Temperature in °C (defaults to configuration default)
*/
enum EpdDrawError updateArea(
EpdRect area,
enum EpdDrawMode mode = MODE_UNKNOWN_WAVEFORM,
int temperature = -1
);
/**
* Set the display to all white
*/
void setAllWhite();
};

View File

@ -1,43 +0,0 @@
#pragma once
#include "EpdiyDisplay.h"
#include <epd_board.h>
#include <epd_display.h>
#include <memory>
/**
* Helper class to create EPDiy displays with common configurations
*/
class EpdiyDisplayHelper {
public:
/**
* Create a display for M5Paper S3
* @param touch Optional touch device
* @param temperature Display temperature in °C (default: 20)
* @param drawMode Default draw mode (default: MODE_DU)
* @param fullRefresh Use full refresh mode (default: false for partial updates)
* @param rotation Display rotation (default: EPD_ROT_PORTRAIT)
*/
static std::shared_ptr<EpdiyDisplay> createM5PaperS3Display(
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr,
int temperature = 20,
enum EpdDrawMode drawMode = MODE_DU,
bool fullRefresh = false,
enum EpdRotation rotation = EPD_ROT_PORTRAIT
) {
auto config = std::make_unique<EpdiyDisplay::Configuration>(
&epd_board_m5papers3,
&ED047TC1,
touch,
static_cast<EpdInitOptions>(EPD_LUT_1K | EPD_FEED_QUEUE_32),
static_cast<const EpdWaveform*>(EPD_BUILTIN_WAVEFORM),
temperature,
drawMode,
fullRefresh,
rotation
);
return std::make_shared<EpdiyDisplay>(std::move(config));
}
};

View File

@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility EspLcdCompat esp_lcd_touch_ft5x06 driver
)

View File

@ -1,3 +0,0 @@
# FT5x06
A Tactility driver for FT5x06 touch.

View File

@ -1,36 +0,0 @@
#include "Ft5x06Touch.h"
#include <esp_lcd_touch_ft5x06.h>
#include <esp_err.h>
#include <esp_lvgl_port.h>
bool Ft5x06Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG();
return esp_lcd_new_panel_io_i2c(configuration->port, &io_config, &outHandle) == ESP_OK;
}
bool Ft5x06Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) {
return esp_lcd_touch_new_i2c_ft5x06(ioHandle, &configuration, &panelHandle) == ESP_OK;
}
esp_lcd_touch_config_t Ft5x06Touch::createEspLcdTouchConfig() {
return {
.x_max = configuration->xMax,
.y_max = configuration->yMax,
.rst_gpio_num = configuration->pinReset,
.int_gpio_num = configuration->pinInterrupt,
.levels = {
.reset = configuration->pinResetLevel,
.interrupt = configuration->pinInterruptLevel,
},
.flags = {
.swap_xy = configuration->swapXy,
.mirror_x = configuration->mirrorX,
.mirror_y = configuration->mirrorY,
},
.process_coordinates = nullptr,
.interrupt_callback = nullptr,
.user_data = nullptr,
.driver_data = nullptr
};
}

View File

@ -1,70 +0,0 @@
#pragma once
#include <Tactility/hal/touch/TouchDevice.h>
#include <Tactility/TactilityCore.h>
#include <driver/i2c.h>
#include <EspLcdTouch.h>
class Ft5x06Touch final : public EspLcdTouch {
public:
class Configuration {
public:
Configuration(
i2c_port_t port,
uint16_t xMax,
uint16_t yMax,
bool swapXy = false,
bool mirrorX = false,
bool mirrorY = false,
gpio_num_t pinReset = GPIO_NUM_NC,
gpio_num_t pinInterrupt = GPIO_NUM_NC,
unsigned int pinResetLevel = 0,
unsigned int pinInterruptLevel = 0
) : port(port),
xMax(xMax),
yMax(yMax),
swapXy(swapXy),
mirrorX(mirrorX),
mirrorY(mirrorY),
pinReset(pinReset),
pinInterrupt(pinInterrupt),
pinResetLevel(pinResetLevel),
pinInterruptLevel(pinInterruptLevel)
{}
i2c_port_t port;
uint16_t xMax;
uint16_t yMax;
bool swapXy;
bool mirrorX;
bool mirrorY;
gpio_num_t pinReset;
gpio_num_t pinInterrupt;
unsigned int pinResetLevel;
unsigned int pinInterruptLevel;
};
private:
std::unique_ptr<Configuration> configuration;
bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override;
bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) override;
esp_lcd_touch_config_t createEspLcdTouchConfig() override;
public:
explicit Ft5x06Touch(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
assert(configuration != nullptr);
}
std::string getName() const override { return "FT5x06"; }
std::string getDescription() const override { return "FT5x06 I2C touch driver"; }
};

View File

@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility EspLcdCompat esp_lcd_touch_ft6336u driver
)

View File

@ -1,3 +0,0 @@
# FT6x36
A Tactility driver for FT6x36 touch.

View File

@ -1,36 +0,0 @@
#include "Ft6x36Touch.h"
#include <esp_lcd_touch_ft6x36.h>
#include <esp_err.h>
#include <esp_lvgl_port.h>
bool Ft6x36Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_FT6x36_CONFIG();
return esp_lcd_new_panel_io_i2c(configuration->port, &io_config, &outHandle) == ESP_OK;
}
bool Ft6x36Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) {
return esp_lcd_touch_new_i2c_ft6x36(ioHandle, &configuration, &panelHandle) == ESP_OK;
}
esp_lcd_touch_config_t Ft6x36Touch::createEspLcdTouchConfig() {
return {
.x_max = configuration->xMax,
.y_max = configuration->yMax,
.rst_gpio_num = configuration->pinReset,
.int_gpio_num = configuration->pinInterrupt,
.levels = {
.reset = configuration->pinResetLevel,
.interrupt = configuration->pinInterruptLevel,
},
.flags = {
.swap_xy = configuration->swapXy,
.mirror_x = configuration->mirrorX,
.mirror_y = configuration->mirrorY,
},
.process_coordinates = nullptr,
.interrupt_callback = nullptr,
.user_data = nullptr,
.driver_data = nullptr
};
}

View File

@ -1,70 +0,0 @@
#pragma once
#include <Tactility/hal/touch/TouchDevice.h>
#include <Tactility/TactilityCore.h>
#include <driver/i2c.h>
#include <EspLcdTouch.h>
class Ft6x36Touch final : public EspLcdTouch {
public:
class Configuration {
public:
Configuration(
i2c_port_t port,
uint16_t xMax,
uint16_t yMax,
bool swapXy = false,
bool mirrorX = false,
bool mirrorY = false,
gpio_num_t pinReset = GPIO_NUM_NC,
gpio_num_t pinInterrupt = GPIO_NUM_NC,
unsigned int pinResetLevel = 0,
unsigned int pinInterruptLevel = 0
) : port(port),
xMax(xMax),
yMax(yMax),
swapXy(swapXy),
mirrorX(mirrorX),
mirrorY(mirrorY),
pinReset(pinReset),
pinInterrupt(pinInterrupt),
pinResetLevel(pinResetLevel),
pinInterruptLevel(pinInterruptLevel)
{}
i2c_port_t port;
uint16_t xMax;
uint16_t yMax;
bool swapXy;
bool mirrorX;
bool mirrorY;
gpio_num_t pinReset;
gpio_num_t pinInterrupt;
unsigned int pinResetLevel;
unsigned int pinInterruptLevel;
};
private:
std::unique_ptr<Configuration> configuration;
bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override;
bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) override;
esp_lcd_touch_config_t createEspLcdTouchConfig() override;
public:
explicit Ft6x36Touch(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
assert(configuration != nullptr);
}
std::string getName() const override { return "FT6x36"; }
std::string getDescription() const override { return "FT6x36 I2C touch driver"; }
};

View File

@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility driver esp_lcd_gc9a01 EspLcdCompat
)

View File

@ -1,3 +0,0 @@
# GC9A01
A basic ESP32 LVGL driver for GC9A01 displays.

View File

@ -1,122 +0,0 @@
#include "Gc9a01Display.h"
#include <tactility/log.h>
#include <esp_lcd_gc9a01.h>
#include <esp_lcd_panel_commands.h>
#include <esp_lvgl_port.h>
constexpr auto* TAG = "GC9A01";
bool Gc9a01Display::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
LOG_I(TAG, "Starting");
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,
.cs_ena_pretrans = 0,
.cs_ena_posttrans = 0,
.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");
return false;
}
return true;
}
bool Gc9a01Display::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,
.rgb_ele_order = configuration->rgbElementOrder,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = 16,
.flags = {
.reset_active_high = false
},
.vendor_config = nullptr
};
if (esp_lcd_new_panel_gc9a01(ioHandle, &panel_config, &panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to create panel");
return false;
}
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to reset panel");
return false;
}
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to init panel");
return false;
}
if (esp_lcd_panel_swap_xy(panelHandle, configuration->swapXY) != ESP_OK) {
LOG_E(TAG, "Failed to swap XY ");
return false;
}
if (esp_lcd_panel_mirror(panelHandle, configuration->mirrorX, configuration->mirrorY) != ESP_OK) {
LOG_E(TAG, "Failed to set panel to mirror");
return false;
}
if (esp_lcd_panel_invert_color(panelHandle, configuration->invertColor) != ESP_OK) {
LOG_E(TAG, "Failed to set panel to invert");
return false;
}
if (esp_lcd_panel_disp_on_off(panelHandle, true) != ESP_OK) {
LOG_E(TAG, "Failed to turn display on");
return false;
}
return true;
}
lvgl_port_display_cfg_t Gc9a01Display::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 = true,
.trans_size = 0,
.hres = configuration->horizontalResolution,
.vres = configuration->verticalResolution,
.monochrome = false,
.rotation = {
.swap_xy = configuration->swapXY,
.mirror_x = configuration->mirrorX,
.mirror_y = configuration->mirrorY,
},
.color_format = LV_COLOR_FORMAT_RGB565,
.flags = {
.buff_dma = true,
.buff_spiram = false,
.sw_rotate = false,
.swap_bytes = true,
.full_refresh = false,
.direct_mode = false
}
};
}

View File

@ -1,101 +0,0 @@
#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 Gc9a01Display 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,
bool swapXY = false,
bool mirrorX = false,
bool mirrorY = false,
bool invertColor = false,
uint32_t bufferSize = 0, // Size in pixel count. 0 means default, which is 1/10 of the screen size
lcd_rgb_element_order_t rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB
) : spiHostDevice(spiHostDevice),
csPin(csPin),
dcPin(dcPin),
horizontalResolution(horizontalResolution),
verticalResolution(verticalResolution),
swapXY(swapXY),
mirrorX(mirrorX),
mirrorY(mirrorY),
invertColor(invertColor),
bufferSize(bufferSize),
rgbElementOrder(rgbElementOrder),
touch(std::move(touch))
{
if (this->bufferSize == 0) {
this->bufferSize = horizontalResolution * verticalResolution / 10;
}
}
spi_host_device_t spiHostDevice;
gpio_num_t csPin;
gpio_num_t dcPin;
gpio_num_t resetPin = GPIO_NUM_NC;
unsigned int pixelClockFrequency = 80'000'000; // Hertz
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; // Size in pixel count. 0 means default, which is 1/10 of the screen size
lcd_rgb_element_order_t rgbElementOrder;
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 Gc9a01Display(std::unique_ptr<Configuration> inConfiguration) :
configuration(std::move(inConfiguration)
) {}
std::string getName() const override { return "GC9A01"; }
std::string getDescription() const override { return "GC9A01 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();

View File

@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility EspLcdCompat esp_lcd_touch_gt911 driver
)

View File

@ -1,3 +0,0 @@
# GT911
GT911 touch driver for Tactility.

View File

@ -1,69 +0,0 @@
#include "Gt911Touch.h"
#include <tactility/log.h>
#include <esp_lcd_io_i2c.h>
#include <esp_lcd_touch_gt911.h>
#include <esp_err.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/esp32_i2c.h>
#include <tactility/drivers/esp32_i2c_master.h>
#include <tactility/drivers/i2c_controller.h>
constexpr auto* TAG = "GT911";
bool Gt911Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
auto* i2c = configuration->i2cController;
if (i2c_controller_has_device_at_address(i2c, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, pdMS_TO_TICKS(10)) == ERROR_NONE) {
io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS;
} else if (i2c_controller_has_device_at_address(i2c, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP, pdMS_TO_TICKS(10)) == ERROR_NONE) {
io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP;
} else {
LOG_E(TAG, "No device found on I2C bus");
return false;
}
// Legacy I2C implementation
auto* driver = device_get_driver(i2c);
if (driver_is_compatible(driver, "espressif,esp32-i2c")) {
auto port = static_cast<const Esp32I2cConfig*>(i2c->config)->port;
return esp_lcd_new_panel_io_i2c_v1(port, &io_config, &outHandle) == ESP_OK;
} else if (driver_is_compatible(driver, "espressif,esp32-i2c-master")) {
auto bus = esp32_i2c_master_get_bus_handle(i2c);
io_config.scl_speed_hz = esp32_i2c_master_get_clock_frequency(configuration->i2cController);
return esp_lcd_new_panel_io_i2c_v2(bus, &io_config, &outHandle) == ESP_OK;
}
LOG_E(TAG, "Unsupported I2C driver");
return false;
}
bool Gt911Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) {
return esp_lcd_touch_new_i2c_gt911(ioHandle, &configuration, &panelHandle) == ESP_OK;
}
esp_lcd_touch_config_t Gt911Touch::createEspLcdTouchConfig() {
return {
.x_max = configuration->xMax,
.y_max = configuration->yMax,
.rst_gpio_num = configuration->pinReset,
.int_gpio_num = configuration->pinInterrupt,
.levels = {
.reset = configuration->pinResetLevel,
.interrupt = configuration->pinInterruptLevel,
},
.flags = {
.swap_xy = static_cast<unsigned int>(configuration->swapXy),
.mirror_x = static_cast<unsigned int>(configuration->mirrorX),
.mirror_y = static_cast<unsigned int>(configuration->mirrorY),
},
.process_coordinates = nullptr,
.interrupt_callback = nullptr,
.user_data = nullptr,
.driver_data = nullptr
};
}

View File

@ -1,71 +0,0 @@
#pragma once
#include <Tactility/hal/touch/TouchDevice.h>
#include <Tactility/TactilityCore.h>
#include <EspLcdTouch.h>
struct Device;
class Gt911Touch final : public EspLcdTouch {
public:
class Configuration {
public:
Configuration(
::Device* i2cController,
uint16_t xMax,
uint16_t yMax,
bool swapXy = false,
bool mirrorX = false,
bool mirrorY = false,
gpio_num_t pinReset = GPIO_NUM_NC,
gpio_num_t pinInterrupt = GPIO_NUM_NC,
unsigned int pinResetLevel = 0,
unsigned int pinInterruptLevel = 0
) : i2cController(i2cController),
xMax(xMax),
yMax(yMax),
swapXy(swapXy),
mirrorX(mirrorX),
mirrorY(mirrorY),
pinReset(pinReset),
pinInterrupt(pinInterrupt),
pinResetLevel(pinResetLevel),
pinInterruptLevel(pinInterruptLevel)
{}
::Device* i2cController;
uint16_t xMax;
uint16_t yMax;
bool swapXy;
bool mirrorX;
bool mirrorY;
gpio_num_t pinReset;
gpio_num_t pinInterrupt;
unsigned int pinResetLevel;
unsigned int pinInterruptLevel;
};
private:
std::unique_ptr<Configuration> configuration;
bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override;
bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) override;
esp_lcd_touch_config_t createEspLcdTouchConfig() override;
public:
explicit Gt911Touch(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
assert(configuration != nullptr);
}
std::string getName() const override { return "GT911"; }
std::string getDescription() const override { return "GT911 I2C touch driver"; }
};

View File

@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility EspLcdCompat esp_lcd_ili9341 driver
)

View File

@ -1,3 +0,0 @@
# ILI934x
A basic ESP32 LVGL driver for ILI9341 and ILI9342 displays.

View File

@ -1,43 +0,0 @@
#include "Ili934xDisplay.h"
#include <esp_lcd_ili9341.h>
#include <esp_lvgl_port.h>
std::shared_ptr<EspLcdConfiguration> Ili934xDisplay::createEspLcdConfiguration(const Configuration& configuration) {
return std::make_shared<EspLcdConfiguration>(EspLcdConfiguration {
.horizontalResolution = configuration.horizontalResolution,
.verticalResolution = configuration.verticalResolution,
.gapX = configuration.gapX,
.gapY = configuration.gapY,
.monochrome = false,
.swapXY = configuration.swapXY,
.mirrorX = configuration.mirrorX,
.mirrorY = configuration.mirrorY,
.invertColor = configuration.invertColor,
.bufferSize = configuration.bufferSize,
.touch = configuration.touch,
.backlightDutyFunction = configuration.backlightDutyFunction,
.resetPin = configuration.resetPin,
.lvglColorFormat = LV_COLOR_FORMAT_RGB565,
.lvglSwapBytes = configuration.swapBytes,
.rgbElementOrder = configuration.rgbElementOrder,
.bitsPerPixel = 16
});
}
esp_lcd_panel_dev_config_t Ili934xDisplay::createPanelConfig(std::shared_ptr<EspLcdConfiguration> espLcdConfiguration, gpio_num_t resetPin) {
return {
.reset_gpio_num = resetPin,
.rgb_ele_order = espLcdConfiguration->rgbElementOrder,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = espLcdConfiguration->bitsPerPixel,
.flags = {
.reset_active_high = 0
},
.vendor_config = nullptr
};
}
bool Ili934xDisplay::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) {
return esp_lcd_new_panel_ili9341(ioHandle, &panelConfig, &panelHandle) == ESP_OK;
}

View File

@ -1,53 +0,0 @@
#pragma once
#include <EspLcdSpiDisplay.h>
#include <driver/gpio.h>
#include <functional>
#include <lvgl.h>
class Ili934xDisplay final : public EspLcdSpiDisplay {
public:
/** Minimal set of overrides for EspLcdConfiguration */
struct Configuration {
unsigned int horizontalResolution;
unsigned int verticalResolution;
int gapX;
int gapY;
bool swapXY;
bool mirrorX;
bool mirrorY;
bool invertColor;
bool swapBytes;
uint32_t bufferSize; // Pixel count, not byte count. Set to 0 for default (1/10th of display size)
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
std::function<void(uint8_t)> backlightDutyFunction; // Nullable
gpio_num_t resetPin;
lcd_rgb_element_order_t rgbElementOrder;
};
private:
static std::shared_ptr<EspLcdConfiguration> createEspLcdConfiguration(const Configuration& configuration);
esp_lcd_panel_dev_config_t createPanelConfig(std::shared_ptr<EspLcdConfiguration> espLcdConfiguration, gpio_num_t resetPin) override;
bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) override;
public:
explicit Ili934xDisplay(const Configuration& configuration, const std::shared_ptr<SpiConfiguration>& spiConfiguration, bool hasGammaCurves) :
EspLcdSpiDisplay(
createEspLcdConfiguration(configuration),
spiConfiguration,
(hasGammaCurves ? 4 : 0)
)
{
}
std::string getName() const override { return "ILI934x"; }
std::string getDescription() const override { return "ILI934x display"; }
};

View File

@ -1,3 +0,0 @@
idf_component_register(SRCS "esp_lcd_jd9853.c"
INCLUDE_DIRS "include"
REQUIRES "esp_lcd" "driver")

View File

@ -1,7 +0,0 @@
# JD9853
SPI display driver.
Source: https://files.waveshare.com/wiki/1.47inch%20Touch%20LCD/1.47inch_Touch_LCD_Demo_ESP32.zip
License: Apache 2.0

View File

@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility driver
)

View File

@ -1,3 +0,0 @@
# PWM Backlight Driver
A very basic driver to control an LCD panel backlight with a PWM signal.

View File

@ -1,67 +0,0 @@
#include "PwmBacklight.h"
#include <tactility/log.h>
constexpr auto* TAG = "PwmBacklight";
namespace driver::pwmbacklight {
static bool isBacklightInitialized = false;
static gpio_num_t backlightPin = GPIO_NUM_NC;
static ledc_timer_t backlightTimer;
static ledc_channel_t backlightChannel;
bool init(gpio_num_t pin, uint32_t frequencyHz, ledc_timer_t timer, ledc_channel_t channel) {
backlightPin = pin;
backlightTimer = timer;
backlightChannel = channel;
LOG_I(TAG, "Init");
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_8_BIT,
.timer_num = backlightTimer,
.freq_hz = frequencyHz,
.clk_cfg = LEDC_AUTO_CLK,
.deconfigure = false
};
if (ledc_timer_config(&ledc_timer) != ESP_OK) {
LOG_E(TAG, "Timer config failed");
return false;
}
ledc_channel_config_t ledc_channel = {
.gpio_num = backlightPin,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = backlightChannel,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = backlightTimer,
.duty = 0,
.hpoint = 0,
.sleep_mode = LEDC_SLEEP_MODE_NO_ALIVE_NO_PD,
.flags = {
.output_invert = 0
}
};
if (ledc_channel_config(&ledc_channel) != ESP_OK) {
LOG_E(TAG, "Channel config failed");
return false;
}
isBacklightInitialized = true;
return true;
}
bool setBacklightDuty(uint8_t duty) {
if (!isBacklightInitialized) {
LOG_E(TAG, "Not initialized");
return false;
}
return ledc_set_duty(LEDC_LOW_SPEED_MODE, backlightChannel, duty) == ESP_OK &&
ledc_update_duty(LEDC_LOW_SPEED_MODE, backlightChannel) == ESP_OK;
}
}

View File

@ -1,12 +0,0 @@
#pragma once
#include <driver/ledc.h>
#include <driver/gpio.h>
namespace driver::pwmbacklight {
bool init(gpio_num_t pin, uint32_t frequencyHz = 40000, ledc_timer_t timer = LEDC_TIMER_0, ledc_channel_t channel = LEDC_CHANNEL_0);
bool setBacklightDuty(uint8_t duty);
}

View File

@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility EspLcdCompat
)

View File

@ -1,3 +0,0 @@
# RGB Display Driver
This driver is used for displays that use multiple parallel data lanes.

View File

@ -1,150 +0,0 @@
#include "RgbDisplay.h"
#include <tactility/check.h>
#include <Tactility/hal/touch/TouchDevice.h>
#include <tactility/log.h>
#include <esp_err.h>
#include <esp_lcd_panel_rgb.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lvgl_port.h>
constexpr auto* TAG = "RgbDisplay";
RgbDisplay::~RgbDisplay() {
check(
displayDriver == nullptr || displayDriver.use_count() == 0,
"DisplayDriver is still in use. This will cause memory access violations."
);
}
bool RgbDisplay::start() {
LOG_I(TAG, "Starting");
if (esp_lcd_new_rgb_panel(&configuration->panelConfig, &panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to create panel");
return false;
}
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to reset panel");
return false;
}
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to init panel");
return false;
}
if (esp_lcd_panel_swap_xy(panelHandle, configuration->swapXY) != ESP_OK) {
LOG_E(TAG, "Failed to swap XY");
return false;
}
if (esp_lcd_panel_mirror(panelHandle, configuration->mirrorX, configuration->mirrorY) != ESP_OK) {
LOG_E(TAG, "Failed to set panel to mirror");
return false;
}
if (esp_lcd_panel_invert_color(panelHandle, configuration->invertColor) != ESP_OK) {
LOG_E(TAG, "Failed to set panel to invert");
return false;
}
return true;
}
bool RgbDisplay::stop() {
if (lvglDisplay != nullptr) {
stopLvgl();
lvglDisplay = nullptr;
}
if (panelHandle != nullptr && esp_lcd_panel_del(panelHandle) != ESP_OK) {
return false;
}
if (displayDriver != nullptr && displayDriver.use_count() > 1) {
LOG_W(TAG, "DisplayDriver is still in use.");
}
auto touch_device = getTouchDevice();
if (touch_device != nullptr) {
touch_device->startLvgl(lvglDisplay);
}
return true;
}
bool RgbDisplay::startLvgl() {
assert(lvglDisplay == nullptr);
if (displayDriver != nullptr && displayDriver.use_count() > 1) {
LOG_W(TAG, "DisplayDriver is still in use.");
}
auto display_config = getLvglPortDisplayConfig();
const lvgl_port_display_rgb_cfg_t rgb_config = {
.flags = {
.bb_mode = configuration->bufferConfiguration.bounceBufferMode,
.avoid_tearing = configuration->bufferConfiguration.avoidTearing
}
};
lvglDisplay = lvgl_port_add_disp_rgb(&display_config, &rgb_config);
LOG_I(TAG, "Finished");
auto touch_device = getTouchDevice();
if (touch_device != nullptr) {
touch_device->startLvgl(lvglDisplay);
}
return lvglDisplay != nullptr;
}
bool RgbDisplay::stopLvgl() {
if (lvglDisplay == nullptr) {
return false;
}
auto touch_device = getTouchDevice();
if (touch_device != nullptr) {
touch_device->stopLvgl();
}
lvgl_port_remove_disp(lvglDisplay);
lvglDisplay = nullptr;
return true;
}
lvgl_port_display_cfg_t RgbDisplay::getLvglPortDisplayConfig() const {
return {
.io_handle = nullptr,
.panel_handle = panelHandle,
.control_handle = nullptr,
.buffer_size = configuration->bufferConfiguration.size,
.double_buffer = configuration->bufferConfiguration.doubleBuffer,
.trans_size = 0,
.hres = configuration->panelConfig.timings.h_res,
.vres = configuration->panelConfig.timings.v_res,
.monochrome = false,
.rotation = {
.swap_xy = configuration->swapXY,
.mirror_x = configuration->mirrorX,
.mirror_y = configuration->mirrorY,
},
.color_format = configuration->colorFormat,
.flags = {
.buff_dma = !configuration->bufferConfiguration.useSpi,
.buff_spiram = configuration->bufferConfiguration.useSpi,
.sw_rotate = false,
.swap_bytes = false,
.full_refresh = false,
.direct_mode = false
}
};
}

View File

@ -1,114 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <EspLcdDisplayDriver.h>
#include <esp_lcd_panel_rgb.h>
#include <esp_lvgl_port_disp.h>
class RgbDisplay final : public tt::hal::display::DisplayDevice {
public:
struct BufferConfiguration final {
uint32_t size; // Size in pixel count. 0 means default, which is 1/15 of the screen size
bool useSpi;
bool doubleBuffer;
bool bounceBufferMode;
bool avoidTearing;
};
class Configuration final {
public:
esp_lcd_rgb_panel_config_t panelConfig;
BufferConfiguration bufferConfiguration;
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
lv_color_format_t colorFormat;
bool swapXY;
bool mirrorX;
bool mirrorY;
bool invertColor;
std::function<void(uint8_t)> _Nullable backlightDutyFunction;
Configuration(
esp_lcd_rgb_panel_config_t panelConfig,
BufferConfiguration bufferConfiguration,
std::shared_ptr<tt::hal::touch::TouchDevice> touch,
lv_color_format_t colorFormat,
bool swapXY = false,
bool mirrorX = false,
bool mirrorY = false,
bool invertColor = false,
std::function<void(uint8_t)> _Nullable backlightDutyFunction = nullptr
) : panelConfig(panelConfig),
bufferConfiguration(bufferConfiguration),
touch(std::move(touch)),
colorFormat(colorFormat),
swapXY(swapXY),
mirrorX(mirrorX),
mirrorY(mirrorY),
invertColor(invertColor),
backlightDutyFunction(std::move(backlightDutyFunction)) {
if (this->bufferConfiguration.size == 0) {
auto horizontal_resolution = panelConfig.timings.h_res;
auto vertical_resolution = panelConfig.timings.v_res;
this->bufferConfiguration.size = horizontal_resolution * vertical_resolution / 15;
}
}
};
private:
std::unique_ptr<Configuration> _Nullable configuration = nullptr;
esp_lcd_panel_handle_t _Nullable panelHandle = nullptr;
lv_display_t* _Nullable lvglDisplay = nullptr;
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable displayDriver;
lvgl_port_display_cfg_t getLvglPortDisplayConfig() const;
public:
explicit RgbDisplay(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
assert(configuration != nullptr);
}
~RgbDisplay() override;
std::string getName() const override { return "RGB Display"; }
std::string getDescription() const override { return "RGB Display"; }
bool start() override;
bool stop() override;
bool supportsLvgl() const override { return true; }
bool startLvgl() override;
bool stopLvgl() override;
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; }
lv_display_t* _Nullable getLvglDisplay() const override { return lvglDisplay; }
// TODO: Fix driver and re-enable
bool supportsDisplayDriver() const override { return false; }
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable getDisplayDriver() override {
if (displayDriver == nullptr) {
auto config = getLvglPortDisplayConfig();
displayDriver = std::make_shared<EspLcdDisplayDriver>(panelHandle, config.hres, config.vres, tt::hal::display::ColorFormat::RGB888);
}
return displayDriver;
}
};
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility driver EspLcdCompat
)

View File

@ -1,3 +0,0 @@
# ST7789
A basic ESP32 LVGL driver for ST7789 displays.

View File

@ -1,43 +0,0 @@
#include "St7789Display.h"
#include <esp_lcd_panel_st7789.h>
std::shared_ptr<EspLcdConfiguration> St7789Display::createEspLcdConfiguration(const Configuration& configuration) {
return std::make_shared<EspLcdConfiguration>(EspLcdConfiguration {
.horizontalResolution = configuration.horizontalResolution,
.verticalResolution = configuration.verticalResolution,
.gapX = configuration.gapX,
.gapY = configuration.gapY,
.monochrome = false,
.swapXY = configuration.swapXY,
.mirrorX = configuration.mirrorX,
.mirrorY = configuration.mirrorY,
.invertColor = configuration.invertColor,
.bufferSize = configuration.bufferSize,
.buffSpiram = configuration.buffSpiram,
.touch = configuration.touch,
.backlightDutyFunction = configuration.backlightDutyFunction,
.resetPin = configuration.resetPin,
.lvglColorFormat = LV_COLOR_FORMAT_RGB565,
.lvglSwapBytes = configuration.lvglSwapBytes,
.rgbElementOrder = configuration.rgbElementOrder,
.bitsPerPixel = 16,
});
}
esp_lcd_panel_dev_config_t St7789Display::createPanelConfig(std::shared_ptr<EspLcdConfiguration> espLcdConfiguration, gpio_num_t resetPin) {
return {
.reset_gpio_num = resetPin,
.rgb_ele_order = espLcdConfiguration->rgbElementOrder,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = espLcdConfiguration->bitsPerPixel,
.flags = {
.reset_active_high = false
},
.vendor_config = nullptr
};
}
bool St7789Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) {
return esp_lcd_new_panel_st7789(ioHandle, &panelConfig, &panelHandle) == ESP_OK;
}

View File

@ -1,56 +0,0 @@
#pragma once
#include <EspLcdSpiDisplay.h>
#include <driver/gpio.h>
#include <functional>
#include <lvgl.h>
class St7789Display final : public EspLcdSpiDisplay {
public:
/** Minimal set of overrides for EspLcdConfiguration */
struct Configuration {
unsigned int horizontalResolution;
unsigned int verticalResolution;
int gapX;
int gapY;
bool swapXY;
bool mirrorX;
bool mirrorY;
bool invertColor;
uint32_t bufferSize; // Pixel count, not byte count. Set to 0 for default (1/10th of display size)
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
std::function<void(uint8_t)> _Nullable backlightDutyFunction;
gpio_num_t resetPin;
bool lvglSwapBytes;
bool buffSpiram = false;
lcd_rgb_element_order_t rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB;
};
private:
static std::shared_ptr<EspLcdConfiguration> createEspLcdConfiguration(const Configuration& configuration);
esp_lcd_panel_dev_config_t createPanelConfig(std::shared_ptr<EspLcdConfiguration> espLcdConfiguration, gpio_num_t resetPin) override;
bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) override;
public:
explicit St7789Display(const Configuration& configuration, const std::shared_ptr<SpiConfiguration>& spiConfiguration) :
EspLcdSpiDisplay(
createEspLcdConfiguration(configuration),
spiConfiguration,
4
)
{
}
std::string getName() const override { return "ST7789"; }
std::string getDescription() const override { return "ST7789 display"; }
};
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility EspLcdCompat esp_lcd_st7796 driver
)

View File

@ -1,3 +0,0 @@
# ST7796
A basic ESP32 LVGL driver for ST7796 parallel i8080 displays.

View File

@ -1,275 +0,0 @@
#include "St7796i8080Display.h"
#include <tactility/log.h>
#include <driver/gpio.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lvgl_port.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <lvgl.h>
constexpr auto* TAG = "St7796i8080Display";
static St7796i8080Display* g_display_instance = nullptr;
St7796i8080Display::St7796i8080Display(const Configuration& config)
: configuration(config), lock(std::make_shared<std::mutex>()) {
// Validate configuration
if (!configuration.isValid()) {
LOG_E(TAG, "Invalid configuration: resolution must be set");
return;
}
}
bool St7796i8080Display::createI80Bus() {
LOG_I(TAG, "Creating I80 bus");
// Create I80 bus configuration
esp_lcd_i80_bus_config_t bus_cfg = {
.dc_gpio_num = configuration.dcPin,
.wr_gpio_num = configuration.wrPin,
.clk_src = LCD_CLK_SRC_PLL160M,
.data_gpio_nums = {
configuration.dataPins[0], configuration.dataPins[1],
configuration.dataPins[2], configuration.dataPins[3],
configuration.dataPins[4], configuration.dataPins[5],
configuration.dataPins[6], configuration.dataPins[7],
},
.bus_width = configuration.busWidth,
.max_transfer_bytes = configuration.bufferSize * sizeof(uint16_t),
.psram_trans_align = 64,
.sram_trans_align = 4
};
if (esp_lcd_new_i80_bus(&bus_cfg, &i80BusHandle) != ESP_OK) {
LOG_E(TAG, "Failed to create I80 bus");
return false;
}
return true;
}
bool St7796i8080Display::createPanelIO() {
LOG_I(TAG, "Creating panel IO");
// Create panel IO
esp_lcd_panel_io_i80_config_t io_cfg = {
.cs_gpio_num = configuration.csPin,
.pclk_hz = configuration.pixelClockFrequency,
.trans_queue_depth = configuration.transactionQueueDepth,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = configuration.lcdCmdBits,
.lcd_param_bits = configuration.lcdParamBits,
.dc_levels = {
.dc_idle_level = configuration.dcLevels.dcIdleLevel,
.dc_cmd_level = configuration.dcLevels.dcCmdLevel,
.dc_dummy_level = configuration.dcLevels.dcDummyLevel,
.dc_data_level = configuration.dcLevels.dcDataLevel,
},
.flags = {
.cs_active_high = configuration.flags.csActiveHigh,
.reverse_color_bits = configuration.flags.reverseColorBits,
.swap_color_bytes = configuration.flags.swapColorBytes,
.pclk_active_neg = configuration.flags.pclkActiveNeg,
.pclk_idle_low = configuration.flags.pclkIdleLow
}
};
if (esp_lcd_new_panel_io_i80(i80BusHandle, &io_cfg, &ioHandle) != ESP_OK) {
LOG_E(TAG, "Failed to create panel");
return false;
}
return true;
}
bool St7796i8080Display::createPanel() {
LOG_I(TAG, "Configuring panel");
// Create ST7796 panel
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = configuration.resetPin,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = 16,
.flags = {
.reset_active_high = false
},
.vendor_config = nullptr
};
if (esp_lcd_new_panel_st7796(ioHandle, &panel_config, &panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to create panel");
return false;
}
// Reset panel
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to reset panel");
return false;
}
// Initialize panel
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to init panel");
return false;
}
// Set swap XY
if (esp_lcd_panel_swap_xy(panelHandle, configuration.swapXY) != ESP_OK) {
LOG_E(TAG, "Failed to swap XY ");
return false;
}
// Set mirror
if (esp_lcd_panel_mirror(panelHandle, configuration.mirrorX, configuration.mirrorY) != ESP_OK) {
LOG_E(TAG, "Failed to set panel to mirror");
return false;
}
// Set inversion
if (esp_lcd_panel_invert_color(panelHandle, configuration.invertColor) != ESP_OK) {
LOG_E(TAG, "Failed to set panel to invert");
return false;
}
// Turn on display
if (esp_lcd_panel_disp_on_off(panelHandle, true) != ESP_OK) {
LOG_E(TAG, "Failed to turn display on");
return false;
}
return true;
}
bool St7796i8080Display::start() {
LOG_I(TAG, "Initializing I8080 ST7796 Display hardware...");
// Calculate buffer size if needed
configuration.calculateBufferSize();
// Allocate buffer
size_t buffer_size = configuration.bufferSize * LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565);
displayBuffer = (uint8_t*)heap_caps_malloc(buffer_size, MALLOC_CAP_DMA);
if (!displayBuffer) {
LOG_E(TAG, "Failed to allocate display buffer");
return false;
}
// Create I80 bus
if (!createI80Bus()) {
stop();
return false;
}
// Create panel IO
if (!createPanelIO()) {
stop();
return false;
}
// Create panel
if (!createPanel()) {
stop();
return false;
}
LOG_I(TAG, "Display hardware initialized");
return true;
}
bool St7796i8080Display::stop() {
// Turn off display
if (panelHandle) {
esp_lcd_panel_disp_on_off(panelHandle, false);
}
// Destroy in reverse order: panel, IO, bus
if (panelHandle) {
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
}
if (ioHandle) {
esp_lcd_panel_io_del(ioHandle);
ioHandle = nullptr;
}
if (i80BusHandle) {
esp_lcd_del_i80_bus(i80BusHandle);
i80BusHandle = nullptr;
}
// Free buffer 1
if (displayBuffer) {
heap_caps_free(displayBuffer);
displayBuffer = nullptr;
}
// Turn off backlight
if (configuration.backlightDutyFunction) {
configuration.backlightDutyFunction(0);
}
return true;
}
bool St7796i8080Display::startLvgl() {
LOG_I(TAG, "Initializing LVGL for ST7796 display");
// Don't reinitialize hardware if it's already done
if (!ioHandle) {
LOG_I(TAG, "Hardware not initialized, calling start()");
if (!start()) {
LOG_E(TAG, "Hardware initialization failed");
return false;
}
} else {
LOG_I(TAG, "Hardware already initialized, skipping");
}
// Create LVGL display using lvgl_port
lvgl_port_display_cfg_t display_cfg = {
.io_handle = ioHandle,
.panel_handle = panelHandle,
.control_handle = nullptr,
.buffer_size = configuration.bufferSize,
.double_buffer = false,
.trans_size = 0,
.hres = configuration.horizontalResolution,
.vres = configuration.verticalResolution,
.monochrome = false,
.rotation = {
.swap_xy = configuration.swapXY,
.mirror_x = configuration.mirrorX,
.mirror_y = configuration.mirrorY,
},
.color_format = LV_COLOR_FORMAT_RGB565,
.flags = {
.buff_dma = true,
.buff_spiram = false,
.sw_rotate = false,
.swap_bytes = false,
.full_refresh = false,
.direct_mode = false
}
};
// Create the LVGL display
lvglDisplay = lvgl_port_add_disp(&display_cfg);
if (!lvglDisplay) {
LOG_E(TAG, "Failed to create LVGL display");
return false;
}
g_display_instance = this;
LOG_I(TAG, "LVGL display created successfully");
return true;
}
bool St7796i8080Display::stopLvgl() {
if (lvglDisplay) {
lvgl_port_remove_disp(lvglDisplay);
lvglDisplay = nullptr;
}
return true;
}

View File

@ -1,136 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_types.h>
#include <esp_lcd_st7796.h>
#include <driver/gpio.h>
#include <array>
#include <memory>
#include <functional>
#include <mutex>
#include <string>
class St7796i8080Display : public tt::hal::display::DisplayDevice {
public:
struct Configuration {
// Pin configuration
gpio_num_t csPin;
gpio_num_t dcPin;
gpio_num_t wrPin;
std::array<gpio_num_t, 8> dataPins;
gpio_num_t resetPin;
gpio_num_t backlightPin;
// Display resolution configuration
uint16_t horizontalResolution;
uint16_t verticalResolution;
// Bus configuration
unsigned int pixelClockFrequency = 20 * 1000 * 1000; // 20MHz default
size_t busWidth = 8; // 8-bit bus
size_t transactionQueueDepth = 10;
size_t bufferSize = 0; // Will be calculated if 0
// LCD command/parameter configuration
int lcdCmdBits = 8;
int lcdParamBits = 8;
// DC line level configuration
struct {
bool dcIdleLevel = 0;
bool dcCmdLevel = 0;
bool dcDummyLevel = 0;
bool dcDataLevel = 1;
} dcLevels;
// Bus flags
struct {
bool csActiveHigh = false;
bool reverseColorBits = false;
bool swapColorBytes = true;
bool pclkActiveNeg = false;
bool pclkIdleLow = false;
} flags;
// Display configuration
bool swapXY = false;
bool mirrorX = false;
bool mirrorY = false;
bool invertColor = true;
// Additional features
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr;
std::function<void(uint8_t)> backlightDutyFunction = nullptr;
// Basic constructor - requires resolution to be set separately
Configuration(gpio_num_t cs, gpio_num_t dc, gpio_num_t wr,
std::array<gpio_num_t, 8> data, gpio_num_t rst, gpio_num_t bl)
: csPin(cs), dcPin(dc), wrPin(wr), dataPins(data), resetPin(rst), backlightPin(bl),
horizontalResolution(0), verticalResolution(0) {} // Initialize to 0
// Method to calculate buffer size after resolution is set
void calculateBufferSize() {
if (bufferSize == 0 && horizontalResolution > 0 && verticalResolution > 0) {
bufferSize = horizontalResolution * verticalResolution / 10;
}
}
// Validation method
bool isValid() const {
return horizontalResolution > 0 && verticalResolution > 0;
}
};
private:
Configuration configuration;
esp_lcd_i80_bus_handle_t i80BusHandle = nullptr;
esp_lcd_panel_io_handle_t ioHandle = nullptr;
esp_lcd_panel_handle_t panelHandle = nullptr;
lv_display_t* lvglDisplay = nullptr;
std::shared_ptr<std::mutex> lock;
uint8_t* displayBuffer = nullptr;
// Internal initialization methods
bool createI80Bus();
bool createPanelIO();
bool createPanel();
public:
explicit St7796i8080Display(const Configuration& config);
lv_display_t* getLvglDisplay() const override { return lvglDisplay; }
std::string getName() const override { return "I8080 ST7796"; }
std::string getDescription() const override { return "I8080-based ST7796 display"; }
// Lifecycle
bool start() override;
bool stop() override;
bool startLvgl() override;
bool stopLvgl() override;
// Capabilities
bool supportsLvgl() const override { return true; }
bool supportsDisplayDriver() const override { return false; }
bool supportsBacklightDuty() const override { return configuration.backlightDutyFunction != nullptr; }
// Touch and backlight
std::shared_ptr<tt::hal::touch::TouchDevice> getTouchDevice() override { return configuration.touch; }
std::shared_ptr<tt::hal::display::DisplayDriver> getDisplayDriver() override { return nullptr; }
void setBacklightDuty(uint8_t backlightDuty) override {
if (configuration.backlightDutyFunction != nullptr) {
configuration.backlightDutyFunction(backlightDuty);
}
}
// Hardware access methods
esp_lcd_panel_io_handle_t getIoHandle() const { return ioHandle; }
esp_lcd_panel_handle_t getPanelHandle() const { return panelHandle; }
// Resolution access methods
uint16_t getHorizontalResolution() const { return configuration.horizontalResolution; }
uint16_t getVerticalResolution() const { return configuration.verticalResolution; }
};
// Factory function for registration
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -7,5 +7,5 @@ file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(jd9853-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
REQUIRES TactilityKernel platform-esp32 JD9853 esp_lcd driver
REQUIRES TactilityKernel platform-esp32 esp_lcd
)

View File

@ -1,3 +1,4 @@
dependencies:
- TactilityKernel
- Platforms/platform-esp32
bindings: bindings

View File

@ -1,5 +1,5 @@
#include <esp_lcd_jd9853.h>
#include <stdio.h>
#include "esp_lcd_jd9853.h"
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
@ -8,16 +8,16 @@
#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 "esp_log.h"
#include "esp_check.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 <esp_log.h>
#include <esp_check.h>
static const char *TAG = "JD9853";