Device and driver migrations

This commit is contained in:
Ken Van Hoeylandt 2026-07-13 23:18:37 +02:00
parent 8f5e92d6e4
commit f31103882d
75 changed files with 2113 additions and 694 deletions

View File

@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port ILI934x XPT2046SoftSPI PwmBacklight driver vfs fatfs
REQUIRES TactilityKernel driver
)

View File

@ -1,32 +0,0 @@
#include "devices/Display.h"
#include <driver/gpio.h>
#include <Tactility/hal/Configuration.h>
#include <PwmBacklight.h>
using namespace tt::hal;
static bool initBoot() {
// Set the RGB LED Pins to output and turn them off
ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT)); // Red
ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT)); // Green
ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT)); // Blue
// 0 on, 1 off
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_4, 1)); // Red
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_16, 1)); // Green
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_17, 1)); // Blue
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT);
}
static DeviceVector createDevices() {
return {
createDisplay(),
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};

View File

@ -1,49 +0,0 @@
#include "Display.h"
#include "Xpt2046SoftSpi.h"
#include <Ili934xDisplay.h>
#include <PwmBacklight.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto configuration = std::make_unique<Xpt2046SoftSpi::Configuration>(
TOUCH_MOSI_PIN,
TOUCH_MISO_PIN,
TOUCH_SCK_PIN,
TOUCH_CS_PIN,
LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION,
false, // swapXY
true, // mirrorX
false // mirrorY
);
return std::make_shared<Xpt2046SoftSpi>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
Ili934xDisplay::Configuration panel_configuration = {
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
.verticalResolution = LCD_VERTICAL_RESOLUTION,
.gapX = 0,
.gapY = 0,
.swapXY = false,
.mirrorX = true,
.mirrorY = false,
.invertColor = false,
.swapBytes = true,
.bufferSize = LCD_BUFFER_SIZE,
.touch = createTouch(),
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
.resetPin = GPIO_NUM_NC,
.rgbElementOrder = LCD_RGB_ELEMENT_ORDER_BGR
};
auto spi_configuration = std::make_shared<Ili934xDisplay::SpiConfiguration>(Ili934xDisplay::SpiConfiguration {
.spiHostDevice = LCD_SPI_HOST,
.csPin = LCD_PIN_CS,
.dcPin = LCD_PIN_DC,
.pixelClockFrequency = 40'000'000,
.transactionQueueDepth = 10
});
return std::make_shared<Ili934xDisplay>(panel_configuration, spi_configuration, true);
}

View File

@ -1,27 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <driver/gpio.h>
#include <driver/spi_common.h>
#include <memory>
// Display
constexpr auto LCD_SPI_HOST = SPI2_HOST;
constexpr auto LCD_PIN_CS = GPIO_NUM_15;
constexpr auto LCD_PIN_DC = GPIO_NUM_2;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 240;
constexpr auto LCD_VERTICAL_RESOLUTION = 320;
constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10;
constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT;
// Display backlight (PWM)
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_21;
// Touch (Software SPI)
constexpr auto TOUCH_MISO_PIN = GPIO_NUM_39;
constexpr auto TOUCH_MOSI_PIN = GPIO_NUM_32;
constexpr auto TOUCH_SCK_PIN = GPIO_NUM_25;
constexpr auto TOUCH_CS_PIN = GPIO_NUM_33;
constexpr auto TOUCH_IRQ_PIN = GPIO_NUM_36;
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -1,23 +0,0 @@
#include <tactility/module.h>
extern "C" {
static error_t start() {
// Empty for now
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module cyd_2432s028r_module = {
.name = "cyd-2432s028r",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}

View File

@ -7,8 +7,9 @@
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <bindings/ili9341.h>
#include <bindings/xpt2046_softspi.h>
/ {
compatible = "root";
@ -32,21 +33,43 @@
pin-scl = <&gpio0 22 GPIO_FLAG_NONE>;
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pin-backlight = <&gpio0 21 GPIO_FLAG_NONE>;
frequency-hz = <512>;
};
touch {
compatible = "xptek,xpt2046-softspi";
pin-mosi = <&gpio0 32 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 39 GPIO_FLAG_NONE>;
pin-sck = <&gpio0 25 GPIO_FLAG_NONE>;
pin-cs = <&gpio0 33 GPIO_FLAG_NONE>;
x-max = <240>;
y-max = <320>;
mirror-x;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
cs-gpios = <&gpio0 15 GPIO_FLAG_NONE>, // Display
<&gpio0 33 GPIO_FLAG_NONE>; // Touch
cs-gpios = <&gpio0 15 GPIO_FLAG_NONE>;
pin-mosi = <&gpio0 13 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 12 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 14 GPIO_FLAG_NONE>;
display@0 {
compatible = "display-placeholder";
};
touch@1 {
compatible = "pointer-placeholder";
display@0 {
compatible = "ilitek,ili9341";
horizontal-resolution = <240>;
vertical-resolution = <320>;
mirror-x;
bgr-order;
pixel-clock-hz = <40000000>;
pin-dc = <&gpio0 2 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
};

View File

@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD
display.size=2.8"

View File

@ -1,3 +1,5 @@
dependencies:
- Platforms/platform-esp32
- Drivers/ili9341-module
- Drivers/xpt2046-softspi-module
dts: cyd,2432s028r.dts

View File

@ -0,0 +1,34 @@
#include <tactility/module.h>
#include <tactility/error.h>
#include <driver/gpio.h>
extern "C" {
static error_t start() {
// Set the RGB LED pins to output and turn them off (0 on, 1 off)
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT); // Red
gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT); // Green
gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT); // Blue
gpio_set_level(GPIO_NUM_4, 1); // Red
gpio_set_level(GPIO_NUM_16, 1); // Green
gpio_set_level(GPIO_NUM_17, 1); // Blue
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module cyd_2432s028r_module = {
.name = "cyd-2432s028r",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}

View File

@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port ST7789 XPT2046SoftSPI PwmBacklight driver vfs fatfs
REQUIRES TactilityKernel driver
)

View File

@ -1,32 +0,0 @@
#include "devices/Display.h"
#include <driver/gpio.h>
#include <Tactility/hal/Configuration.h>
#include <PwmBacklight.h>
using namespace tt::hal;
static bool initBoot() {
// Set the RGB LED Pins to output and turn them off
ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT)); // Red
ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT)); // Green
ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT)); // Blue
// 0 on, 1 off
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_4, 1)); // Red
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_16, 1)); // Green
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_17, 1)); // Blue
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT);
}
static DeviceVector createDevices() {
return {
createDisplay(),
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};

View File

@ -1,50 +0,0 @@
#include "Display.h"
#include "Xpt2046SoftSpi.h"
#include <St7789Display.h>
#include <PwmBacklight.h>
constexpr auto* TAG = "CYD";
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto configuration = std::make_unique<Xpt2046SoftSpi::Configuration>(
TOUCH_MOSI_PIN,
TOUCH_MISO_PIN,
TOUCH_SCK_PIN,
TOUCH_CS_PIN,
LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION,
false, // swapXY
true, // mirrorX
false // mirrorY
);
return std::make_shared<Xpt2046SoftSpi>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
St7789Display::Configuration panel_configuration = {
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
.verticalResolution = LCD_VERTICAL_RESOLUTION,
.gapX = 0,
.gapY = 0,
.swapXY = false,
.mirrorX = false,
.mirrorY = false,
.invertColor = false,
.bufferSize = LCD_BUFFER_SIZE,
.touch = createTouch(),
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
.resetPin = GPIO_NUM_NC,
.lvglSwapBytes = false
};
auto spi_configuration = std::make_shared<St7789Display::SpiConfiguration>(St7789Display::SpiConfiguration {
.spiHostDevice = LCD_SPI_HOST,
.csPin = LCD_PIN_CS,
.dcPin = LCD_PIN_DC,
.pixelClockFrequency = 62'500'000,
.transactionQueueDepth = 10
});
return std::make_shared<St7789Display>(panel_configuration, spi_configuration);
}

View File

@ -1,27 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <driver/gpio.h>
#include <driver/spi_common.h>
#include <memory>
// Display
constexpr auto LCD_SPI_HOST = SPI2_HOST;
constexpr auto LCD_PIN_CS = GPIO_NUM_15;
constexpr auto LCD_PIN_DC = GPIO_NUM_2;
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_21;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 240;
constexpr auto LCD_VERTICAL_RESOLUTION = 320;
constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10;
constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT;
// Touch (Software SPI)
constexpr auto TOUCH_MISO_PIN = GPIO_NUM_39;
constexpr auto TOUCH_MOSI_PIN = GPIO_NUM_32;
constexpr auto TOUCH_SCK_PIN = GPIO_NUM_25;
constexpr auto TOUCH_CS_PIN = GPIO_NUM_33;
constexpr auto TOUCH_IRQ_PIN = GPIO_NUM_36;
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -1,23 +0,0 @@
#include <tactility/module.h>
extern "C" {
static error_t start() {
// Empty for now
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module cyd_2432s028rv3_module = {
.name = "cyd-2432s028rv3",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}

View File

@ -7,8 +7,9 @@
#include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <bindings/st7789.h>
#include <bindings/xpt2046_softspi.h>
/ {
compatible = "root";
@ -32,21 +33,41 @@
pin-scl = <&gpio0 22 GPIO_FLAG_NONE>;
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pin-backlight = <&gpio0 21 GPIO_FLAG_NONE>;
frequency-hz = <512>;
};
touch {
compatible = "xptek,xpt2046-softspi";
pin-mosi = <&gpio0 32 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 39 GPIO_FLAG_NONE>;
pin-sck = <&gpio0 25 GPIO_FLAG_NONE>;
pin-cs = <&gpio0 33 GPIO_FLAG_NONE>;
x-max = <240>;
y-max = <320>;
mirror-x;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
cs-gpios = <&gpio0 15 GPIO_FLAG_NONE>, // Display
<&gpio0 33 GPIO_FLAG_NONE>; // Touch
cs-gpios = <&gpio0 15 GPIO_FLAG_NONE>;
pin-mosi = <&gpio0 13 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 12 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 14 GPIO_FLAG_NONE>;
display@0 {
compatible = "display-placeholder";
};
touch@1 {
compatible = "pointer-placeholder";
display@0 {
compatible = "sitronix,st7789";
horizontal-resolution = <240>;
vertical-resolution = <320>;
pixel-clock-hz = <62500000>;
pin-dc = <&gpio0 2 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
};
@ -57,7 +78,7 @@
pin-mosi = <&gpio0 23 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 19 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 18 GPIO_FLAG_NONE>;
sdcard@0 {
compatible = "espressif,esp32-sdspi";
frequency-khz = <20000>;

View File

@ -7,12 +7,17 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD
display.size=2.8"
display.shape=rectangle
display.dpi=143
touch.calibrationSupported=true
touch.calibrationRequired=false
cdn.warningMessage=There are 3 hardware variants of this board. This build only supports board version 3.
lvgl.colorDepth=16

View File

@ -1,3 +1,5 @@
dependencies:
- Platforms/platform-esp32
- Drivers/st7789-module
- Drivers/xpt2046-softspi-module
dts: cyd,2432s028rv3.dts

View File

@ -0,0 +1,33 @@
#include <tactility/module.h>
#include <tactility/error.h>
#include <driver/gpio.h>
extern "C" {
static error_t start() {
// Set the RGB LED pins to output and turn them off (0 on, 1 off)
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT); // Red
gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT); // Green
gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT); // Blue
gpio_set_level(GPIO_NUM_4, 1); // Red
gpio_set_level(GPIO_NUM_16, 1); // Green
gpio_set_level(GPIO_NUM_17, 1); // Blue
return ERROR_NONE;
}
static error_t stop() {
return ERROR_NONE;
}
struct Module cyd_2432s028rv3_module = {
.name = "cyd-2432s028rv3",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}

View File

@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port ILI934x XPT2046SoftSPI PwmBacklight driver vfs fatfs
REQUIRES TactilityKernel driver
)

View File

@ -1,19 +0,0 @@
#include "devices/Display.h"
#include <Tactility/hal/Configuration.h>
#include <PwmBacklight.h>
static bool initBoot() {
return driver::pwmbacklight::init(LCD_BACKLIGHT_PIN);
}
static tt::hal::DeviceVector createDevices() {
return {
createDisplay(),
};
}
extern const tt::hal::Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};

View File

@ -1,51 +0,0 @@
#include "Display.h"
#include <Xpt2046SoftSpi.h>
#include <Ili934xDisplay.h>
#include <PwmBacklight.h>
#include <Tactility/hal/touch/TouchDevice.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto config = std::make_unique<Xpt2046SoftSpi::Configuration>(
TOUCH_MOSI_PIN,
TOUCH_MISO_PIN,
TOUCH_SCK_PIN,
TOUCH_CS_PIN,
LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION,
false,
true,
false
);
return std::make_shared<Xpt2046SoftSpi>(std::move(config));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
Ili934xDisplay::Configuration panel_configuration = {
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
.verticalResolution = LCD_VERTICAL_RESOLUTION,
.gapX = 0,
.gapY = 0,
.swapXY = false,
.mirrorX = true,
.mirrorY = false,
.invertColor = false,
.swapBytes = true,
.bufferSize = LCD_BUFFER_SIZE,
.touch = createTouch(),
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
.resetPin = GPIO_NUM_NC,
.rgbElementOrder = LCD_RGB_ELEMENT_ORDER_BGR
};
auto spi_configuration = std::make_shared<Ili934xDisplay::SpiConfiguration>(Ili934xDisplay::SpiConfiguration {
.spiHostDevice = LCD_SPI_HOST,
.csPin = LCD_PIN_CS,
.dcPin = LCD_PIN_DC,
.pixelClockFrequency = 40'000'000,
.transactionQueueDepth = 10
});
return std::make_shared<Ili934xDisplay>(panel_configuration, spi_configuration, true);
}

View File

@ -1,27 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <driver/gpio.h>
#include <driver/spi_common.h>
#include <memory>
// Display
constexpr auto LCD_SPI_HOST = SPI2_HOST;
constexpr auto LCD_PIN_CS = GPIO_NUM_15;
constexpr auto LCD_PIN_DC = GPIO_NUM_2;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 240;
constexpr auto LCD_VERTICAL_RESOLUTION = 320;
constexpr auto LCD_BUFFER_HEIGHT = (LCD_VERTICAL_RESOLUTION / 10);
constexpr auto LCD_BUFFER_SIZE = (LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT);
// Touch (Software SPI)
constexpr auto TOUCH_MISO_PIN = GPIO_NUM_39;
constexpr auto TOUCH_MOSI_PIN = GPIO_NUM_32;
constexpr auto TOUCH_SCK_PIN = GPIO_NUM_25;
constexpr auto TOUCH_CS_PIN = GPIO_NUM_33;
constexpr auto TOUCH_IRQ_PIN = GPIO_NUM_36;
// Backlight
constexpr auto LCD_BACKLIGHT_PIN = GPIO_NUM_21;
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -5,8 +5,9 @@
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <bindings/ili9341.h>
#include <bindings/xpt2046_softspi.h>
/ {
compatible = "root";
@ -22,21 +23,43 @@
gpio-count = <40>;
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pin-backlight = <&gpio0 21 GPIO_FLAG_NONE>;
frequency-hz = <512>;
};
touch {
compatible = "xptek,xpt2046-softspi";
pin-mosi = <&gpio0 32 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 39 GPIO_FLAG_NONE>;
pin-sck = <&gpio0 25 GPIO_FLAG_NONE>;
pin-cs = <&gpio0 33 GPIO_FLAG_NONE>;
x-max = <240>;
y-max = <320>;
mirror-x;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
cs-gpios = <&gpio0 15 GPIO_FLAG_NONE>, // Display
<&gpio0 33 GPIO_FLAG_NONE>; // Touch
cs-gpios = <&gpio0 15 GPIO_FLAG_NONE>;
pin-mosi = <&gpio0 13 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 12 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 14 GPIO_FLAG_NONE>;
display@0 {
compatible = "display-placeholder";
};
touch@1 {
compatible = "pointer-placeholder";
display@0 {
compatible = "ilitek,ili9341";
horizontal-resolution = <240>;
vertical-resolution = <320>;
mirror-x;
bgr-order;
pixel-clock-hz = <40000000>;
pin-dc = <&gpio0 2 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
};
@ -47,7 +70,7 @@
pin-mosi = <&gpio0 23 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 19 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 18 GPIO_FLAG_NONE>;
sdcard@0 {
compatible = "espressif,esp32-sdspi";
frequency-khz = <20000>;

View File

@ -7,10 +7,15 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD
display.size=2.8"
display.shape=rectangle
display.dpi=143
touch.calibrationSupported=true
touch.calibrationRequired=false
lvgl.colorDepth=16

View File

@ -1,3 +1,5 @@
dependencies:
- Platforms/platform-esp32
- Drivers/ili9341-module
- Drivers/xpt2046-softspi-module
dts: cyd,e32r28t.dts

View File

@ -13,4 +13,7 @@ display.size=2.8"
display.shape=rectangle
display.dpi=125
touch.calibrationSupported=true
touch.calibrationRequired=false
lvgl.colorDepth=16

View File

@ -13,4 +13,7 @@ display.size=2.8"
display.shape=rectangle
display.dpi=143
touch.calibrationSupported=true
touch.calibrationRequired=false
lvgl.colorDepth=16

View File

@ -15,4 +15,7 @@ display.size=3.5"
display.shape=rectangle
display.dpi=165
touch.calibrationSupported=true
touch.calibrationRequired=false
lvgl.colorDepth=16

View File

@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility ButtonControl XPT2046SoftSPI PwmBacklight EstimatedPower ST7789-i8080 driver vfs fatfs
REQUIRES TactilityKernel driver
)

View File

@ -1,22 +0,0 @@
#include "devices/Power.h"
#include "devices/Display.h"
#include <ButtonControl.h>
#include <Tactility/hal/Configuration.h>
bool initBoot();
using namespace tt::hal;
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
return {
createDisplay(),
std::make_shared<Power>(),
ButtonControl::createOneButtonControl(0)
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};

View File

@ -1,48 +0,0 @@
#include "devices/Power.h"
#include "devices/Display.h"
#include "PwmBacklight.h"
#include <Tactility/SystemEvents.h>
#include <tactility/log.h>
#include <Tactility/TactilityCore.h>
#define TAG "thmi"
static bool powerOn() {
gpio_config_t power_signal_config = {
.pin_bit_mask = (1ULL << THMI_POWERON_GPIO) | (1ULL << THMI_POWEREN_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
if (gpio_config(&power_signal_config) != ESP_OK) {
return false;
}
if (gpio_set_level(THMI_POWERON_GPIO, 1) != ESP_OK) {
return false;
}
if (gpio_set_level(THMI_POWEREN_GPIO, 1) != ESP_OK) {
return false;
}
return true;
}
bool initBoot() {
LOG_I(TAG, "Powering on the board...");
if (!powerOn()) {
LOG_E(TAG, "Failed to power on the board.");
return false;
}
if (!driver::pwmbacklight::init(DISPLAY_BL, 30000)) {
LOG_E(TAG, "Failed to initialize backlight.");
return false;
}
return true;
}

View File

@ -1,45 +0,0 @@
#include <Xpt2046SoftSpi.h>
#include <Tactility/hal/touch/TouchDevice.h>
#include "Display.h"
#include "PwmBacklight.h"
#include "St7789i8080Display.h"
static bool touchSpiInitialized = false;
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto config = std::make_unique<Xpt2046SoftSpi::Configuration>(
TOUCH_MOSI_PIN,
TOUCH_MISO_PIN,
TOUCH_SCK_PIN,
TOUCH_CS_PIN,
DISPLAY_HORIZONTAL_RESOLUTION,
DISPLAY_VERTICAL_RESOLUTION
);
return std::make_shared<Xpt2046SoftSpi>(std::move(config));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
// Create configuration
auto config = St7789i8080Display::Configuration(
DISPLAY_CS, // CS
DISPLAY_DC, // DC
DISPLAY_WR, // WR
DISPLAY_RD, // RD
{ DISPLAY_I80_D0, DISPLAY_I80_D1, DISPLAY_I80_D2, DISPLAY_I80_D3,
DISPLAY_I80_D4, DISPLAY_I80_D5, DISPLAY_I80_D6, DISPLAY_I80_D7 }, // D0..D7
DISPLAY_RST, // RST
DISPLAY_BL // BL
);
// Set resolution explicitly
config.horizontalResolution = DISPLAY_HORIZONTAL_RESOLUTION;
config.verticalResolution = DISPLAY_VERTICAL_RESOLUTION;
config.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty;
config.touch = createTouch();
config.invertColor = false;
auto display = std::make_shared<St7789i8080Display>(config);
return display;
}

View File

@ -1,35 +0,0 @@
#pragma once
#include <driver/gpio.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include "driver/spi_common.h"
class St7789i8080Display;
constexpr auto DISPLAY_CS = GPIO_NUM_6;
constexpr auto DISPLAY_DC = GPIO_NUM_7;
constexpr auto DISPLAY_WR = GPIO_NUM_8;
constexpr auto DISPLAY_RD = GPIO_NUM_NC;
constexpr auto DISPLAY_RST = GPIO_NUM_NC;
constexpr auto DISPLAY_BL = GPIO_NUM_38;
constexpr auto DISPLAY_I80_D0 = GPIO_NUM_48;
constexpr auto DISPLAY_I80_D1 = GPIO_NUM_47;
constexpr auto DISPLAY_I80_D2 = GPIO_NUM_39;
constexpr auto DISPLAY_I80_D3 = GPIO_NUM_40;
constexpr auto DISPLAY_I80_D4 = GPIO_NUM_41;
constexpr auto DISPLAY_I80_D5 = GPIO_NUM_42;
constexpr auto DISPLAY_I80_D6 = GPIO_NUM_45;
constexpr auto DISPLAY_I80_D7 = GPIO_NUM_46;
constexpr auto DISPLAY_HORIZONTAL_RESOLUTION = 240;
constexpr auto DISPLAY_VERTICAL_RESOLUTION = 320;
// Touch (XPT2046, resistive)
constexpr auto TOUCH_SPI_HOST = SPI2_HOST;
constexpr auto TOUCH_MISO_PIN = GPIO_NUM_4;
constexpr auto TOUCH_MOSI_PIN = GPIO_NUM_3;
constexpr auto TOUCH_SCK_PIN = GPIO_NUM_1;
constexpr auto TOUCH_CS_PIN = GPIO_NUM_2;
constexpr auto TOUCH_IRQ_PIN = GPIO_NUM_9;
// Factory function for registration
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -1,90 +0,0 @@
#include "Power.h"
#include <driver/adc.h>
#include <tactility/log.h>
constexpr auto* TAG = "Power";
bool Power::adcInitCalibration() {
bool calibrated = false;
esp_err_t efuse_read_result = esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP_FIT);
if (efuse_read_result == ESP_ERR_NOT_SUPPORTED) {
LOG_W(TAG, "Calibration scheme not supported, skip software calibration");
} else if (efuse_read_result == ESP_ERR_INVALID_VERSION) {
LOG_W(TAG, "eFuse not burnt, skip software calibration");
} else if (efuse_read_result == ESP_OK) {
calibrated = true;
LOG_I(TAG, "Calibration success");
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, static_cast<adc_bits_width_t>(ADC_WIDTH_BIT_DEFAULT), 0, &adcCharacteristics);
} else {
LOG_W(TAG, "eFuse read failed, skipping calibration");
}
return calibrated;
}
uint32_t Power::adcReadValue() const {
int adc_raw = adc1_get_raw(ADC1_CHANNEL_4);
LOG_D(TAG, "Raw data: %d", adc_raw);
uint32_t voltage;
if (calibrated) {
voltage = esp_adc_cal_raw_to_voltage(adc_raw, &adcCharacteristics);
LOG_D(TAG, "Calibrated data: %d mV", (int)voltage);
} else {
voltage = (adc_raw * 3300) / 4095; // fallback
LOG_D(TAG, "Estimated data: %d mV", (int)voltage);
}
return voltage;
}
bool Power::ensureInitialized() {
if (!initialized) {
if (adc1_config_width(ADC_WIDTH_BIT_12) != ESP_OK) {
LOG_E(TAG, "ADC1 config width failed");
return false;
}
if (adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11) != ESP_OK) {
LOG_E(TAG, "ADC1 config attenuation failed");
return false;
}
calibrated = adcInitCalibration();
initialized = true;
}
return true;
}
bool Power::supportsMetric(MetricType type) const {
switch (type) {
using enum MetricType;
case BatteryVoltage:
case ChargeLevel:
return true;
default:
return false;
}
}
bool Power::getMetric(MetricType type, MetricData& data) {
if (!ensureInitialized()) {
return false;
}
switch (type) {
case MetricType::BatteryVoltage:
data.valueAsUint32 = adcReadValue() * 2;
return true;
case MetricType::ChargeLevel:
data.valueAsUint8 = chargeFromAdcVoltage.estimateCharge(adcReadValue() * 2);
return true;
default:
return false;
}
}

View File

@ -1,33 +0,0 @@
#pragma once
#include <string>
#include <esp_adc_cal.h>
#include <driver/gpio.h>
#include <ChargeFromVoltage.h>
#include <Tactility/hal/power/PowerDevice.h>
constexpr auto THMI_POWEREN_GPIO = GPIO_NUM_10;
constexpr auto THMI_POWERON_GPIO = GPIO_NUM_14;
using tt::hal::power::PowerDevice;
class Power final : public PowerDevice {
ChargeFromVoltage chargeFromAdcVoltage = ChargeFromVoltage(3.3f, 4.2f);
esp_adc_cal_characteristics_t adcCharacteristics;
bool initialized = false;
bool calibrated = false;
bool adcInitCalibration();
uint32_t adcReadValue() const;
bool ensureInitialized();
public:
std::string getName() const override { return "T-HMI Power"; }
std::string getDescription() const override { return "Power measurement via ADC"; }
bool supportsMetric(MetricType type) const override;
bool getMetric(MetricType type, MetricData& data) override;
};

View File

@ -12,10 +12,15 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD
display.size=2.8"
display.shape=rectangle
display.dpi=125
touch.calibrationSupported=true
touch.calibrationRequired=false
lvgl.colorDepth=16

View File

@ -1,3 +1,6 @@
dependencies:
- Platforms/platform-esp32
- Drivers/xpt2046-softspi-module
- Drivers/st7789-i8080-module
- Drivers/button-control-module
dts: lilygo,thmi.dts

View File

@ -1,14 +1,19 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/battery_sense.h>
#include <tactility/bindings/esp32_adc_oneshot.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_sdmmc.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
#include <tactility/bindings/esp32_i8080.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <tactility/bindings/gpio_hog.h>
#include <bindings/button_control.h>
#include <bindings/st7789_i8080.h>
#include <bindings/xpt2046_softspi.h>
/ {
compatible = "root";
@ -28,23 +33,88 @@
compatible = "espressif,esp32-gpio";
gpio-count = <49>;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
cs-gpios = <&gpio0 6 GPIO_FLAG_NONE>, // Display
<&gpio0 2 GPIO_FLAG_NONE>; // Touch
pin-mosi = <&gpio0 3 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 4 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 1 GPIO_FLAG_NONE>;
// Board power-enable pins. Must be asserted before the i8080 bus/display below start, since
// devicetree devices are constructed and started earlier (kernel_init()) than the deprecated
// HAL's initBoot() used to run. gpio-hog nodes run in declaration order, so they must stay
// before the i8080 bus node.
power_on {
compatible = "tactility,gpio-hog";
pin = <&gpio0 14 GPIO_FLAG_NONE>;
};
power_en {
compatible = "tactility,gpio-hog";
pin = <&gpio0 10 GPIO_FLAG_NONE>;
};
adc0 {
compatible = "espressif,esp32-adc-oneshot";
unit-id = <ADC_UNIT_1>;
channels = <ADC_CHANNEL_4 ADC_ATTEN_DB_12 ADC_BITWIDTH_DEFAULT>;
};
// Battery voltage sits behind a 2:1 divider before reaching the ADC (see the deprecated HAL's
// old Power.cpp: adcReadValue() * 2). 3300mV matches its uncalibrated fallback reference
// voltage. Charge-percent curve is battery-sense's own fixed 3200-4200mV estimate (see
// TactilityKernel/source/drivers/battery_sense.cpp) rather than the old driver's 3300-4200mV
// ChargeFromVoltage curve - a shared kernel driver, not a per-board tunable.
battery-sense {
compatible = "battery-sense";
io-channel = <&adc0 0>;
reference-voltage-mv = <3300>;
multiplier = <2000>;
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pin-backlight = <&gpio0 38 GPIO_FLAG_NONE>;
frequency-hz = <30000>;
};
i8080_0 {
compatible = "espressif,esp32-i8080";
pin-dc = <&gpio0 7 GPIO_FLAG_NONE>;
pin-wr = <&gpio0 8 GPIO_FLAG_NONE>;
pin-d0 = <&gpio0 48 GPIO_FLAG_NONE>;
pin-d1 = <&gpio0 47 GPIO_FLAG_NONE>;
pin-d2 = <&gpio0 39 GPIO_FLAG_NONE>;
pin-d3 = <&gpio0 40 GPIO_FLAG_NONE>;
pin-d4 = <&gpio0 41 GPIO_FLAG_NONE>;
pin-d5 = <&gpio0 42 GPIO_FLAG_NONE>;
pin-d6 = <&gpio0 45 GPIO_FLAG_NONE>;
pin-d7 = <&gpio0 46 GPIO_FLAG_NONE>;
// horizontal-resolution * vertical-resolution / 10 (partial buffer) * 2 bytes/pixel
max-transfer-bytes = <15360>;
cs-gpios = <&gpio0 6 GPIO_FLAG_NONE>;
display@0 {
compatible = "display-placeholder";
compatible = "sitronix,st7789-i8080";
horizontal-resolution = <240>;
vertical-resolution = <320>;
pixel-clock-hz = <16000000>;
backlight = <&display_backlight>;
};
};
touch@1 {
compatible = "pointer-placeholder";
};
touch {
compatible = "xptek,xpt2046-softspi";
pin-mosi = <&gpio0 3 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 4 GPIO_FLAG_NONE>;
pin-sck = <&gpio0 1 GPIO_FLAG_NONE>;
pin-cs = <&gpio0 2 GPIO_FLAG_NONE>;
x-max = <240>;
y-max = <320>;
};
// One-button mode: short press = select next, long press = press/enter (matches the
// deprecated HAL's old ButtonControl::createOneButtonControl(0)).
buttons {
compatible = "tactility,button-control";
pin-primary = <&gpio0 0 GPIO_FLAG_NONE>;
};
sdmmc0 {

View File

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(st7789-i8080-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
REQUIRES TactilityKernel platform-esp32 esp_lcd driver
)

View File

@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,5 @@
# ST7789 i8080 Display Driver
A kernel driver for the `ST7789` display panel over an i8080 (parallel) bus, built on ESP-IDF's `esp_lcd` component. Child of an i8080 bus controller device (e.g. `espressif,esp32-i8080` in `Platforms/platform-esp32`) - use `st7789-module` instead for SPI-attached ST7789 panels.
License: [Apache v2.0](LICENSE-Apache-2.0.md)

View File

@ -0,0 +1,63 @@
description: Sitronix ST7789 display panel over an i8080 (parallel) bus
compatible: "sitronix,st7789-i8080"
bus: i8080
properties:
horizontal-resolution:
type: int
required: true
description: Horizontal resolution in pixels
vertical-resolution:
type: int
required: true
description: Vertical resolution in pixels
gap-x:
type: int
default: 0
description: X offset applied to all draw operations
gap-y:
type: int
default: 0
description: Y offset applied to all draw operations
swap-xy:
type: boolean
default: false
description: Swap the X and Y axes
mirror-x:
type: boolean
default: false
description: Mirror the X axis
mirror-y:
type: boolean
default: false
description: Mirror the Y axis
invert-color:
type: boolean
default: false
description: Invert the panel's color output
bgr-order:
type: boolean
default: false
description: Use BGR element order instead of RGB
pixel-clock-hz:
type: int
default: 16000000
description: i80 bus write-clock frequency in Hz
transaction-queue-depth:
type: int
default: 10
description: Size of the internal transaction queue
pin-reset:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Reset GPIO pin
reset-active-high:
type: boolean
default: false
description: Whether the reset pin is active high
backlight:
type: phandle
default: NULL
description: Optional reference to this display's backlight device

View File

@ -0,0 +1,3 @@
dependencies:
- TactilityKernel
bindings: bindings

View File

@ -0,0 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/st7789_i8080.h>
DEFINE_DEVICETREE(st7789_i8080, struct St7789I8080Config)

View File

@ -0,0 +1,34 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <tactility/device.h>
#include <tactility/drivers/gpio.h>
struct St7789I8080Config {
uint16_t horizontal_resolution;
uint16_t vertical_resolution;
int32_t gap_x;
int32_t gap_y;
bool swap_xy;
bool mirror_x;
bool mirror_y;
bool invert_color;
bool bgr_order;
uint32_t pixel_clock_hz;
uint8_t transaction_queue_depth;
struct GpioPinSpec pin_reset;
bool reset_active_high;
// Optional reference to this display's backlight device, NULL if none.
struct Device* backlight;
};
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/module.h>
#ifdef __cplusplus
extern "C" {
#endif
extern struct Module st7789_i8080_module;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,32 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver st7789_i8080_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&st7789_i8080_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&st7789_i8080_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module st7789_i8080_module = {
.name = "st7789_i8080",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
} // extern "C"

View File

@ -0,0 +1,349 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/st7789_i8080.h>
#include <st7789_i8080_module.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/display.h>
#include <tactility/drivers/esp32_i8080.h>
#include <tactility/drivers/i8080_controller.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <esp_err.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_st7789.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <cstdlib>
#define TAG "ST7789I8080"
#define GET_CONFIG(device) (static_cast<const St7789I8080Config*>((device)->config))
namespace {
// Panel bring-up commands beyond esp_lcd_panel_init()'s generic ST7789 sequence: gate/porch/power
// control and gamma curves. Kept from the deprecated HAL's St7789i8080Display driver verbatim -
// likely panel-specific tuning for the exact glass used on LilyGO T-HMI boards, not guaranteed
// redundant with esp_lcd's built-in defaults, so preserved rather than dropped.
struct LcdInitCmd {
uint8_t cmd;
uint8_t data[14];
uint8_t len;
};
constexpr LcdInitCmd ST7789_INIT_CMDS[] = {
{0x36, {0x08}, 1},
{0x3A, {0x05}, 1},
{0x20, {0}, 0},
{0xB2, {0x0B, 0x0B, 0x00, 0x33, 0x33}, 5},
{0xB7, {0x75}, 1},
{0xBB, {0x28}, 1},
{0xC0, {0x2C}, 1},
{0xC2, {0x01}, 1},
{0xC3, {0x1F}, 1},
{0xC6, {0x13}, 1},
{0xD0, {0xA7}, 1},
{0xD0, {0xA4, 0xA1}, 2},
{0xD6, {0xA1}, 1},
{0xE0, {0xF0, 0x05, 0x0A, 0x06, 0x06, 0x03, 0x2B, 0x32, 0x43, 0x36, 0x11, 0x10, 0x2B, 0x32}, 14},
{0xE1, {0xF0, 0x08, 0x0C, 0x0B, 0x09, 0x24, 0x2B, 0x22, 0x43, 0x38, 0x15, 0x16, 0x2F, 0x37}, 14},
};
} // namespace
struct St7789I8080Internal {
esp_lcd_panel_io_handle_t io_handle;
esp_lcd_panel_handle_t panel_handle;
// See Ili9341Internal's identical field in ili9341-module for why this exists: draw_bitmap()
// must block until the transfer physically completes (DisplayApi's synchronous contract).
SemaphoreHandle_t draw_done_semaphore;
};
static bool IRAM_ATTR on_color_trans_done(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void* user_ctx) {
auto* internal = static_cast<St7789I8080Internal*>(user_ctx);
BaseType_t high_task_woken = pdFALSE;
xSemaphoreGiveFromISR(internal->draw_done_semaphore, &high_task_woken);
return high_task_woken == pdTRUE;
}
static int pin_or_unused(const struct GpioPinSpec& pin) {
return pin.gpio_controller == nullptr ? -1 : static_cast<int>(pin.pin);
}
// region Driver lifecycle
static error_t start(Device* device) {
auto* parent = device_get_parent(device);
check(device_get_type(parent) == &I8080_CONTROLLER_TYPE);
esp_lcd_i80_bus_handle_t bus_handle;
if (esp32_i8080_get_bus_handle(device, &bus_handle) != ERROR_NONE) {
LOG_E(TAG, "Failed to resolve i80 bus handle");
return ERROR_RESOURCE;
}
struct GpioPinSpec cs_pin;
if (esp32_i8080_get_cs_pin(device, &cs_pin) != ERROR_NONE) {
LOG_E(TAG, "Failed to resolve CS pin");
return ERROR_RESOURCE;
}
const auto* config = GET_CONFIG(device);
auto* internal = static_cast<St7789I8080Internal*>(malloc(sizeof(St7789I8080Internal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
internal->draw_done_semaphore = xSemaphoreCreateBinary();
if (internal->draw_done_semaphore == nullptr) {
free(internal);
return ERROR_OUT_OF_MEMORY;
}
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = pin_or_unused(cs_pin),
.pclk_hz = config->pixel_clock_hz,
.trans_queue_depth = config->transaction_queue_depth,
.on_color_trans_done = on_color_trans_done,
.user_ctx = internal,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.dc_levels = {
.dc_idle_level = 0,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.flags = {
.cs_active_high = 0,
.reverse_color_bits = 0,
.swap_color_bytes = 1,
.pclk_active_neg = 0,
.pclk_idle_low = 0,
},
};
esp_err_t ret = esp_lcd_new_panel_io_i80(bus_handle, &io_config, &internal->io_handle);
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to create panel IO: %s", esp_err_to_name(ret));
vSemaphoreDelete(internal->draw_done_semaphore);
free(internal);
return ERROR_RESOURCE;
}
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = pin_or_unused(config->pin_reset),
.rgb_ele_order = config->bgr_order ? LCD_RGB_ELEMENT_ORDER_BGR : LCD_RGB_ELEMENT_ORDER_RGB,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = 16,
.flags = { .reset_active_high = config->reset_active_high },
.vendor_config = nullptr,
};
ret = esp_lcd_new_panel_st7789(internal->io_handle, &panel_config, &internal->panel_handle);
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to create panel: %s", esp_err_to_name(ret));
esp_lcd_panel_io_del(internal->io_handle);
vSemaphoreDelete(internal->draw_done_semaphore);
free(internal);
return ERROR_RESOURCE;
}
// Bring-up sequence, order matches the deprecated HAL's St7789i8080Display (proven correct on
// real T-HMI hardware). Every failure path below must clean up fully, same reasoning as
// ili9341-module's start().
bool ok =
esp_lcd_panel_reset(internal->panel_handle) == ESP_OK &&
esp_lcd_panel_init(internal->panel_handle) == ESP_OK;
if (ok) {
for (const auto& init_cmd : ST7789_INIT_CMDS) {
if (esp_lcd_panel_io_tx_param(internal->io_handle, init_cmd.cmd, init_cmd.data, init_cmd.len) != ESP_OK) {
ok = false;
break;
}
}
}
if (ok) {
int gap_x = config->swap_xy ? config->gap_y : config->gap_x;
int gap_y = config->swap_xy ? config->gap_x : config->gap_y;
ok = (gap_x == 0 && gap_y == 0) || esp_lcd_panel_set_gap(internal->panel_handle, gap_x, gap_y) == ESP_OK;
}
ok = ok && (!config->swap_xy || esp_lcd_panel_swap_xy(internal->panel_handle, true) == ESP_OK);
ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK);
ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK;
if (!ok) {
LOG_E(TAG, "Failed to bring up panel");
esp_lcd_panel_del(internal->panel_handle);
esp_lcd_panel_io_del(internal->io_handle);
vSemaphoreDelete(internal->draw_done_semaphore);
free(internal);
return ERROR_RESOURCE;
}
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<St7789I8080Internal*>(device_get_driver_data(device));
error_t result = ERROR_NONE;
if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel");
result = ERROR_RESOURCE;
}
if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel IO");
result = ERROR_RESOURCE;
}
vSemaphoreDelete(internal->draw_done_semaphore);
free(internal);
return result;
}
// endregion
// region DisplayApi
static error_t st7789_i8080_reset(Device* device) {
auto* internal = static_cast<St7789I8080Internal*>(device_get_driver_data(device));
return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t st7789_i8080_init(Device* device) {
auto* internal = static_cast<St7789I8080Internal*>(device_get_driver_data(device));
return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t st7789_i8080_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
auto* internal = static_cast<St7789I8080Internal*>(device_get_driver_data(device));
xSemaphoreTake(internal->draw_done_semaphore, 0);
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) {
return ERROR_RESOURCE;
}
xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY);
return ERROR_NONE;
}
static error_t st7789_i8080_mirror(Device* device, bool x_axis, bool y_axis) {
auto* internal = static_cast<St7789I8080Internal*>(device_get_driver_data(device));
return esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t st7789_i8080_swap_xy(Device* device, bool swap_axes) {
auto* internal = static_cast<St7789I8080Internal*>(device_get_driver_data(device));
return esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static bool st7789_i8080_get_swap_xy(Device* device) {
return GET_CONFIG(device)->swap_xy;
}
static bool st7789_i8080_get_mirror_x(Device* device) {
return GET_CONFIG(device)->mirror_x;
}
static bool st7789_i8080_get_mirror_y(Device* device) {
return GET_CONFIG(device)->mirror_y;
}
static error_t st7789_i8080_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
auto* internal = static_cast<St7789I8080Internal*>(device_get_driver_data(device));
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t st7789_i8080_invert_color(Device* device, bool invert_color_data) {
auto* internal = static_cast<St7789I8080Internal*>(device_get_driver_data(device));
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t st7789_i8080_disp_on_off(Device* device, bool on_off) {
auto* internal = static_cast<St7789I8080Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t st7789_i8080_disp_sleep(Device* device, bool sleep) {
auto* internal = static_cast<St7789I8080Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
// swap_color_bytes=1 in the panel IO config means the i80 peripheral itself byte-swaps each
// RGB565 pixel over the parallel bus, matching how ili9341-module's SPI variant needs
// RGB565_SWAPPED - same reasoning, different transport.
static enum DisplayColorFormat st7789_i8080_get_color_format(Device*) {
return DISPLAY_COLOR_FORMAT_RGB565_SWAPPED;
}
static uint16_t st7789_i8080_get_resolution_x(Device* device) {
return GET_CONFIG(device)->horizontal_resolution;
}
static uint16_t st7789_i8080_get_resolution_y(Device* device) {
return GET_CONFIG(device)->vertical_resolution;
}
static void st7789_i8080_get_frame_buffer(Device*, uint8_t, void** out_buffer) {
*out_buffer = nullptr;
}
static uint8_t st7789_i8080_get_frame_buffer_count(Device*) {
return 0;
}
static error_t st7789_i8080_get_backlight(Device* device, Device** backlight) {
auto* configured_backlight = GET_CONFIG(device)->backlight;
if (configured_backlight == nullptr) {
return ERROR_NOT_SUPPORTED;
}
*backlight = configured_backlight;
return ERROR_NONE;
}
// endregion
static const DisplayApi st7789_i8080_display_api = {
.reset = st7789_i8080_reset,
.init = st7789_i8080_init,
.draw_bitmap = st7789_i8080_draw_bitmap,
.mirror = st7789_i8080_mirror,
.swap_xy = st7789_i8080_swap_xy,
.get_swap_xy = st7789_i8080_get_swap_xy,
.get_mirror_x = st7789_i8080_get_mirror_x,
.get_mirror_y = st7789_i8080_get_mirror_y,
.set_gap = st7789_i8080_set_gap,
.invert_color = st7789_i8080_invert_color,
.disp_on_off = st7789_i8080_disp_on_off,
.disp_sleep = st7789_i8080_disp_sleep,
.get_color_format = st7789_i8080_get_color_format,
.get_resolution_x = st7789_i8080_get_resolution_x,
.get_resolution_y = st7789_i8080_get_resolution_y,
.get_frame_buffer = st7789_i8080_get_frame_buffer,
.get_frame_buffer_count = st7789_i8080_get_frame_buffer_count,
.get_backlight = st7789_i8080_get_backlight,
};
Driver st7789_i8080_driver = {
.name = "st7789_i8080",
.compatible = (const char*[]) { "sitronix,st7789-i8080", nullptr },
.start_device = start,
.stop_device = stop,
.api = &st7789_i8080_display_api,
.device_type = &DISPLAY_TYPE,
.owner = &st7789_i8080_module,
.internal = nullptr
};

View File

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(xpt2046-softspi-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
REQUIRES TactilityKernel platform-esp32 driver
)

View File

@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,9 @@
# XPT2046 Software SPI Touch Driver
A kernel driver for the `XPT2046` resistive touch controller, bit-banged over plain GPIO pins instead of a hardware SPI peripheral. Use this instead of `xpt2046-module` when no SPI host is free for touch (e.g. both `SPI2_HOST` and `SPI3_HOST` are already claimed by the display and an SD card).
No reset or interrupt pin support (matches the controller's typical wiring: polled, no dedicated IRQ line wired on most panels using it).
See https://grobotronics.com/images/datasheets/xpt2046-datasheet.pdf
License: [Apache v2.0](LICENSE-Apache-2.0.md)

View File

@ -0,0 +1,44 @@
description: >
XPT2046 resistive touch controller driven over bit-banged (software) SPI on plain GPIO
pins, for boards where no hardware SPI peripheral is available for touch (e.g. both
SPI2_HOST and SPI3_HOST are already claimed by the display and SD card).
compatible: "xptek,xpt2046-softspi"
properties:
pin-mosi:
type: phandles
required: true
description: MOSI (controller-out, peripheral-in) GPIO pin
pin-miso:
type: phandles
required: true
description: MISO (controller-in, peripheral-out) GPIO pin
pin-sck:
type: phandles
required: true
description: Clock GPIO pin
pin-cs:
type: phandles
required: true
description: Chip-select GPIO pin
x-max:
type: int
required: true
description: Maximum X coordinate reported by the controller (typically the panel's horizontal resolution)
y-max:
type: int
required: true
description: Maximum Y coordinate reported by the controller (typically the panel's vertical resolution)
swap-xy:
type: boolean
default: false
description: Swap the X and Y axes
mirror-x:
type: boolean
default: false
description: Mirror the X axis
mirror-y:
type: boolean
default: false
description: Mirror the Y axis

View File

@ -0,0 +1,3 @@
dependencies:
- TactilityKernel
bindings: bindings

View File

@ -0,0 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/xpt2046_softspi.h>
DEFINE_DEVICETREE(xpt2046_softspi, struct Xpt2046SoftSpiConfig)

View File

@ -0,0 +1,27 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <tactility/drivers/gpio.h>
struct Xpt2046SoftSpiConfig {
struct GpioPinSpec pin_mosi;
struct GpioPinSpec pin_miso;
struct GpioPinSpec pin_sck;
struct GpioPinSpec pin_cs;
uint16_t x_max;
uint16_t y_max;
bool swap_xy;
bool mirror_x;
bool mirror_y;
};
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/module.h>
#ifdef __cplusplus
extern "C" {
#endif
extern struct Module xpt2046_softspi_module;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,32 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver xpt2046_softspi_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&xpt2046_softspi_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&xpt2046_softspi_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module xpt2046_softspi_module = {
.name = "xpt2046_softspi",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
} // extern "C"

View File

@ -0,0 +1,293 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/xpt2046_softspi.h>
#include <xpt2046_softspi_module.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/pointer.h>
#include <tactility/delay.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <cstdlib>
#define TAG "XPT2046SoftSPI"
#define GET_CONFIG(device) (static_cast<const Xpt2046SoftSpiConfig*>((device)->config))
namespace {
constexpr uint8_t CMD_READ_X = 0xD0;
constexpr uint8_t CMD_READ_Y = 0x90;
constexpr int RAW_MIN_DEFAULT = 100;
constexpr int RAW_MAX_DEFAULT = 1900;
constexpr int RAW_VALID_MIN = 100;
constexpr int RAW_VALID_MAX = 3900;
constexpr int SAMPLE_COUNT = 8;
} // namespace
struct Xpt2046SoftSpiInternal {
GpioDescriptor* mosi;
GpioDescriptor* miso;
GpioDescriptor* sck;
GpioDescriptor* cs;
bool swap_xy;
bool mirror_x;
bool mirror_y;
bool touched;
uint16_t x;
uint16_t y;
};
// region Driver lifecycle
static void release_descriptors(Xpt2046SoftSpiInternal* internal) {
if (internal->mosi != nullptr) gpio_descriptor_release(internal->mosi);
if (internal->miso != nullptr) gpio_descriptor_release(internal->miso);
if (internal->sck != nullptr) gpio_descriptor_release(internal->sck);
if (internal->cs != nullptr) gpio_descriptor_release(internal->cs);
}
static error_t start(Device* device) {
const auto* config = GET_CONFIG(device);
auto* internal = static_cast<Xpt2046SoftSpiInternal*>(malloc(sizeof(Xpt2046SoftSpiInternal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
*internal = {};
internal->mosi = gpio_descriptor_acquire(config->pin_mosi.gpio_controller, config->pin_mosi.pin, GPIO_OWNER_GPIO);
internal->miso = gpio_descriptor_acquire(config->pin_miso.gpio_controller, config->pin_miso.pin, GPIO_OWNER_GPIO);
internal->sck = gpio_descriptor_acquire(config->pin_sck.gpio_controller, config->pin_sck.pin, GPIO_OWNER_GPIO);
internal->cs = gpio_descriptor_acquire(config->pin_cs.gpio_controller, config->pin_cs.pin, GPIO_OWNER_GPIO);
if (internal->mosi == nullptr || internal->miso == nullptr || internal->sck == nullptr || internal->cs == nullptr) {
LOG_E(TAG, "Failed to acquire GPIO descriptors");
release_descriptors(internal);
free(internal);
return ERROR_RESOURCE;
}
bool ok =
gpio_descriptor_set_flags(internal->mosi, GPIO_FLAG_DIRECTION_OUTPUT) == ERROR_NONE &&
gpio_descriptor_set_flags(internal->sck, GPIO_FLAG_DIRECTION_OUTPUT) == ERROR_NONE &&
gpio_descriptor_set_flags(internal->cs, GPIO_FLAG_DIRECTION_OUTPUT) == ERROR_NONE &&
gpio_descriptor_set_flags(internal->miso, GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_PULL_UP) == ERROR_NONE;
// Idle state: CS high (deselected), SCK/MOSI low.
ok = ok &&
gpio_descriptor_set_level(internal->cs, true) == ERROR_NONE &&
gpio_descriptor_set_level(internal->sck, false) == ERROR_NONE &&
gpio_descriptor_set_level(internal->mosi, false) == ERROR_NONE;
if (!ok) {
LOG_E(TAG, "Failed to configure GPIO pins");
release_descriptors(internal);
free(internal);
return ERROR_RESOURCE;
}
internal->swap_xy = config->swap_xy;
internal->mirror_x = config->mirror_x;
internal->mirror_y = config->mirror_y;
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<Xpt2046SoftSpiInternal*>(device_get_driver_data(device));
release_descriptors(internal);
free(internal);
return ERROR_NONE;
}
// endregion
// region Bit-banged SPI
// XPT2046 protocol: 8-bit command out, 12-bit conversion result back (MSB first), clocked
// manually since no hardware SPI peripheral is available for this pin set (see the binding's
// description). 1us clock half-period is well within the XPT2046's timing budget (it's rated
// for a multi-MHz SPI clock; this bit-bang loop runs orders of magnitude slower).
static int read_spi_command(Xpt2046SoftSpiInternal* internal, uint8_t command) {
int result = 0;
gpio_descriptor_set_level(internal->cs, false);
delay_micros(1);
for (int i = 7; i >= 0; i--) {
gpio_descriptor_set_level(internal->mosi, (command & (1 << i)) != 0);
gpio_descriptor_set_level(internal->sck, true);
delay_micros(1);
gpio_descriptor_set_level(internal->sck, false);
delay_micros(1);
}
for (int i = 11; i >= 0; i--) {
gpio_descriptor_set_level(internal->sck, true);
delay_micros(1);
bool level = false;
gpio_descriptor_get_level(internal->miso, &level);
if (level) {
result |= (1 << i);
}
gpio_descriptor_set_level(internal->sck, false);
delay_micros(1);
}
gpio_descriptor_set_level(internal->cs, true);
return result;
}
// endregion
// region PointerApi
static error_t xpt2046_softspi_enter_sleep(Device*) {
// No software-controlled power-down for this bit-banged variant; nothing to do.
return ERROR_NONE;
}
static error_t xpt2046_softspi_exit_sleep(Device*) {
return ERROR_NONE;
}
// Takes and averages several raw samples (rejecting out-of-range noise) then maps them onto
// [0, x_max]/[0, y_max], applying swap/mirror. Mirrors the deprecated HAL's Xpt2046SoftSpi driver.
static error_t xpt2046_softspi_read_data(Device* device, TickType_t timeout) {
(void)timeout;
auto* internal = static_cast<Xpt2046SoftSpiInternal*>(device_get_driver_data(device));
const auto* config = GET_CONFIG(device);
int total_x = 0;
int total_y = 0;
int valid_samples = 0;
for (int i = 0; i < SAMPLE_COUNT; i++) {
const int raw_x = read_spi_command(internal, CMD_READ_X);
const int raw_y = read_spi_command(internal, CMD_READ_Y);
if (raw_x > RAW_VALID_MIN && raw_x < RAW_VALID_MAX && raw_y > RAW_VALID_MIN && raw_y < RAW_VALID_MAX) {
total_x += raw_x;
total_y += raw_y;
valid_samples++;
}
delay_millis(1);
}
if (valid_samples < 3) {
internal->touched = false;
return ERROR_NONE;
}
const int raw_x = total_x / valid_samples;
const int raw_y = total_y / valid_samples;
int mapped_x = (raw_x - RAW_MIN_DEFAULT) * static_cast<int>(config->x_max) / (RAW_MAX_DEFAULT - RAW_MIN_DEFAULT);
int mapped_y = (raw_y - RAW_MIN_DEFAULT) * static_cast<int>(config->y_max) / (RAW_MAX_DEFAULT - RAW_MIN_DEFAULT);
if (internal->swap_xy) {
const int swapped = mapped_x;
mapped_x = mapped_y;
mapped_y = swapped;
}
if (internal->mirror_x) {
mapped_x = static_cast<int>(config->x_max) - mapped_x;
}
if (internal->mirror_y) {
mapped_y = static_cast<int>(config->y_max) - mapped_y;
}
if (mapped_x < 0) mapped_x = 0;
if (mapped_x > static_cast<int>(config->x_max)) mapped_x = static_cast<int>(config->x_max);
if (mapped_y < 0) mapped_y = 0;
if (mapped_y > static_cast<int>(config->y_max)) mapped_y = static_cast<int>(config->y_max);
internal->x = static_cast<uint16_t>(mapped_x);
internal->y = static_cast<uint16_t>(mapped_y);
internal->touched = true;
return ERROR_NONE;
}
static bool xpt2046_softspi_get_touched_points(Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) {
(void)strength;
auto* internal = static_cast<Xpt2046SoftSpiInternal*>(device_get_driver_data(device));
if (!internal->touched || max_point_count < 1) {
*point_count = 0;
return false;
}
*x = internal->x;
*y = internal->y;
*point_count = 1;
return true;
}
static error_t xpt2046_softspi_set_swap_xy(Device* device, bool swap) {
auto* internal = static_cast<Xpt2046SoftSpiInternal*>(device_get_driver_data(device));
internal->swap_xy = swap;
return ERROR_NONE;
}
static error_t xpt2046_softspi_get_swap_xy(Device* device, bool* swap) {
auto* internal = static_cast<Xpt2046SoftSpiInternal*>(device_get_driver_data(device));
*swap = internal->swap_xy;
return ERROR_NONE;
}
static error_t xpt2046_softspi_set_mirror_x(Device* device, bool mirror) {
auto* internal = static_cast<Xpt2046SoftSpiInternal*>(device_get_driver_data(device));
internal->mirror_x = mirror;
return ERROR_NONE;
}
static error_t xpt2046_softspi_get_mirror_x(Device* device, bool* mirror) {
auto* internal = static_cast<Xpt2046SoftSpiInternal*>(device_get_driver_data(device));
*mirror = internal->mirror_x;
return ERROR_NONE;
}
static error_t xpt2046_softspi_set_mirror_y(Device* device, bool mirror) {
auto* internal = static_cast<Xpt2046SoftSpiInternal*>(device_get_driver_data(device));
internal->mirror_y = mirror;
return ERROR_NONE;
}
static error_t xpt2046_softspi_get_mirror_y(Device* device, bool* mirror) {
auto* internal = static_cast<Xpt2046SoftSpiInternal*>(device_get_driver_data(device));
*mirror = internal->mirror_y;
return ERROR_NONE;
}
// endregion
static const PointerApi xpt2046_softspi_pointer_api = {
.enter_sleep = xpt2046_softspi_enter_sleep,
.exit_sleep = xpt2046_softspi_exit_sleep,
.read_data = xpt2046_softspi_read_data,
.get_touched_points = xpt2046_softspi_get_touched_points,
.set_swap_xy = xpt2046_softspi_set_swap_xy,
.get_swap_xy = xpt2046_softspi_get_swap_xy,
.set_mirror_x = xpt2046_softspi_set_mirror_x,
.get_mirror_x = xpt2046_softspi_get_mirror_x,
.set_mirror_y = xpt2046_softspi_set_mirror_y,
.get_mirror_y = xpt2046_softspi_get_mirror_y,
};
Driver xpt2046_softspi_driver = {
.name = "xpt2046_softspi",
.compatible = (const char*[]) { "xptek,xpt2046-softspi", nullptr },
.start_device = start,
.stop_device = stop,
.api = &xpt2046_softspi_pointer_api,
.device_type = &POINTER_TYPE,
.owner = &xpt2046_softspi_module,
.internal = nullptr
};

View File

@ -6,7 +6,7 @@ idf_component_register(
SRCS ${SOURCES}
INCLUDE_DIRS "include/"
PRIV_INCLUDE_DIRS "private/"
REQUIRES TactilityKernel driver esp_adc esp_driver_i2c vfs fatfs esp_wifi esp_netif esp_event
REQUIRES TactilityKernel driver esp_adc esp_driver_i2c esp_lcd vfs fatfs esp_wifi esp_netif esp_event
)
idf_component_optional_requires(PRIVATE bt usb espressif__usb_host_hid espressif__usb_host_msc)

View File

@ -0,0 +1,64 @@
description: ESP32 i8080 (8080-series parallel) LCD bus controller
include: ["i8080-controller.yaml"]
compatible: "espressif,esp32-i8080"
properties:
pin-dc:
type: phandles
required: true
description: Data/Command GPIO pin
pin-wr:
type: phandles
required: true
description: Write-strobe GPIO pin
pin-rd:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Read-strobe GPIO pin. Optional - driven high once and left alone if provided, since this bus only ever writes.
pin-d0:
type: phandles
required: true
description: Data bus bit 0 GPIO pin
pin-d1:
type: phandles
required: true
description: Data bus bit 1 GPIO pin
pin-d2:
type: phandles
required: true
description: Data bus bit 2 GPIO pin
pin-d3:
type: phandles
required: true
description: Data bus bit 3 GPIO pin
pin-d4:
type: phandles
required: true
description: Data bus bit 4 GPIO pin
pin-d5:
type: phandles
required: true
description: Data bus bit 5 GPIO pin
pin-d6:
type: phandles
required: true
description: Data bus bit 6 GPIO pin
pin-d7:
type: phandles
required: true
description: Data bus bit 7 GPIO pin
max-transfer-bytes:
type: int
required: true
description: |
Maximum size in bytes of a single transfer over this bus. Sized for the attached
panel's buffer (e.g. horizontal-resolution * vertical-resolution / N * bytes-per-pixel
for an N-th sized partial buffer) - there is no bus-level default since it's entirely
dependent on what's attached.
cs-gpios:
type: phandle-array
element-type: "struct GpioPinSpec"
default: "{ }"
description: Null-terminated array of chip select GPIO pin specs for peripherals on this bus

View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <tactility/drivers/esp32_i8080.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(esp32_i8080, struct Esp32I8080Config)
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,55 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/drivers/i8080_controller.h>
#include <tactility/drivers/gpio.h>
#include <esp_lcd_io_i80.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Esp32I8080Config {
struct GpioPinSpec pin_dc;
struct GpioPinSpec pin_wr;
struct GpioPinSpec pin_rd;
struct GpioPinSpec pin_d0;
struct GpioPinSpec pin_d1;
struct GpioPinSpec pin_d2;
struct GpioPinSpec pin_d3;
struct GpioPinSpec pin_d4;
struct GpioPinSpec pin_d5;
struct GpioPinSpec pin_d6;
struct GpioPinSpec pin_d7;
int max_transfer_bytes;
/** Array of chip select GPIO pin specs */
struct GpioPinSpec* cs_gpios;
/** The item count of cs_gpios */
uint8_t cs_gpios_count;
};
/**
* @brief Get the CS pin spec for a child device on this i8080 bus.
* Uses the child device's address as index into the parent's cs_gpios array.
* @param[in] child_device a child device of an i8080 controller
* @param[out] out_pin the GPIO pin spec for the CS pin
* @retval ERROR_NONE on success
* @retval ERROR_INVALID_STATE if the parent is not an i8080 controller
* @retval ERROR_OUT_OF_RANGE if the device address exceeds the cs_gpios array
*/
error_t esp32_i8080_get_cs_pin(struct Device* child_device, struct GpioPinSpec* out_pin);
/**
* @brief Get the i80 bus handle for a child device's i8080 controller parent.
* Only valid after the controller device has started.
* @param[in] child_device a child device of an i8080 controller
* @param[out] out_handle the bus handle
* @retval ERROR_NONE on success
* @retval ERROR_INVALID_STATE if the parent is not an i8080 controller, or hasn't started
*/
error_t esp32_i8080_get_bus_handle(struct Device* child_device, esp_lcd_i80_bus_handle_t* out_handle);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,185 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/device.h>
#include <tactility/drivers/esp32_i8080.h>
#include <tactility/log.h>
#include "tactility/drivers/gpio_descriptor.h"
#include <tactility/drivers/esp32_gpio_helpers.h>
#include <tactility/drivers/gpio_controller.h>
#include <esp_lcd_panel_io.h>
#include <new>
#include <vector>
#define TAG "esp32_i8080"
#define GET_CONFIG(device) ((const struct Esp32I8080Config*)device->config)
#define GET_DATA(device) ((struct Esp32I8080Internal*)device_get_driver_data(device))
extern "C" {
struct Esp32I8080Internal {
esp_lcd_i80_bus_handle_t bus_handle = nullptr;
GpioDescriptor* dc_descriptor = nullptr;
GpioDescriptor* wr_descriptor = nullptr;
GpioDescriptor* rd_descriptor = nullptr;
GpioDescriptor* d0_descriptor = nullptr;
GpioDescriptor* d1_descriptor = nullptr;
GpioDescriptor* d2_descriptor = nullptr;
GpioDescriptor* d3_descriptor = nullptr;
GpioDescriptor* d4_descriptor = nullptr;
GpioDescriptor* d5_descriptor = nullptr;
GpioDescriptor* d6_descriptor = nullptr;
GpioDescriptor* d7_descriptor = nullptr;
std::vector<GpioDescriptor*> cs_descriptors;
void cleanup_pins() {
release_pin(&dc_descriptor);
release_pin(&wr_descriptor);
release_pin(&rd_descriptor);
release_pin(&d0_descriptor);
release_pin(&d1_descriptor);
release_pin(&d2_descriptor);
release_pin(&d3_descriptor);
release_pin(&d4_descriptor);
release_pin(&d5_descriptor);
release_pin(&d6_descriptor);
release_pin(&d7_descriptor);
for (auto*& desc : cs_descriptors) {
release_pin(&desc);
}
cs_descriptors.clear();
}
};
static error_t start(Device* device) {
LOG_I(TAG, "start %s", device->name);
auto* data = new (std::nothrow) Esp32I8080Internal();
if (data == nullptr) return ERROR_OUT_OF_MEMORY;
device_set_driver_data(device, data);
const auto* config = GET_CONFIG(device);
bool pins_ok =
acquire_pin_or_set_null(config->pin_dc, &data->dc_descriptor) &&
acquire_pin_or_set_null(config->pin_wr, &data->wr_descriptor) &&
acquire_pin_or_set_null(config->pin_rd, &data->rd_descriptor) &&
acquire_pin_or_set_null(config->pin_d0, &data->d0_descriptor) &&
acquire_pin_or_set_null(config->pin_d1, &data->d1_descriptor) &&
acquire_pin_or_set_null(config->pin_d2, &data->d2_descriptor) &&
acquire_pin_or_set_null(config->pin_d3, &data->d3_descriptor) &&
acquire_pin_or_set_null(config->pin_d4, &data->d4_descriptor) &&
acquire_pin_or_set_null(config->pin_d5, &data->d5_descriptor) &&
acquire_pin_or_set_null(config->pin_d6, &data->d6_descriptor) &&
acquire_pin_or_set_null(config->pin_d7, &data->d7_descriptor);
if (!pins_ok) {
LOG_E(TAG, "Failed to acquire required i8080 pins");
data->cleanup_pins();
device_set_driver_data(device, nullptr);
delete data;
return ERROR_RESOURCE;
}
// RD is never toggled by the i80 LCD peripheral (write-only bus); drive it high once so the
// panel doesn't see spurious read strobes.
if (data->rd_descriptor != nullptr) {
gpio_descriptor_set_flags(data->rd_descriptor, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_level(data->rd_descriptor, true);
}
esp_lcd_i80_bus_config_t bus_cfg = {
.dc_gpio_num = get_native_pin(data->dc_descriptor),
.wr_gpio_num = get_native_pin(data->wr_descriptor),
.clk_src = LCD_CLK_SRC_DEFAULT,
.data_gpio_nums = {
get_native_pin(data->d0_descriptor),
get_native_pin(data->d1_descriptor),
get_native_pin(data->d2_descriptor),
get_native_pin(data->d3_descriptor),
get_native_pin(data->d4_descriptor),
get_native_pin(data->d5_descriptor),
get_native_pin(data->d6_descriptor),
get_native_pin(data->d7_descriptor),
},
.bus_width = 8,
.max_transfer_bytes = static_cast<size_t>(config->max_transfer_bytes),
.psram_trans_align = 64,
.sram_trans_align = 4
};
esp_err_t ret = esp_lcd_new_i80_bus(&bus_cfg, &data->bus_handle);
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to create I80 bus: %s", esp_err_to_name(ret));
data->cleanup_pins();
device_set_driver_data(device, nullptr);
delete data;
return ERROR_RESOURCE;
}
// Acquire and deselect all CS pins (drive high)
for (uint8_t i = 0; i < config->cs_gpios_count; i++) {
const GpioPinSpec* cs = &config->cs_gpios[i];
if (cs->gpio_controller == nullptr) continue;
GpioDescriptor* desc = gpio_descriptor_acquire(cs->gpio_controller, cs->pin, GPIO_OWNER_GPIO);
if (desc != nullptr) {
gpio_descriptor_set_flags(desc, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_level(desc, true);
data->cs_descriptors.push_back(desc);
}
}
return ERROR_NONE;
}
static error_t stop(Device* device) {
LOG_I(TAG, "stop %s", device->name);
auto* data = GET_DATA(device);
if (data->bus_handle != nullptr) {
esp_lcd_del_i80_bus(data->bus_handle);
}
data->cleanup_pins();
device_set_driver_data(device, nullptr);
delete data;
return ERROR_NONE;
}
error_t esp32_i8080_get_cs_pin(Device* child_device, GpioPinSpec* out_pin) {
auto* parent = device_get_parent(child_device);
if (parent == nullptr || device_get_type(parent) != &I8080_CONTROLLER_TYPE) return ERROR_INVALID_STATE;
const auto* config = GET_CONFIG(parent);
int32_t index = child_device->address;
if (index < 0 || index >= config->cs_gpios_count) return ERROR_OUT_OF_RANGE;
*out_pin = config->cs_gpios[index];
return ERROR_NONE;
}
error_t esp32_i8080_get_bus_handle(Device* child_device, esp_lcd_i80_bus_handle_t* out_handle) {
auto* parent = device_get_parent(child_device);
if (parent == nullptr || device_get_type(parent) != &I8080_CONTROLLER_TYPE) return ERROR_INVALID_STATE;
auto* data = GET_DATA(parent);
if (data == nullptr || data->bus_handle == nullptr) return ERROR_INVALID_STATE;
*out_handle = data->bus_handle;
return ERROR_NONE;
}
extern struct Module platform_esp32_module;
Driver esp32_i8080_driver = {
.name = "esp32_i8080",
.compatible = (const char*[]) { "espressif,esp32-i8080", nullptr },
.start_device = start,
.stop_device = stop,
.api = nullptr,
.device_type = &I8080_CONTROLLER_TYPE,
.owner = &platform_esp32_module,
.internal = nullptr
};
} // extern "C"

View File

@ -16,6 +16,9 @@ extern Driver esp32_gpio_driver;
extern Driver esp32_i2c_driver;
extern Driver esp32_i2c_master_driver;
extern Driver esp32_i2s_driver;
#if SOC_LCD_I80_SUPPORTED
extern Driver esp32_i8080_driver;
#endif
extern Driver esp32_ledc_backlight_driver;
#if SOC_SDMMC_HOST_SUPPORTED
extern Driver esp32_sdmmc_driver;
@ -49,6 +52,9 @@ static error_t start() {
check(driver_construct_add(&esp32_i2c_driver) == ERROR_NONE);
check(driver_construct_add(&esp32_i2c_master_driver) == ERROR_NONE);
check(driver_construct_add(&esp32_i2s_driver) == ERROR_NONE);
#if SOC_LCD_I80_SUPPORTED
check(driver_construct_add(&esp32_i8080_driver) == ERROR_NONE);
#endif
check(driver_construct_add(&esp32_ledc_backlight_driver) == ERROR_NONE);
#if SOC_SDMMC_HOST_SUPPORTED
check(driver_construct_add(&esp32_sdmmc_driver) == ERROR_NONE);
@ -100,6 +106,9 @@ static error_t stop() {
check(driver_remove_destruct(&esp32_i2c_driver) == ERROR_NONE);
check(driver_remove_destruct(&esp32_i2c_master_driver) == ERROR_NONE);
check(driver_remove_destruct(&esp32_i2s_driver) == ERROR_NONE);
#if SOC_LCD_I80_SUPPORTED
check(driver_remove_destruct(&esp32_i8080_driver) == ERROR_NONE);
#endif
check(driver_remove_destruct(&esp32_ledc_backlight_driver) == ERROR_NONE);
#if SOC_SDMMC_HOST_SUPPORTED
check(driver_remove_destruct(&esp32_sdmmc_driver) == ERROR_NONE);

View File

@ -18,6 +18,10 @@
#include <lvgl.h>
#include <tactility/lvgl_pointer.h>
#ifdef ESP_PLATFORM
#include <sdkconfig.h>
#endif
namespace tt::app::kerneldisplay {
constexpr auto* TAG = "KernelDisplay";
@ -100,9 +104,11 @@ class KernelDisplayApp final : public App {
}
}
#if defined(CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED)
static void onCalibrateTouchClicked(lv_event_t*) {
app::touchcalibration::start();
}
#endif
static void onScreensaverChanged(lv_event_t* event) {
auto* app = static_cast<KernelDisplayApp*>(lv_event_get_user_data(event));
@ -257,6 +263,7 @@ public:
}
}
#if defined(CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED)
if (lvgl_pointer_get_default() != nullptr) {
auto* calibrate_wrapper = lv_obj_create(main_wrapper);
lv_obj_set_size(calibrate_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
@ -275,6 +282,7 @@ public:
lv_label_set_text(calibrate_button_label, "Calibrate");
lv_obj_center(calibrate_button_label);
}
#endif
}
void onHide(AppContext& app) override {

View File

@ -0,0 +1 @@
description: i8080 (8080-series parallel) LCD bus controller

View File

@ -0,0 +1,19 @@
description: >
Claims a GPIO pin and drives it to a fixed level for as long as the module is running, with
no further runtime behavior. Useful for board-level power-enable pins that must be asserted
before other devicetree devices try to use hardware gated by that pin - all devicetree
devices are constructed and started during kernel_init(), which runs before the deprecated
HAL's initBoot() hook. Declare a gpio-hog node before the dependent device(s) in the .dts
source so it runs first.
compatible: "tactility,gpio-hog"
properties:
pin:
type: phandles
required: true
description: The GPIO pin to hog
output-high:
type: boolean
default: true
description: Level to drive the pin to (true = high, false = low)

View File

@ -0,0 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <tactility/drivers/gpio_hog.h>
DEFINE_DEVICETREE(gpio_hog, struct GpioHogConfig)

View File

@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <tactility/drivers/gpio.h>
struct GpioHogConfig {
struct GpioPinSpec pin;
bool output_high;
};
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Generic device type for i8080 (8080-series parallel) LCD bus controllers.
*
* Unlike SPI_CONTROLLER_TYPE, there is no locking API: an i80 bus only ever has one
* consumer (a single display panel) in every known board, so no arbitration is needed.
*/
extern const struct DeviceType I8080_CONTROLLER_TYPE;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,55 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/drivers/gpio_hog.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <tactility/module.h>
#define TAG "GpioHog"
#define GET_CONFIG(device) (static_cast<const GpioHogConfig*>((device)->config))
namespace {
const struct DeviceType GPIO_HOG_TYPE { .name = "gpio_hog" };
}
static error_t start(Device* device) {
const auto* config = GET_CONFIG(device);
auto* descriptor = gpio_descriptor_acquire(config->pin.gpio_controller, config->pin.pin, GPIO_OWNER_HOG);
if (descriptor == nullptr) {
LOG_E(TAG, "Failed to acquire GPIO descriptor");
return ERROR_RESOURCE;
}
if (gpio_descriptor_set_flags(descriptor, GPIO_FLAG_DIRECTION_OUTPUT) != ERROR_NONE ||
gpio_descriptor_set_level(descriptor, config->output_high) != ERROR_NONE) {
LOG_E(TAG, "Failed to configure hogged pin");
gpio_descriptor_release(descriptor);
return ERROR_RESOURCE;
}
device_set_driver_data(device, descriptor);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* descriptor = static_cast<GpioDescriptor*>(device_get_driver_data(device));
gpio_descriptor_release(descriptor);
return ERROR_NONE;
}
extern Module root_module;
Driver gpio_hog_driver = {
.name = "gpio_hog",
.compatible = (const char*[]) { "tactility,gpio-hog", nullptr },
.start_device = start,
.stop_device = stop,
.api = nullptr,
.device_type = &GPIO_HOG_TYPE,
.owner = &root_module,
.internal = nullptr
};

View File

@ -0,0 +1,11 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/drivers/i8080_controller.h>
#include <tactility/device.h>
extern "C" {
const DeviceType I8080_CONTROLLER_TYPE {
.name = "i8080-controller"
};
}

View File

@ -24,6 +24,8 @@ static error_t start() {
if (driver_construct_add(&battery_sense_driver) != ERROR_NONE) return ERROR_RESOURCE;
extern Driver battery_sense_power_supply_driver;
if (driver_construct_add(&battery_sense_power_supply_driver) != ERROR_NONE) return ERROR_RESOURCE;
extern Driver gpio_hog_driver;
if (driver_construct_add(&gpio_hog_driver) != ERROR_NONE) return ERROR_RESOURCE;
return ERROR_NONE;
}