mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-20 17:05:06 +00:00
Compare commits
2 Commits
08eac48e64
...
29e80cfd65
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29e80cfd65 | ||
|
|
429125734a |
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct Module btt_panda_touch_module = {
|
||||
.name = "btt-panda-touch",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
Module btt_panda_touch_module = {
|
||||
.name = "btt-panda-touch"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module cyd_2432s024c_module = {
|
||||
.name = "cyd-2432s024c",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct Module cyd_2432s024r_module = {
|
||||
.name = "cyd-2432s024r",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
Module cyd_2432s024r_module = {
|
||||
.name = "cyd-2432s024r"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct Module cyd_2432s028r_module = {
|
||||
.name = "cyd-2432s028r",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
Module cyd_2432s028r_module = {
|
||||
.name = "cyd-2432s028r"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct Module cyd_2432s028rv3_module = {
|
||||
.name = "cyd-2432s028rv3",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
Module cyd_2432s028rv3_module = {
|
||||
.name = "cyd-2432s028rv3"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module cyd_2432s032c_module = {
|
||||
.name = "cyd-2432s032c",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "cyd-2432s032c"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module cyd_3248s035c_module = {
|
||||
.name = "cyd-3248s035c",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "cyd-3248s035c"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module cyd_4848s040c_module = {
|
||||
.name = "cyd-4848s040c",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "cyd-4848s040c"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module cyd_8048s043c_module = {
|
||||
.name = "cyd-8048s043c",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "cyd-8048s043c"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module cyd_e32r28t_module = {
|
||||
.name = "cyd-e32r28t",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "cyd-e32r28t"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module cyd_e32r32p_module = {
|
||||
.name = "cyd-e32r32p",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "cyd-e32r32p"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module elecrow_crowpanel_advance_28_module = {
|
||||
.name = "elecrow-crowpanel-advance-28",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr
|
||||
.name = "elecrow-crowpanel-advance-28"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module elecrow_crowpanel_advance_35_module = {
|
||||
.name = "elecrow-crowpanel-advance-35",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "elecrow-crowpanel-advance-35"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module elecrow_crowpanel_advance_50_module = {
|
||||
.name = "elecrow-crowpanel-advance-50",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "elecrow-crowpanel-advance-50"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module elecrow_crowpanel_basic_28_module = {
|
||||
.name = "elecrow-crowpanel-basic-28",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "elecrow-crowpanel-basic-28"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module elecrow_crowpanel_basic_35_module = {
|
||||
.name = "elecrow-crowpanel-basic-35",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "elecrow-crowpanel-basic-35"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module elecrow_crowpanel_basic_50_module = {
|
||||
.name = "elecrow-crowpanel-basic-50",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "elecrow-crowpanel-basic-50"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module es3c28p_module = {
|
||||
.name = "es3c28p",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "es3c28p"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module generic_esp32_module = {
|
||||
.name = "generic-esp32",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "generic-esp32"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module generic_esp32c6_module = {
|
||||
.name = "generic-esp32c6",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "generic-esp32c6"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module generic_esp32p4_module = {
|
||||
.name = "generic-esp32p4",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "generic-esp32p4"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module generic_esp32s3_module = {
|
||||
.name = "generic-esp32s3",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "generic-esp32s3"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module guition_jc1060p470ciwy_module = {
|
||||
.name = "guition-jc1060p470ciwy",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "guition-jc1060p470ciwy"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module guition_jc2432w328c_module = {
|
||||
.name = "guition-jc2432w328c",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "guition-jc2432w328c"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module guition_jc8048w550c_module = {
|
||||
.name = "guition-jc8048w550c",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "guition-jc8048w550c"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module heltec_wifi_lora_32_v3_module = {
|
||||
.name = "heltec-wifi-lora-32-v3",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "heltec-wifi-lora-32-v3"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES source/*.c*)
|
||||
idf_component_register(
|
||||
SRCS ${SOURCE_FILES}
|
||||
INCLUDE_DIRS "source"
|
||||
REQUIRES Tactility GDEQ031T10 driver
|
||||
REQUIRES TactilityKernel
|
||||
)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
general.vendor=LilyGO
|
||||
general.name=T-Deck Max
|
||||
# SD card support is not yet working; remove once it is functional.
|
||||
# SD card support is not yet working, display driver is untested since refactoring; remove once it is functional.
|
||||
general.incubating=true
|
||||
|
||||
apps.launcherAppId=Launcher
|
||||
@ -18,14 +18,17 @@ hardware.bluetooth=true
|
||||
# Internal until the SD card is verified working on this board; switch to SD then.
|
||||
storage.userDataLocation=Internal
|
||||
|
||||
dependencies.useDeprecatedHal=false
|
||||
|
||||
display.size=3.1"
|
||||
display.shape=rectangle
|
||||
display.dpi=128
|
||||
|
||||
# The GDEQ031T10 e-paper is monochrome, but its driver renders via
|
||||
# LV_COLOR_FORMAT_I1 on its own display. The global LVGL color depth must stay
|
||||
# at 16: esp_lvgl_port refuses to compile with LV_COLOR_DEPTH_1 set.
|
||||
lvgl.colorDepth=16
|
||||
lvgl.colorDepth=8
|
||||
# Monochrome theme: the default colour theme renders accent-coloured UI that
|
||||
# thresholds to invisible on a 1bpp panel (enables CONFIG_LV_USE_THEME_MONO).
|
||||
lvgl.theme=Mono
|
||||
|
||||
cdn.warningMessage=Display not reliably working. Use at your own risk.
|
||||
|
||||
sdkconfig.CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=0
|
||||
|
||||
@ -2,6 +2,7 @@ dependencies:
|
||||
- Platforms/platform-esp32
|
||||
- Drivers/xl9555-module
|
||||
- Drivers/cst66xx-module
|
||||
- Drivers/gdeq031t10-module
|
||||
- Drivers/tca8418-module
|
||||
- Drivers/sy6970-module
|
||||
- Drivers/bq27220-module
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#include <tactility/bindings/pwm_backlight.h>
|
||||
#include <bindings/xl9555.h>
|
||||
#include <bindings/cst66xx.h>
|
||||
#include <bindings/gdeq031t10.h>
|
||||
#include <bindings/tca8418.h>
|
||||
#include <bindings/sy6970.h>
|
||||
#include <bindings/bq27220.h>
|
||||
@ -143,6 +144,17 @@
|
||||
// exceeds the default ~4 KB transfer limit. Raise the bus limit to fit it.
|
||||
max-transfer-size = <65536>;
|
||||
|
||||
display@0 {
|
||||
compatible = "gooddisplay,gdeq031t10";
|
||||
pin-dc = <&gpio0 35 GPIO_FLAG_NONE>;
|
||||
pin-reset = <&gpio0 9 GPIO_FLAG_NONE>;
|
||||
pin-busy = <&gpio0 37 GPIO_FLAG_NONE>;
|
||||
clock-speed-hz = <10000000>;
|
||||
// Default to a fast (~1s) refresh for the automatic ghost-clears so the
|
||||
// full-screen flash they cause is as short as possible.
|
||||
refresh-mode = <GDEQ031T10_REFRESH_FAST>;
|
||||
};
|
||||
|
||||
sdcard@1 {
|
||||
compatible = "espressif,esp32-sdspi";
|
||||
status = "disabled";
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
#include "devices/Display.h"
|
||||
|
||||
#include <Tactility/hal/Configuration.h>
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/esp32_spi.h>
|
||||
|
||||
using namespace tt::hal;
|
||||
|
||||
static bool initBoot() {
|
||||
// The LoRa and SD chip-selects share the EPD's SPI bus but aren't wired up
|
||||
// yet. spi0's start() already drives every cs-gpio high during kernel init;
|
||||
// re-assert deselection before the EPD driver first transacts, as a cheap
|
||||
// guard against either chip latching stray EPD command bytes (same pattern
|
||||
// as the esp32_sdspi mount path).
|
||||
auto* spi0 = device_find_by_name("spi0");
|
||||
check(spi0 != nullptr);
|
||||
esp32_spi_deselect_all_cs(spi0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static DeviceVector createDevices() {
|
||||
// Touch, keyboard and battery/charging are kernel drivers (cst66xx, tca8418, sy6970,
|
||||
// bq27220 - see the .dts), started independently of this HAL layer. Only the EPD
|
||||
// display remains on the deprecated HAL.
|
||||
return DeviceVector {
|
||||
createDisplay()
|
||||
};
|
||||
}
|
||||
|
||||
extern const Configuration hardwareConfiguration = {
|
||||
.initBoot = initBoot,
|
||||
.createDevices = createDevices
|
||||
};
|
||||
@ -1,29 +0,0 @@
|
||||
#include "Display.h"
|
||||
|
||||
#include <Gdeq031t10Display.h>
|
||||
|
||||
// Pins from Xinyuan-LilyGO/T-Deck-MAX's lib/TDeckMaxBoard/src/TDeckMaxBoard.h and docs/pinmap.md.
|
||||
constexpr auto EPD_SPI_HOST = SPI2_HOST;
|
||||
constexpr auto EPD_PIN_CS = GPIO_NUM_34;
|
||||
constexpr auto EPD_PIN_DC = GPIO_NUM_35;
|
||||
constexpr auto EPD_PIN_RST = GPIO_NUM_9;
|
||||
constexpr auto EPD_PIN_BUSY = GPIO_NUM_37;
|
||||
|
||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
||||
// Touch is a kernel driver (hynitron,cst66xx - see the .dts), discovered and fed to LVGL
|
||||
// independently of this display, so no touch device is passed into the configuration here.
|
||||
auto configuration = std::make_unique<Gdeq031t10Display::Configuration>(
|
||||
EPD_SPI_HOST,
|
||||
EPD_PIN_CS,
|
||||
EPD_PIN_DC,
|
||||
EPD_PIN_RST,
|
||||
EPD_PIN_BUSY,
|
||||
nullptr,
|
||||
10'000'000,
|
||||
// Default to a fast (~1s) refresh for the automatic ghost-clears so the
|
||||
// full-screen flash they cause is as short as possible.
|
||||
Gdeq031t10Display::RefreshMode::Fast
|
||||
);
|
||||
|
||||
return std::make_shared<Gdeq031t10Display>(std::move(configuration));
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/hal/display/DisplayDevice.h>
|
||||
|
||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
||||
@ -2,22 +2,8 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
struct Module lilygo_tdeck_max_module = {
|
||||
.name = "lilygo-tdeck-max",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
Module lilygo_tdeck_max_module = {
|
||||
.name = "lilygo-tdeck-max"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
||||
hardware.esptoolFlashFreq=120M
|
||||
hardware.bluetooth=true
|
||||
|
||||
dependencies.useDeprecatedHal=false
|
||||
|
||||
storage.userDataLocation=SD
|
||||
|
||||
display.size=2.8"
|
||||
|
||||
@ -19,9 +19,6 @@
|
||||
|
||||
constexpr auto* TAG = "tdeck-plus";
|
||||
|
||||
// Legacy placeholder (required until legacy HAL is cleaned up everywhere)
|
||||
extern const tt::hal::Configuration hardwareConfiguration = {};
|
||||
|
||||
extern "C" {
|
||||
|
||||
void subscribe_events() {
|
||||
|
||||
7
Devices/lilygo-tdeck-pro/CMakeLists.txt
Normal file
7
Devices/lilygo-tdeck-pro/CMakeLists.txt
Normal file
@ -0,0 +1,7 @@
|
||||
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${SOURCE_FILES}
|
||||
INCLUDE_DIRS "source"
|
||||
REQUIRES TactilityKernel
|
||||
)
|
||||
33
Devices/lilygo-tdeck-pro/device.properties
Normal file
33
Devices/lilygo-tdeck-pro/device.properties
Normal file
@ -0,0 +1,33 @@
|
||||
general.vendor=LilyGO
|
||||
general.name=T-Deck Pro
|
||||
# The display driver is not reliable yet
|
||||
general.incubating=true
|
||||
|
||||
apps.launcherAppId=Launcher
|
||||
|
||||
hardware.target=ESP32S3
|
||||
hardware.flashSize=16MB
|
||||
hardware.flashMode=DIO
|
||||
hardware.spiRam=true
|
||||
hardware.spiRamMode=AUTO
|
||||
hardware.spiRamSpeed=80M
|
||||
hardware.tinyUsb=true
|
||||
hardware.esptoolFlashFreq=80M
|
||||
hardware.bluetooth=true
|
||||
|
||||
storage.userDataLocation=SD
|
||||
|
||||
dependencies.useDeprecatedHal=false
|
||||
|
||||
display.size=3.1"
|
||||
display.shape=rectangle
|
||||
display.dpi=128
|
||||
|
||||
lvgl.colorDepth=8
|
||||
# Monochrome theme: the default colour theme renders accent-coloured UI that
|
||||
# thresholds to invisible on a 1bpp panel (enables CONFIG_LV_USE_THEME_MONO).
|
||||
lvgl.theme=Mono
|
||||
|
||||
cdn.warningMessage=Only for hardware revision 1.0! Display not reliably working. Use at your own risk.
|
||||
|
||||
sdkconfig.CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=0
|
||||
8
Devices/lilygo-tdeck-pro/devicetree.yaml
Normal file
8
Devices/lilygo-tdeck-pro/devicetree.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
dependencies:
|
||||
- Platforms/platform-esp32
|
||||
- Drivers/cst328-module
|
||||
- Drivers/gdeq031t10-module
|
||||
- Drivers/tca8418-module
|
||||
- Drivers/sy6970-module
|
||||
- Drivers/bq25896-module
|
||||
dts: lilygo,tdeck-pro.dts
|
||||
138
Devices/lilygo-tdeck-pro/lilygo,tdeck-pro.dts
Normal file
138
Devices/lilygo-tdeck-pro/lilygo,tdeck-pro.dts
Normal file
@ -0,0 +1,138 @@
|
||||
/dts-v1/;
|
||||
|
||||
#include <tactility/bindings/root.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_master.h>
|
||||
#include <tactility/bindings/esp32_spi.h>
|
||||
#include <tactility/bindings/esp32_sdspi.h>
|
||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||
#include <tactility/bindings/pwm_backlight.h>
|
||||
#include <bindings/cst328.h>
|
||||
#include <bindings/gdeq031t10.h>
|
||||
#include <bindings/tca8418.h>
|
||||
#include <bindings/bq25896.h>
|
||||
|
||||
// Reference for V1.0 (not V1.1!): https://github.com/Xinyuan-LilyGO/T-Deck-Pro/tree/HD-V1-250326
|
||||
// lib/TDeckMaxBoard/src/TDeckMaxBoard.h, docs/pinmap.md
|
||||
/ {
|
||||
compatible = "root";
|
||||
model = "LilyGO T-Deck Pro";
|
||||
|
||||
wifi0 {
|
||||
compatible = "espressif,esp32-wifi-pinned";
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
ble0 {
|
||||
compatible = "espressif,esp32-ble";
|
||||
};
|
||||
|
||||
gpio0 {
|
||||
compatible = "espressif,esp32-gpio";
|
||||
gpio-count = <49>;
|
||||
};
|
||||
|
||||
i2c0 {
|
||||
compatible = "espressif,esp32-i2c-master";
|
||||
port = <I2C_NUM_0>;
|
||||
clock-frequency = <100000>;
|
||||
pin-sda = <&gpio0 13 GPIO_FLAG_NONE>;
|
||||
pin-scl = <&gpio0 14 GPIO_FLAG_NONE>;
|
||||
|
||||
touch {
|
||||
compatible = "hynitron,cst328";
|
||||
reg = <0x1A>;
|
||||
pin-reset = <&gpio0 45 GPIO_FLAG_NONE>;
|
||||
};
|
||||
|
||||
// TCA8418 4x10 matrix scanner. Keymaps are written in the vendor's row/column
|
||||
// orientation (Xinyuan-LilyGO/T-Deck-MAX examples/factory/peri_keypad.cpp); the
|
||||
// TCA8418 columns are wired in reverse of that order, hence reverse-columns.
|
||||
// ALT (shift/uppercase) is row 2 col 0; SYM (symbol layer) is row 3 col 8.
|
||||
keyboard {
|
||||
compatible = "ti,tca8418";
|
||||
reg = <0x34>;
|
||||
rows = <4>;
|
||||
columns = <10>;
|
||||
reverse-columns;
|
||||
keymap-lc = [
|
||||
113 119 101 114 116 121 117 105 111 112 // q w e r t y u i o p
|
||||
97 115 100 102 103 104 106 107 108 8 // a s d f g h j k l BACKSPACE
|
||||
0 122 120 99 118 98 110 109 36 10 // z x c v b n m $ ENTER
|
||||
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
|
||||
];
|
||||
keymap-uc = [
|
||||
81 87 69 82 84 89 85 73 79 80 // Q W E R T Y U I O P
|
||||
65 83 68 70 71 72 74 75 76 8 // A S D F G H J K L BACKSPACE
|
||||
0 90 88 67 86 66 78 77 36 10 // Z X C V B N M $ ENTER
|
||||
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
|
||||
];
|
||||
keymap-sy = [
|
||||
49 50 51 52 53 54 55 56 57 48 // 1 2 3 4 5 6 7 8 9 0
|
||||
64 35 43 45 42 47 40 41 95 8 // @ # + - * / ( ) _ BACKSPACE
|
||||
0 33 63 59 58 39 34 44 46 10 // ! ? ; : ' " , . ENTER
|
||||
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
|
||||
];
|
||||
shift-row = <2>;
|
||||
shift-col = <0>;
|
||||
sym-row = <3>;
|
||||
sym-col = <8>;
|
||||
};
|
||||
|
||||
bq27220 {
|
||||
compatible = "ti,bq25896";
|
||||
reg = <0x6B>;
|
||||
};
|
||||
};
|
||||
|
||||
// Keyboard backlight (LED_PWM), GPIO42. The keypress-triggered flash/decay effect
|
||||
// from the pre-migration driver isn't reproduced here - kernel drivers don't have a
|
||||
// home for that kind of UI policy (same reasoning as the touch driver's dropped bezel
|
||||
// keys). Off by default; app code can drive brightness via the generic pwm-backlight
|
||||
// API if desired.
|
||||
keyboard_backlight_pwm {
|
||||
compatible = "espressif,esp32-pwm-ledc";
|
||||
pin = <&gpio0 42 GPIO_FLAG_NONE>;
|
||||
period-ns = <33333>;
|
||||
ledc-timer = <0>;
|
||||
ledc-channel = <0>;
|
||||
};
|
||||
|
||||
keyboard_backlight {
|
||||
compatible = "pwm-backlight";
|
||||
status = "disabled";
|
||||
pwm = <&keyboard_backlight_pwm>;
|
||||
};
|
||||
|
||||
spi0 {
|
||||
compatible = "espressif,esp32-spi";
|
||||
host = <SPI2_HOST>;
|
||||
cs-gpios = <&gpio0 34 GPIO_FLAG_NONE>, // 0: EPD display
|
||||
<&gpio0 48 GPIO_FLAG_NONE>, // 1: SD card
|
||||
<&gpio0 3 GPIO_FLAG_NONE>; // 2: LoRa radio (SX1262, not wired up yet)
|
||||
pin-mosi = <&gpio0 33 GPIO_FLAG_NONE>;
|
||||
pin-miso = <&gpio0 47 GPIO_FLAG_NONE>;
|
||||
pin-sclk = <&gpio0 36 GPIO_FLAG_NONE>;
|
||||
// The EPD pushes its whole 9600-byte framebuffer in one SPI write, which
|
||||
// exceeds the default ~4 KB transfer limit. Raise the bus limit to fit it.
|
||||
max-transfer-size = <65536>;
|
||||
|
||||
display@0 {
|
||||
compatible = "gooddisplay,gdeq031t10";
|
||||
pin-dc = <&gpio0 35 GPIO_FLAG_NONE>;
|
||||
pin-busy = <&gpio0 37 GPIO_FLAG_NONE>;
|
||||
clock-speed-hz = <10000000>;
|
||||
// Default to a fast (~1s) refresh for the automatic ghost-clears so the
|
||||
// full-screen flash they cause is as short as possible.
|
||||
refresh-mode = <GDEQ031T10_REFRESH_FAST>;
|
||||
};
|
||||
|
||||
sdcard@1 {
|
||||
compatible = "espressif,esp32-sdspi";
|
||||
status = "disabled";
|
||||
frequency-khz = <20000>;
|
||||
};
|
||||
};
|
||||
};
|
||||
9
Devices/lilygo-tdeck-pro/source/module.cpp
Normal file
9
Devices/lilygo-tdeck-pro/source/module.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct Module lilygo_tdeck_pro_module = {
|
||||
.name = "lilygo-tdeck-pro"
|
||||
};
|
||||
|
||||
}
|
||||
@ -12,6 +12,8 @@ hardware.tinyUsb=true
|
||||
hardware.esptoolFlashFreq=120M
|
||||
hardware.bluetooth=true
|
||||
|
||||
dependencies.useDeprecatedHal=false
|
||||
|
||||
storage.userDataLocation=SD
|
||||
|
||||
display.size=2.8"
|
||||
|
||||
@ -18,9 +18,6 @@
|
||||
|
||||
constexpr auto* TAG = "tdeck";
|
||||
|
||||
// Legacy placeholder (required until legacy HAL is cleaned up everywhere)
|
||||
extern const tt::hal::Configuration hardwareConfiguration = {};
|
||||
|
||||
extern "C" {
|
||||
|
||||
void subscribe_events() {
|
||||
@ -63,9 +60,7 @@ static error_t stop() {
|
||||
Module lilygo_tdeck_module = {
|
||||
.name = "lilygo-tdeck",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.stop = stop
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -30,16 +30,17 @@ static error_t start() {
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
// Empty for now
|
||||
if (gpio_set_level(POWER_ON_PIN, 0) != ESP_OK) {
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
struct Module lilygo_tdisplay_s3_module = {
|
||||
Module lilygo_tdisplay_s3_module = {
|
||||
.name = "lilygo-tdisplay-s3",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.stop = stop
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -2,22 +2,8 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
struct Module lilygo_tdisplay_module = {
|
||||
.name = "lilygo-tdisplay",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
Module lilygo_tdisplay_module = {
|
||||
.name = "lilygo-tdisplay"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -2,22 +2,8 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
struct Module lilygo_tdongle_s3_module = {
|
||||
.name = "lilygo-tdongle-s3",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
Module lilygo_tdongle_s3_module = {
|
||||
.name = "lilygo-tdongle-s3"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -2,22 +2,8 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
struct Module lilygo_thmi_module = {
|
||||
.name = "lilygo-thmi",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
Module lilygo_thmi_module = {
|
||||
.name = "lilygo-thmi"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ hardware.tinyUsb=true
|
||||
hardware.esptoolFlashFreq=40M
|
||||
hardware.bluetooth=true
|
||||
|
||||
dependencies.useDeprecatedHal=false
|
||||
|
||||
storage.userDataLocation=SD
|
||||
|
||||
display.size=2.33"
|
||||
|
||||
@ -15,18 +15,15 @@
|
||||
|
||||
constexpr auto* TAG = "T-Lora Pager";
|
||||
|
||||
// Legacy placeholder (required until legacy HAL is cleaned up everywhere). All board
|
||||
// peripherals are kernel drivers now (see the .dts): display (st7796), battery (bq27220),
|
||||
// charger power-off (bq25896), haptics (drv2605), keyboard (tca8418) and the encoder wheel
|
||||
// (lilygo,tpager-encoder, bound to LVGL below).
|
||||
extern const tt::hal::Configuration hardwareConfiguration = {};
|
||||
|
||||
extern "C" {
|
||||
|
||||
static void subscribe_events() {
|
||||
tt::kernel::SystemEventSubscription event_subscription;
|
||||
|
||||
static error_t start() {
|
||||
|
||||
LOG_I(TAG, LOG_MESSAGE_POWER_ON_START);
|
||||
|
||||
tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent) {
|
||||
event_subscription = tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent) {
|
||||
// The kernel tpager_encoder device is already started by kernel_init(); this just
|
||||
// registers it as an LVGL input device, which requires LVGL to be up first.
|
||||
lvgl_lock();
|
||||
@ -50,23 +47,18 @@ static void subscribe_events() {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static error_t start() {
|
||||
subscribe_events();
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
tt::kernel::unsubscribeSystemEvent(event_subscription);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
struct Module lilygo_tlora_pager_module = {
|
||||
Module lilygo_tlora_pager_module = {
|
||||
.name = "lilygo-tlora-pager",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.stop = stop
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -2,22 +2,8 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
struct Module m5stack_cardputer_adv_module = {
|
||||
.name = "m5stack-cardputer-adv",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
Module m5stack_cardputer_adv_module = {
|
||||
.name = "m5stack-cardputer-adv"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -2,22 +2,8 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Module m5stack_cardputer_module = {
|
||||
.name = "m5stack-cardputer",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "m5stack-cardputer"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
@ -7,11 +6,7 @@ extern "C" {
|
||||
// and axp192-module's start()), replacing what used to be done by hand here via a
|
||||
// DEVICE_EVENT_STARTED device_listener.
|
||||
Module m5stack_core2_module = {
|
||||
.name = "m5stack-core2",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "m5stack-core2"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,16 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct Module m5stack_cores3_module = {
|
||||
.name = "m5stack-cores3",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
Module m5stack_cores3_module = {
|
||||
.name = "m5stack-cores3"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
@ -9,26 +7,16 @@ extern Driver papers3_power_driver;
|
||||
extern Driver papers3_power_supply_driver;
|
||||
extern Driver papers3_display_driver;
|
||||
|
||||
static error_t start() {
|
||||
check(driver_construct_add(&papers3_power_supply_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&papers3_power_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&papers3_display_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
check(driver_remove_destruct(&papers3_display_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&papers3_power_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&papers3_power_supply_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
static Driver* const papers3_drivers[] = {
|
||||
&papers3_display_driver,
|
||||
&papers3_power_driver,
|
||||
&papers3_power_supply_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module m5stack_papers3_module = {
|
||||
.name = "m5stack-papers3",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = papers3_drivers
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -56,9 +56,7 @@ static error_t stop() {
|
||||
Module m5stack_stackchan_module = {
|
||||
.name = "m5stack-stackchan",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.stop = stop
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ constexpr auto* TAG = "StickCPlus2";
|
||||
|
||||
extern "C" {
|
||||
|
||||
static bool init_power() {
|
||||
static error_t init_power() {
|
||||
// CH552 applies 4 V to GPIO 0, which reduces Wi-Fi sensitivity.
|
||||
// Setting output to high adds a bias of 3.3 V and suppresses over-voltage:
|
||||
gpio_set_direction(GPIO_NUM_0, GPIO_MODE_OUTPUT);
|
||||
@ -17,25 +17,12 @@ static bool init_power() {
|
||||
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(GPIO_NUM_4, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static error_t start() {
|
||||
init_power();
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Module m5stack_stickc_plus2_module = {
|
||||
.name = "m5stack-stickc-plus2",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.start = init_power
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module m5stack_sticks3_module = {
|
||||
.name = "m5stack-sticks3",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "m5stack-sticks3"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
#include "devices/tab5_power_control.h"
|
||||
#include "devices/tab_5_camera.h"
|
||||
|
||||
#define TAG "Tab5"
|
||||
constexpr auto* TAG = "Tab5";
|
||||
|
||||
// PI4IOE5V6408-0 (0x43) bit 1
|
||||
constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1;
|
||||
@ -65,44 +65,31 @@ static void on_io_expander0_started(Device* device, DeviceEvent event, void* con
|
||||
extern "C" {
|
||||
|
||||
static error_t start() {
|
||||
|
||||
/* 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(&tab5_keyboard_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&tab5_power_control_driver) == ERROR_NONE);
|
||||
|
||||
tab5_detect_start();
|
||||
|
||||
tab5_camera_init();
|
||||
|
||||
device_listener_add(on_io_expander0_started, nullptr);
|
||||
|
||||
tab5_headphone_detect_start();
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
tab5_headphone_detect_stop();
|
||||
|
||||
device_listener_remove(on_io_expander0_started);
|
||||
|
||||
tab5_detect_stop();
|
||||
|
||||
/* 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(&tab5_keyboard_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&tab5_power_control_driver) == ERROR_NONE);
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static Driver* const tab5_drivers[] = {
|
||||
&tab5_keyboard_driver,
|
||||
&tab5_power_control_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module m5stack_tab5_module = {
|
||||
.name = "m5stack-tab5",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = tab5_drivers
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -38,29 +38,26 @@ static void on_device_event(Device* device, DeviceEvent event, void* context) {
|
||||
}
|
||||
|
||||
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(&unphone_power_switch_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&unphone_nav_buttons_driver) == ERROR_NONE);
|
||||
device_listener_add(on_device_event, nullptr);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
device_listener_remove(&on_device_event);
|
||||
/* 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(&unphone_nav_buttons_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&unphone_power_switch_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
struct Module unphone_module = {
|
||||
static Driver* const unphone_drivers[] = {
|
||||
&unphone_nav_buttons_driver,
|
||||
&unphone_power_switch_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module unphone_module = {
|
||||
.name = "unphone",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = unphone_drivers
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -2,22 +2,8 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
// Empty for now
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
struct Module waveshare_esp32_s3_geek_module = {
|
||||
.name = "waveshare-esp32-s3-geek",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
Module waveshare_esp32_s3_geek_module = {
|
||||
.name = "waveshare-esp32-s3-geek"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module waveshare_s3_lcd_13_module = {
|
||||
.name = "waveshare-s3-lcd-13",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "waveshare-s3-lcd-13"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module waveshare_s3_touch_lcd_128_module = {
|
||||
.name = "waveshare-s3-touch-lcd-128",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "waveshare-s3-touch-lcd-128"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module waveshare_s3_touch_lcd_147_module = {
|
||||
.name = "waveshare-s3-touch-lcd-147",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "waveshare-s3-touch-lcd-147"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module waveshare_s3_touch_lcd_43_module = {
|
||||
.name = "waveshare-s3-touch-lcd-43",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "waveshare-s3-touch-lcd-43"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
Module wireless_tag_wt32_sc01_plus_module = {
|
||||
.name = "wireless-tag-wt32-sc01-plus",
|
||||
.start = [] -> error_t { return ERROR_NONE; },
|
||||
.stop = [] -> error_t { return ERROR_NONE; },
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.name = "wireless-tag-wt32-sc01-plus"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
|
||||
## Higher Priority
|
||||
|
||||
- Drivers/audio-codec-module is not a module. Move it somewhere else. Or make it an actual module.
|
||||
- LilyGO T-Dongle S3: 1 button control, stop auto-launching web server
|
||||
- Core2: support power off via software
|
||||
- Create `#define` for empty module (for modules that fully rely on device.properties and don't define drivers or have start/stop logic)
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "Source"
|
||||
INCLUDE_DIRS "Source"
|
||||
REQUIRES Tactility esp_lcd esp_lcd_touch esp_lvgl_port
|
||||
)
|
||||
@ -1,3 +0,0 @@
|
||||
# EspLcdCompat
|
||||
|
||||
A set of helper classes to implement `esp_lcd` drivers in Tactility.
|
||||
@ -1,130 +0,0 @@
|
||||
#include "EspLcdDisplay.h"
|
||||
#include "EspLcdDisplayDriver.h"
|
||||
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/check.h>
|
||||
#include <Tactility/hal/touch/TouchDevice.h>
|
||||
#include <cassert>
|
||||
#include <esp_lvgl_port_disp.h>
|
||||
|
||||
constexpr auto* TAG = "EspLcdDisplay";
|
||||
|
||||
EspLcdDisplay::~EspLcdDisplay() {
|
||||
check(
|
||||
displayDriver == nullptr || displayDriver.use_count() < 2, // 1 reference is held by this class
|
||||
"DisplayDriver is still in use. This will cause memory access violations."
|
||||
);
|
||||
}
|
||||
|
||||
bool EspLcdDisplay::start() {
|
||||
if (!createIoHandle(ioHandle)) {
|
||||
LOG_E(TAG, "Failed to create IO handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!createPanelHandle(ioHandle, panelHandle)) {
|
||||
LOG_E(TAG, "Failed to create panel handle");
|
||||
esp_lcd_panel_io_del(ioHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EspLcdDisplay::stop() {
|
||||
if (lvglDisplay != nullptr) {
|
||||
stopLvgl();
|
||||
lvglDisplay = nullptr;
|
||||
}
|
||||
|
||||
if (panelHandle != nullptr && esp_lcd_panel_del(panelHandle) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ioHandle != nullptr && esp_lcd_panel_io_del(ioHandle) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (displayDriver != nullptr && displayDriver.use_count() > 1) {
|
||||
LOG_W(TAG, "DisplayDriver is still in use.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EspLcdDisplay::startLvgl() {
|
||||
assert(lvglDisplay == nullptr);
|
||||
|
||||
if (displayDriver != nullptr && displayDriver.use_count() > 1) {
|
||||
LOG_W(TAG, "DisplayDriver is still in use.");
|
||||
}
|
||||
|
||||
auto lvgl_port_config = getLvglPortDisplayConfig(ioHandle, panelHandle);
|
||||
|
||||
if (isRgbPanel()) {
|
||||
auto rgb_config = getLvglPortDisplayRgbConfig(ioHandle, panelHandle);
|
||||
lvglDisplay = lvgl_port_add_disp_rgb(&lvgl_port_config , &rgb_config);
|
||||
} else {
|
||||
lvglDisplay = lvgl_port_add_disp(&lvgl_port_config );
|
||||
}
|
||||
|
||||
auto touch_device = getTouchDevice();
|
||||
if (touch_device != nullptr && touch_device->supportsLvgl()) {
|
||||
touch_device->startLvgl(lvglDisplay);
|
||||
}
|
||||
|
||||
return lvglDisplay != nullptr;
|
||||
}
|
||||
|
||||
bool EspLcdDisplay::stopLvgl() {
|
||||
if (lvglDisplay == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto touch_device = getTouchDevice();
|
||||
if (touch_device != nullptr) {
|
||||
touch_device->stopLvgl();
|
||||
}
|
||||
|
||||
lvgl_port_remove_disp(lvglDisplay);
|
||||
lvglDisplay = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<tt::hal::display::DisplayDriver> EspLcdDisplay::getDisplayDriver() {
|
||||
assert(lvglDisplay == nullptr); // Still attached to LVGL context. Call stopLvgl() first.
|
||||
if (displayDriver == nullptr) {
|
||||
auto lvgl_port_config = getLvglPortDisplayConfig(ioHandle, panelHandle);
|
||||
|
||||
tt::hal::display::ColorFormat color_format;
|
||||
if (lvgl_port_config.color_format == LV_COLOR_FORMAT_I1) {
|
||||
color_format = tt::hal::display::ColorFormat::Monochrome;
|
||||
} else if (lvgl_port_config.color_format == LV_COLOR_FORMAT_RGB565) {
|
||||
if (rgbElementOrder == LCD_RGB_ELEMENT_ORDER_RGB) {
|
||||
if (lvgl_port_config.flags.swap_bytes) {
|
||||
color_format = tt::hal::display::ColorFormat::RGB565Swapped;
|
||||
} else {
|
||||
color_format = tt::hal::display::ColorFormat::RGB565;
|
||||
}
|
||||
} else {
|
||||
if (lvgl_port_config.flags.swap_bytes) {
|
||||
color_format = tt::hal::display::ColorFormat::BGR565Swapped;
|
||||
} else {
|
||||
color_format = tt::hal::display::ColorFormat::BGR565;
|
||||
}
|
||||
}
|
||||
} else if (lvgl_port_config.color_format == LV_COLOR_FORMAT_RGB888) {
|
||||
color_format = tt::hal::display::ColorFormat::RGB888;
|
||||
} else {
|
||||
check(false, "unsupported driver");
|
||||
}
|
||||
|
||||
displayDriver = std::make_shared<EspLcdDisplayDriver>(
|
||||
panelHandle,
|
||||
lvgl_port_config.hres,
|
||||
lvgl_port_config.vres,
|
||||
color_format
|
||||
);
|
||||
}
|
||||
return displayDriver;
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <tactility/check.h>
|
||||
#include <Tactility/hal/display/DisplayDevice.h>
|
||||
|
||||
#include <esp_lcd_types.h>
|
||||
#include <esp_lvgl_port_disp.h>
|
||||
|
||||
/** @deprecated use EspLcdDisplayV2 */
|
||||
class EspLcdDisplay : public tt::hal::display::DisplayDevice {
|
||||
|
||||
esp_lcd_panel_io_handle_t ioHandle = nullptr;
|
||||
esp_lcd_panel_handle_t panelHandle = nullptr;
|
||||
lv_display_t* lvglDisplay = nullptr;
|
||||
std::shared_ptr<tt::hal::display::DisplayDriver> displayDriver;
|
||||
/** @warning This is never changed, so a driver that needs BGR is not supported */
|
||||
lcd_rgb_element_order_t rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB;
|
||||
|
||||
protected:
|
||||
|
||||
// Used for sending commands such as setting curves
|
||||
esp_lcd_panel_io_handle_t getIoHandle() const { return ioHandle; }
|
||||
|
||||
virtual bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) = 0;
|
||||
|
||||
virtual bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) = 0;
|
||||
|
||||
virtual lvgl_port_display_cfg_t getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) = 0;
|
||||
|
||||
virtual bool isRgbPanel() const { return false; }
|
||||
|
||||
virtual lvgl_port_display_rgb_cfg_t getLvglPortDisplayRgbConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) { check(false, "Not supported"); }
|
||||
|
||||
public:
|
||||
|
||||
EspLcdDisplay() = default;
|
||||
|
||||
~EspLcdDisplay() override;
|
||||
|
||||
bool start() final;
|
||||
|
||||
bool stop() final;
|
||||
|
||||
// region LVGL
|
||||
|
||||
bool supportsLvgl() const final { return true; }
|
||||
|
||||
bool startLvgl() final;
|
||||
|
||||
bool stopLvgl() final;
|
||||
|
||||
lv_display_t* getLvglDisplay() const final { return lvglDisplay; }
|
||||
|
||||
// endregion
|
||||
|
||||
// region DisplayDriver
|
||||
|
||||
bool supportsDisplayDriver() const override { return true; }
|
||||
|
||||
/** @return a NativeDisplay instance if this device supports it */
|
||||
std::shared_ptr<tt::hal::display::DisplayDriver> getDisplayDriver() final;
|
||||
|
||||
// endregion
|
||||
};
|
||||
@ -1,47 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/hal/display/DisplayDriver.h>
|
||||
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#if CONFIG_SOC_MIPI_DSI_SUPPORTED
|
||||
#include <esp_lcd_mipi_dsi.h>
|
||||
#endif
|
||||
|
||||
class EspLcdDisplayDriver : public tt::hal::display::DisplayDriver {
|
||||
|
||||
esp_lcd_panel_handle_t panelHandle;
|
||||
uint16_t hRes;
|
||||
uint16_t vRes;
|
||||
tt::hal::display::ColorFormat colorFormat;
|
||||
|
||||
public:
|
||||
|
||||
EspLcdDisplayDriver(
|
||||
esp_lcd_panel_handle_t panelHandle,
|
||||
uint16_t hRes,
|
||||
uint16_t vRes,
|
||||
tt::hal::display::ColorFormat colorFormat
|
||||
) : panelHandle(panelHandle), hRes(hRes), vRes(vRes), colorFormat(colorFormat) {}
|
||||
|
||||
tt::hal::display::ColorFormat getColorFormat() const override {
|
||||
return colorFormat;
|
||||
}
|
||||
|
||||
bool drawBitmap(int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) override {
|
||||
bool result = esp_lcd_panel_draw_bitmap(panelHandle, xStart, yStart, xEnd, yEnd, pixelData) == ESP_OK;
|
||||
return result;
|
||||
}
|
||||
|
||||
uint16_t getPixelWidth() const override { return hRes; }
|
||||
|
||||
uint16_t getPixelHeight() const override { return vRes; }
|
||||
|
||||
#if CONFIG_SOC_MIPI_DSI_SUPPORTED
|
||||
uint8_t getFrameBuffers(void* outBuffers[2]) const override {
|
||||
if (outBuffers == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return (esp_lcd_dpi_panel_get_frame_buffer(panelHandle, 2, &outBuffers[0], &outBuffers[1]) == ESP_OK) ? 2 : 0;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
@ -1,231 +0,0 @@
|
||||
#include "EspLcdDisplayV2.h"
|
||||
#include "EspLcdDisplayDriver.h"
|
||||
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/check.h>
|
||||
#include <Tactility/hal/touch/TouchDevice.h>
|
||||
#include <cassert>
|
||||
#include <esp_lvgl_port_disp.h>
|
||||
|
||||
constexpr auto* TAG = "EspLcdDispV2";
|
||||
|
||||
inline unsigned int getBufferSize(const std::shared_ptr<EspLcdConfiguration>& configuration) {
|
||||
if (configuration->bufferSize != DEFAULT_BUFFER_SIZE) {
|
||||
return configuration->bufferSize;
|
||||
} else {
|
||||
return configuration->horizontalResolution * (configuration->verticalResolution / 10);
|
||||
}
|
||||
}
|
||||
|
||||
EspLcdDisplayV2::~EspLcdDisplayV2() {
|
||||
check(
|
||||
displayDriver == nullptr || displayDriver.use_count() < 2, // 1 reference is held by this class
|
||||
"DisplayDriver is still in use. This will cause memory access violations."
|
||||
);
|
||||
}
|
||||
|
||||
bool EspLcdDisplayV2::applyConfiguration() const {
|
||||
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to reset panel");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to init panel");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (configuration->invertColor && esp_lcd_panel_invert_color(panelHandle, configuration->invertColor) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to set panel to invert");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Warning: it looks like LVGL rotation is broken when "gap" is set and the screen is moved to a non-default orientation
|
||||
int gap_x = configuration->swapXY ? configuration->gapY : configuration->gapX;
|
||||
int gap_y = configuration->swapXY ? configuration->gapX : configuration->gapY;
|
||||
bool should_set_gap = gap_x != 0 || gap_y != 0;
|
||||
if (should_set_gap && esp_lcd_panel_set_gap(panelHandle, gap_x, gap_y) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to set panel gap");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (configuration->swapXY && esp_lcd_panel_swap_xy(panelHandle, configuration->swapXY) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to swap XY ");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool should_set_mirror = configuration->mirrorX || configuration->mirrorY;
|
||||
if (should_set_mirror && esp_lcd_panel_mirror(panelHandle, configuration->mirrorX, configuration->mirrorY) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to set panel to mirror");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (configuration->invertColor && esp_lcd_panel_invert_color(panelHandle, configuration->invertColor) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to set panel to invert");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_disp_on_off(panelHandle, true) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to turn display on");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EspLcdDisplayV2::start() {
|
||||
if (!createIoHandle(ioHandle)) {
|
||||
LOG_E(TAG, "Failed to create IO handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_lcd_panel_dev_config_t panel_config = createPanelConfig(configuration, configuration->resetPin);
|
||||
|
||||
if (!createPanelHandle(ioHandle, panel_config, panelHandle)) {
|
||||
LOG_E(TAG, "Failed to create panel handle");
|
||||
esp_lcd_panel_io_del(ioHandle);
|
||||
ioHandle = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!applyConfiguration()) {
|
||||
esp_lcd_panel_del(panelHandle);
|
||||
panelHandle = nullptr;
|
||||
esp_lcd_panel_io_del(ioHandle);
|
||||
ioHandle = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EspLcdDisplayV2::stop() {
|
||||
if (lvglDisplay != nullptr) {
|
||||
stopLvgl();
|
||||
lvglDisplay = nullptr;
|
||||
}
|
||||
|
||||
if (panelHandle != nullptr && esp_lcd_panel_del(panelHandle) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ioHandle != nullptr && esp_lcd_panel_io_del(ioHandle) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (displayDriver != nullptr && displayDriver.use_count() > 1) {
|
||||
LOG_W(TAG, "DisplayDriver is still in use.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EspLcdDisplayV2::startLvgl() {
|
||||
assert(lvglDisplay == nullptr);
|
||||
|
||||
if (displayDriver != nullptr && displayDriver.use_count() > 1) {
|
||||
LOG_W(TAG, "DisplayDriver is still in use.");
|
||||
}
|
||||
|
||||
auto lvgl_port_config = getLvglPortDisplayConfig(configuration, ioHandle, panelHandle);
|
||||
|
||||
if (useDsiPanel()) {
|
||||
auto dsi_config = getLvglPortDisplayDsiConfig(ioHandle, panelHandle);
|
||||
lvglDisplay = lvgl_port_add_disp_dsi(&lvgl_port_config, &dsi_config);
|
||||
} else if (isRgbPanel()) {
|
||||
auto rgb_config = getLvglPortDisplayRgbConfig(ioHandle, panelHandle);
|
||||
lvglDisplay = lvgl_port_add_disp_rgb(&lvgl_port_config, &rgb_config);
|
||||
} else {
|
||||
lvglDisplay = lvgl_port_add_disp(&lvgl_port_config);
|
||||
}
|
||||
|
||||
auto touch_device = getTouchDevice();
|
||||
if (touch_device != nullptr && touch_device->supportsLvgl()) {
|
||||
touch_device->startLvgl(lvglDisplay);
|
||||
}
|
||||
|
||||
return lvglDisplay != nullptr;
|
||||
}
|
||||
|
||||
bool EspLcdDisplayV2::stopLvgl() {
|
||||
if (lvglDisplay == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto touch_device = getTouchDevice();
|
||||
if (touch_device != nullptr) {
|
||||
touch_device->stopLvgl();
|
||||
}
|
||||
|
||||
lvgl_port_remove_disp(lvglDisplay);
|
||||
lvglDisplay = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
lvgl_port_display_cfg_t EspLcdDisplayV2::getLvglPortDisplayConfig(std::shared_ptr<EspLcdConfiguration> configuration, esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) {
|
||||
return lvgl_port_display_cfg_t {
|
||||
.io_handle = ioHandle,
|
||||
.panel_handle = panelHandle,
|
||||
.control_handle = nullptr,
|
||||
.buffer_size = getBufferSize(configuration),
|
||||
.double_buffer = false,
|
||||
.trans_size = 0,
|
||||
.hres = configuration->horizontalResolution,
|
||||
.vres = configuration->verticalResolution,
|
||||
.monochrome = configuration->monochrome,
|
||||
.rotation = {
|
||||
.swap_xy = configuration->swapXY,
|
||||
.mirror_x = configuration->mirrorX,
|
||||
.mirror_y = configuration->mirrorY,
|
||||
},
|
||||
.color_format = configuration->lvglColorFormat,
|
||||
.flags = {
|
||||
.buff_dma = configuration->buffSpiram ? 0u : 1u,
|
||||
.buff_spiram = configuration->buffSpiram ? 1u : 0u,
|
||||
.sw_rotate = configuration->swRotate ? 1u : 0u,
|
||||
.swap_bytes = configuration->lvglSwapBytes,
|
||||
.full_refresh = 0,
|
||||
.direct_mode = 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
std::shared_ptr<tt::hal::display::DisplayDriver> EspLcdDisplayV2::getDisplayDriver() {
|
||||
assert(lvglDisplay == nullptr); // Still attached to LVGL context. Call stopLvgl() first.
|
||||
if (displayDriver == nullptr) {
|
||||
auto lvgl_port_config = getLvglPortDisplayConfig(configuration, ioHandle, panelHandle);
|
||||
auto panel_config = createPanelConfig(configuration, GPIO_NUM_NC);
|
||||
|
||||
tt::hal::display::ColorFormat color_format;
|
||||
if (lvgl_port_config.color_format == LV_COLOR_FORMAT_I1) {
|
||||
color_format = tt::hal::display::ColorFormat::Monochrome;
|
||||
} else if (lvgl_port_config.color_format == LV_COLOR_FORMAT_RGB565) {
|
||||
if (panel_config.rgb_ele_order == LCD_RGB_ELEMENT_ORDER_RGB) {
|
||||
if (lvgl_port_config.flags.swap_bytes) {
|
||||
color_format = tt::hal::display::ColorFormat::RGB565Swapped;
|
||||
} else {
|
||||
color_format = tt::hal::display::ColorFormat::RGB565;
|
||||
}
|
||||
} else {
|
||||
if (lvgl_port_config.flags.swap_bytes) {
|
||||
color_format = tt::hal::display::ColorFormat::BGR565Swapped;
|
||||
} else {
|
||||
color_format = tt::hal::display::ColorFormat::BGR565;
|
||||
}
|
||||
}
|
||||
} else if (lvgl_port_config.color_format == LV_COLOR_FORMAT_RGB888) {
|
||||
color_format = tt::hal::display::ColorFormat::RGB888;
|
||||
} else {
|
||||
check(false, "unsupported driver");
|
||||
}
|
||||
|
||||
displayDriver = std::make_shared<EspLcdDisplayDriver>(
|
||||
panelHandle,
|
||||
lvgl_port_config.hres,
|
||||
lvgl_port_config.vres,
|
||||
color_format
|
||||
);
|
||||
}
|
||||
return displayDriver;
|
||||
}
|
||||
|
||||
@ -1,114 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <tactility/check.h>
|
||||
#include <Tactility/hal/display/DisplayDevice.h>
|
||||
#include <esp_lcd_panel_dev.h>
|
||||
|
||||
#include <esp_lcd_types.h>
|
||||
#include <esp_lvgl_port_disp.h>
|
||||
|
||||
constexpr auto DEFAULT_BUFFER_SIZE = 0;
|
||||
|
||||
struct EspLcdConfiguration {
|
||||
unsigned int horizontalResolution;
|
||||
unsigned int verticalResolution;
|
||||
int gapX;
|
||||
int gapY;
|
||||
bool monochrome;
|
||||
bool swapXY;
|
||||
bool mirrorX;
|
||||
bool mirrorY;
|
||||
bool invertColor;
|
||||
uint32_t bufferSize; // Size in pixel count. 0 means default, which is 1/10 of the screen size
|
||||
bool swRotate = false; // Use LVGL software rotation instead of hardware swap_xy (required for MIPI-DSI panels that don't support swap_xy)
|
||||
bool buffSpiram = false; // Allocate LVGL draw buffers from PSRAM instead of DMA-capable internal SRAM (required when sw_rotate needs a 3rd buffer that won't fit in internal SRAM)
|
||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
||||
std::function<void(uint8_t)> _Nullable backlightDutyFunction;
|
||||
gpio_num_t resetPin;
|
||||
lv_color_format_t lvglColorFormat;
|
||||
bool lvglSwapBytes;
|
||||
lcd_rgb_element_order_t rgbElementOrder;
|
||||
uint32_t bitsPerPixel;
|
||||
};
|
||||
|
||||
class EspLcdDisplayV2 : public tt::hal::display::DisplayDevice {
|
||||
esp_lcd_panel_io_handle_t _Nullable ioHandle = nullptr;
|
||||
esp_lcd_panel_handle_t _Nullable panelHandle = nullptr;
|
||||
lv_display_t* _Nullable lvglDisplay = nullptr;
|
||||
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable displayDriver;
|
||||
std::shared_ptr<EspLcdConfiguration> configuration;
|
||||
|
||||
bool applyConfiguration() const;
|
||||
|
||||
lvgl_port_display_cfg_t getLvglPortDisplayConfig(std::shared_ptr<EspLcdConfiguration> configuration, esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle);
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
virtual bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) = 0;
|
||||
|
||||
virtual esp_lcd_panel_dev_config_t createPanelConfig(std::shared_ptr<EspLcdConfiguration> espLcdConfiguration, gpio_num_t resetPin) = 0;
|
||||
|
||||
virtual bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) = 0;
|
||||
|
||||
virtual bool isRgbPanel() const { return false; }
|
||||
|
||||
virtual lvgl_port_display_rgb_cfg_t getLvglPortDisplayRgbConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) { check(false, "Not supported"); }
|
||||
|
||||
// Hook for MIPI-DSI DPI panels to let LVGL port use DSI-specific path
|
||||
virtual bool useDsiPanel() const { return false; }
|
||||
|
||||
virtual lvgl_port_display_dsi_cfg_t getLvglPortDisplayDsiConfig(esp_lcd_panel_io_handle_t /*ioHandle*/, esp_lcd_panel_handle_t /*panelHandle*/) {
|
||||
return lvgl_port_display_dsi_cfg_t{ .flags = { .avoid_tearing = 0 } };
|
||||
}
|
||||
|
||||
// Used for sending commands such as setting curves
|
||||
esp_lcd_panel_io_handle_t getIoHandle() const { return ioHandle; }
|
||||
|
||||
public:
|
||||
|
||||
explicit EspLcdDisplayV2(const std::shared_ptr<EspLcdConfiguration>& configuration) :
|
||||
configuration(configuration)
|
||||
{}
|
||||
|
||||
~EspLcdDisplayV2() override;
|
||||
|
||||
bool start() final;
|
||||
|
||||
bool stop() final;
|
||||
|
||||
// region LVGL
|
||||
|
||||
bool supportsLvgl() const final { return true; }
|
||||
|
||||
bool startLvgl() final;
|
||||
|
||||
bool stopLvgl() final;
|
||||
|
||||
lv_display_t* _Nullable getLvglDisplay() const final { return lvglDisplay; }
|
||||
|
||||
// endregion
|
||||
|
||||
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable getTouchDevice() override { return configuration->touch; }
|
||||
|
||||
// region Backlight
|
||||
|
||||
void setBacklightDuty(uint8_t backlightDuty) override {
|
||||
if (configuration->backlightDutyFunction != nullptr) {
|
||||
configuration->backlightDutyFunction(backlightDuty);
|
||||
}
|
||||
}
|
||||
|
||||
bool supportsBacklightDuty() const override { return configuration->backlightDutyFunction != nullptr; }
|
||||
|
||||
// endregion
|
||||
|
||||
// region DisplayDriver
|
||||
|
||||
bool supportsDisplayDriver() const override { return true; }
|
||||
|
||||
/** @return a NativeDisplay instance if this device supports it */
|
||||
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable getDisplayDriver() final;
|
||||
|
||||
// endregion
|
||||
};
|
||||
@ -1,70 +0,0 @@
|
||||
#include "EspLcdSpiDisplay.h"
|
||||
|
||||
#include <esp_lcd_panel_commands.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
constexpr auto* TAG = "EspLcdSpiDisplay";
|
||||
|
||||
bool EspLcdSpiDisplay::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
|
||||
LOG_I(TAG, "createIoHandle");
|
||||
|
||||
const esp_lcd_panel_io_spi_config_t panel_io_config = {
|
||||
.cs_gpio_num = spiConfiguration->csPin,
|
||||
.dc_gpio_num = spiConfiguration->dcPin,
|
||||
.spi_mode = 0,
|
||||
.pclk_hz = spiConfiguration->pixelClockFrequency,
|
||||
.trans_queue_depth = spiConfiguration->transactionQueueDepth,
|
||||
.on_color_trans_done = nullptr,
|
||||
.user_ctx = nullptr,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
.cs_ena_pretrans = 0,
|
||||
.cs_ena_posttrans = 0,
|
||||
.flags = {
|
||||
.dc_high_on_cmd = 0,
|
||||
.dc_low_on_data = 0,
|
||||
.dc_low_on_param = 0,
|
||||
.octal_mode = 0,
|
||||
.quad_mode = 0,
|
||||
.sio_mode = 1,
|
||||
.lsb_first = 0,
|
||||
.cs_high_active = 0
|
||||
}
|
||||
};
|
||||
|
||||
if (esp_lcd_new_panel_io_spi(spiConfiguration->spiHostDevice, &panel_io_config, &outHandle) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to create panel");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EspLcdSpiDisplay::setGammaCurve(uint8_t index) {
|
||||
uint8_t gamma_curve;
|
||||
switch (index) {
|
||||
case 0:
|
||||
gamma_curve = 0x01;
|
||||
break;
|
||||
case 1:
|
||||
gamma_curve = 0x04;
|
||||
break;
|
||||
case 2:
|
||||
gamma_curve = 0x02;
|
||||
break;
|
||||
case 3:
|
||||
gamma_curve = 0x08;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
const uint8_t param[] = {
|
||||
gamma_curve
|
||||
};
|
||||
|
||||
auto io_handle = getIoHandle();
|
||||
assert(io_handle != nullptr);
|
||||
if (esp_lcd_panel_io_tx_param(io_handle, LCD_CMD_GAMSET, param, 1) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to set gamma");
|
||||
}
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "EspLcdDisplayV2.h"
|
||||
|
||||
#include <driver/spi_common.h>
|
||||
|
||||
#include <esp_lcd_io_spi.h>
|
||||
#include <esp_lcd_types.h>
|
||||
|
||||
/**
|
||||
* Adds IO implementations on top of EspLcdDisplayV2
|
||||
* @warning This is an abstract class. You need to extend it to use it.
|
||||
*/
|
||||
class EspLcdSpiDisplay : public EspLcdDisplayV2 {
|
||||
|
||||
public:
|
||||
|
||||
struct SpiConfiguration {
|
||||
spi_host_device_t spiHostDevice;
|
||||
gpio_num_t csPin;
|
||||
gpio_num_t dcPin;
|
||||
unsigned int pixelClockFrequency = 80'000'000; // Hertz
|
||||
size_t transactionQueueDepth = 10;
|
||||
};
|
||||
|
||||
explicit EspLcdSpiDisplay(const std::shared_ptr<EspLcdConfiguration>& configuration, const std::shared_ptr<SpiConfiguration> spiConfiguration, int gammaCurveCount) :
|
||||
EspLcdDisplayV2(configuration),
|
||||
spiConfiguration(spiConfiguration),
|
||||
gammaCurveCount(gammaCurveCount)
|
||||
{}
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<SpiConfiguration> spiConfiguration;
|
||||
int gammaCurveCount;
|
||||
|
||||
protected:
|
||||
|
||||
bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) override;
|
||||
|
||||
// region Gamma
|
||||
|
||||
void setGammaCurve(uint8_t index) override;
|
||||
|
||||
uint8_t getGammaCurveCount() const override { return gammaCurveCount; }
|
||||
|
||||
// endregion
|
||||
};
|
||||
|
||||
@ -1,93 +0,0 @@
|
||||
#include "EspLcdTouch.h"
|
||||
|
||||
|
||||
#include <EspLcdTouchDriver.h>
|
||||
#include <tactility/log.h>
|
||||
#include <esp_lvgl_port_touch.h>
|
||||
|
||||
constexpr auto* TAG = "EspLcdTouch";
|
||||
|
||||
bool EspLcdTouch::start() {
|
||||
if (!createIoHandle(ioHandle) != ESP_OK) {
|
||||
LOG_E(TAG, "Touch IO failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
config = createEspLcdTouchConfig();
|
||||
|
||||
if (!createTouchHandle(ioHandle, config, touchHandle)) {
|
||||
LOG_E(TAG, "Driver init failed");
|
||||
esp_lcd_panel_io_del(ioHandle);
|
||||
ioHandle = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EspLcdTouch::stop() {
|
||||
if (lvglDevice != nullptr) {
|
||||
stopLvgl();
|
||||
}
|
||||
|
||||
if (ioHandle != nullptr) {
|
||||
esp_lcd_panel_io_del(ioHandle);
|
||||
ioHandle = nullptr;
|
||||
}
|
||||
|
||||
if (touchHandle != nullptr) {
|
||||
esp_lcd_touch_del(touchHandle);
|
||||
touchHandle = nullptr;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EspLcdTouch::startLvgl(lv_disp_t* display) {
|
||||
if (lvglDevice != nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (touchDriver != nullptr && touchDriver.use_count() > 1) {
|
||||
LOG_W(TAG, "TouchDriver is still in use.");
|
||||
}
|
||||
|
||||
const lvgl_port_touch_cfg_t touch_cfg = {
|
||||
.disp = display,
|
||||
.handle = touchHandle,
|
||||
};
|
||||
|
||||
LOG_I(TAG, "Adding touch to LVGL");
|
||||
lvglDevice = lvgl_port_add_touch(&touch_cfg);
|
||||
if (lvglDevice == nullptr) {
|
||||
LOG_E(TAG, "Adding touch failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EspLcdTouch::stopLvgl() {
|
||||
if (lvglDevice == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lvgl_port_remove_touch(lvglDevice);
|
||||
lvglDevice = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<tt::hal::touch::TouchDriver> _Nullable EspLcdTouch::getTouchDriver() {
|
||||
assert(lvglDevice == nullptr); // Still attached to LVGL context. Call stopLvgl() first.
|
||||
|
||||
if (touchHandle == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (touchDriver == nullptr) {
|
||||
touchDriver = std::make_shared<EspLcdTouchDriver>(touchHandle);
|
||||
}
|
||||
|
||||
return touchDriver;
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_lcd_touch.h>
|
||||
#include <esp_lcd_types.h>
|
||||
#include <lvgl.h>
|
||||
#include <Tactility/hal/touch/TouchDevice.h>
|
||||
#include <Tactility/hal/touch/TouchDriver.h>
|
||||
|
||||
class EspLcdTouch : public tt::hal::touch::TouchDevice {
|
||||
|
||||
esp_lcd_touch_config_t config;
|
||||
esp_lcd_panel_io_handle_t _Nullable ioHandle = nullptr;
|
||||
esp_lcd_touch_handle_t _Nullable touchHandle = nullptr;
|
||||
lv_indev_t* lvglDevice = nullptr;
|
||||
std::shared_ptr<tt::hal::touch::TouchDriver> touchDriver;
|
||||
|
||||
protected:
|
||||
|
||||
esp_lcd_touch_handle_t _Nullable getTouchHandle() const { return touchHandle; }
|
||||
|
||||
virtual bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) = 0;
|
||||
|
||||
virtual bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& touchHandle) = 0;
|
||||
|
||||
virtual esp_lcd_touch_config_t createEspLcdTouchConfig() = 0;
|
||||
|
||||
public:
|
||||
|
||||
bool start() final;
|
||||
|
||||
bool stop() final;
|
||||
|
||||
bool supportsLvgl() const final { return true; }
|
||||
|
||||
bool startLvgl(lv_display_t* display) final;
|
||||
|
||||
bool stopLvgl() final;
|
||||
|
||||
lv_indev_t* getLvglIndev() final { return lvglDevice; }
|
||||
|
||||
bool supportsTouchDriver() override { return true; }
|
||||
|
||||
std::shared_ptr<tt::hal::touch::TouchDriver> _Nullable getTouchDriver() final;
|
||||
};
|
||||
@ -1,13 +0,0 @@
|
||||
#include "EspLcdTouchDriver.h"
|
||||
|
||||
#include <tactility/log.h>
|
||||
|
||||
constexpr auto* TAG = "EspLcdTouchDriver";
|
||||
|
||||
bool EspLcdTouchDriver::getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* _Nullable strength, uint8_t* pointCount, uint8_t maxPointCount) {
|
||||
if (esp_lcd_touch_read_data(handle) != ESP_OK) {
|
||||
LOG_E(TAG, "Read data failed");
|
||||
return false;
|
||||
}
|
||||
return esp_lcd_touch_get_coordinates(handle, x, y, strength, pointCount, maxPointCount);
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_lcd_touch.h>
|
||||
#include <Tactility/hal/touch/TouchDriver.h>
|
||||
|
||||
class EspLcdTouchDriver final : public tt::hal::touch::TouchDriver {
|
||||
|
||||
esp_lcd_touch_handle_t handle;
|
||||
|
||||
public:
|
||||
|
||||
EspLcdTouchDriver(esp_lcd_touch_handle_t handle) : handle(handle) {}
|
||||
|
||||
bool getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* pointCount, uint8_t maxPointCount) override;
|
||||
};
|
||||
@ -1,5 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "Source"
|
||||
INCLUDE_DIRS "Source"
|
||||
REQUIRES Tactility driver
|
||||
)
|
||||
@ -1,35 +0,0 @@
|
||||
# GDEQ031T10
|
||||
|
||||
Display driver for the GoodDisplay GDEQ031T10, a 3.1" 320x240 SPI e-paper panel
|
||||
(UC8253-family controller) used by the LilyGO T-Deck Pro/Max.
|
||||
|
||||
The command sequence (panel setting, power on/off, fast/partial LUT timings, deep
|
||||
sleep) is ported from the vendor reference driver in
|
||||
[Xinyuan-LilyGO/T-Deck-MAX](https://github.com/Xinyuan-LilyGO/T-Deck-MAX)
|
||||
(`examples/Elink_paper/GDEQ031T10_Arduino/Display_EPD_W21.cpp`).
|
||||
|
||||
Unlike `EPDiyDisplay` (which targets parallel-bus "EPD47"-style panels via the
|
||||
`epdiy` library), this panel uses a simple 4-wire SPI interface (CS/DC/RST/BUSY +
|
||||
SCK/MOSI), so it's driven directly with ESP-IDF's `spi_master` and `gpio` APIs
|
||||
rather than `epdiy` or `esp_lcd_panel`.
|
||||
|
||||
Each LVGL flush is staged and handed off to a dedicated refresh task (the physical
|
||||
refresh takes hundreds of ms to over a second, too long to run in LVGL's flush
|
||||
callback without stalling input). The task diffs the new frame against a shadow of
|
||||
what the panel currently holds and, where possible, performs a windowed partial
|
||||
refresh of just the changed region instead of redrawing the whole panel. A full
|
||||
refresh is still done on the first draw, on an explicit `requestFullRefresh()`,
|
||||
when a change covers most of the screen, or periodically to clear the ghosting
|
||||
that partial updates accumulate (gated by `MAX_PARTIAL_REFRESHES`, with a
|
||||
localized ghost-clear preferred over a full flash when the accumulated changes are
|
||||
confined to a small region). The panel's charge pump is powered off once the
|
||||
refresh task's queue goes idle, not after every single refresh. Four refresh
|
||||
modes are available, trading speed for quality:
|
||||
|
||||
- `Full` (~3s) - best quality, used by default
|
||||
- `Fast` (~1.0s)
|
||||
- `Slow` (~1.5s)
|
||||
- `Partial` (~0.5s) - most ghosting
|
||||
|
||||
`requestFullRefresh()` forces the next flush to use `Full` mode, useful for
|
||||
clearing up ghosting accumulated from a run of fast/partial refreshes.
|
||||
@ -1,638 +0,0 @@
|
||||
#include "Gdeq031t10Display.h"
|
||||
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <themes/mono/lv_theme_mono.h>
|
||||
// Full lv_theme_t definition (opaque in public lvgl.h) — needed to extend the
|
||||
// mono theme with a custom apply callback, per LVGL's theme-extension pattern.
|
||||
#include <themes/lv_theme_private.h>
|
||||
|
||||
constexpr auto* TAG = "GDEQ031T10";
|
||||
|
||||
// UC8253-family commands, ported from Xinyuan-LilyGO/T-Deck-MAX's
|
||||
// Display_EPD_W21.cpp reference driver.
|
||||
namespace {
|
||||
constexpr uint8_t CMD_PANEL_SETTING = 0x00;
|
||||
constexpr uint8_t CMD_POWER_ON_OFF = 0x02; // shared opcode: power on when followed by 0x04, power off as standalone 0x02
|
||||
constexpr uint8_t CMD_POWER_ON = 0x04;
|
||||
constexpr uint8_t CMD_DEEP_SLEEP = 0x07;
|
||||
constexpr uint8_t CMD_DATA_START_OLD = 0x10;
|
||||
constexpr uint8_t CMD_DISPLAY_REFRESH = 0x12;
|
||||
constexpr uint8_t CMD_DATA_START_NEW = 0x13;
|
||||
constexpr uint8_t CMD_VCOM_DATA_INTERVAL = 0x50;
|
||||
constexpr uint8_t CMD_PARTIAL_WINDOW = 0x90;
|
||||
constexpr uint8_t CMD_PARTIAL_IN = 0x91;
|
||||
constexpr uint8_t CMD_PARTIAL_OUT = 0x92;
|
||||
constexpr uint8_t CMD_FAST_MODE_ENABLE = 0xE0;
|
||||
constexpr uint8_t CMD_FAST_MODE_TIMING = 0xE5;
|
||||
|
||||
constexpr uint8_t DEEP_SLEEP_CHECK_CODE = 0xA5;
|
||||
}
|
||||
|
||||
bool Gdeq031t10Display::writeCommand(uint8_t command) {
|
||||
gpio_set_level(configuration->pinDc, 0);
|
||||
spi_transaction_t transaction = {};
|
||||
transaction.length = 8;
|
||||
transaction.tx_buffer = &command;
|
||||
if (spi_device_polling_transmit(spiDevice, &transaction) != ESP_OK) {
|
||||
LOG_E(TAG, "SPI command transfer failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gdeq031t10Display::writeData(const uint8_t* data, size_t length) {
|
||||
gpio_set_level(configuration->pinDc, 1);
|
||||
spi_transaction_t transaction = {};
|
||||
transaction.length = length * 8;
|
||||
transaction.tx_buffer = data;
|
||||
if (spi_device_polling_transmit(spiDevice, &transaction) != ESP_OK) {
|
||||
LOG_E(TAG, "SPI data transfer failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gdeq031t10Display::waitWhileBusy() const {
|
||||
// BUSY pin reads high when the controller is idle/ready.
|
||||
constexpr TickType_t timeout = pdMS_TO_TICKS(5000);
|
||||
const TickType_t start = xTaskGetTickCount();
|
||||
while (gpio_get_level(configuration->pinBusy) != 1) {
|
||||
if (xTaskGetTickCount() - start > timeout) {
|
||||
LOG_E(TAG, "Timed out waiting for panel BUSY");
|
||||
return false;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(2));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Gdeq031t10Display::reset() const {
|
||||
gpio_set_level(configuration->pinReset, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
gpio_set_level(configuration->pinReset, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
bool Gdeq031t10Display::initFull() {
|
||||
reset();
|
||||
bool ok = writeCommand(CMD_PANEL_SETTING);
|
||||
ok = ok && writeData(configuration->mirror180 ? 0x13 : 0x1F);
|
||||
ok = ok && writeCommand(CMD_POWER_ON);
|
||||
ok = ok && waitWhileBusy();
|
||||
if (!ok) {
|
||||
LOG_E(TAG, "Full init failed");
|
||||
return false;
|
||||
}
|
||||
panelMode = RefreshMode::Full;
|
||||
panelPowerOn = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gdeq031t10Display::initFast() {
|
||||
if (!initFull()) {
|
||||
return false;
|
||||
}
|
||||
bool ok = writeCommand(CMD_FAST_MODE_ENABLE);
|
||||
ok = ok && writeData(0x02);
|
||||
ok = ok && writeCommand(CMD_FAST_MODE_TIMING);
|
||||
ok = ok && writeData(0x5A); // ~1.0s
|
||||
if (!ok) {
|
||||
LOG_E(TAG, "Fast mode init failed");
|
||||
return false;
|
||||
}
|
||||
panelMode = RefreshMode::Fast;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gdeq031t10Display::initSlow() {
|
||||
if (!initFull()) {
|
||||
return false;
|
||||
}
|
||||
bool ok = writeCommand(CMD_FAST_MODE_ENABLE);
|
||||
ok = ok && writeData(0x02);
|
||||
ok = ok && writeCommand(CMD_FAST_MODE_TIMING);
|
||||
ok = ok && writeData(0x6E); // ~1.5s
|
||||
if (!ok) {
|
||||
LOG_E(TAG, "Slow mode init failed");
|
||||
return false;
|
||||
}
|
||||
panelMode = RefreshMode::Slow;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gdeq031t10Display::initPartial() {
|
||||
if (!initFull()) {
|
||||
return false;
|
||||
}
|
||||
bool ok = writeCommand(CMD_FAST_MODE_ENABLE);
|
||||
ok = ok && writeData(0x02);
|
||||
ok = ok && writeCommand(CMD_FAST_MODE_TIMING);
|
||||
ok = ok && writeData(0x79);
|
||||
ok = ok && writeCommand(CMD_VCOM_DATA_INTERVAL);
|
||||
ok = ok && writeData(0xD7);
|
||||
if (!ok) {
|
||||
LOG_E(TAG, "Partial mode init failed");
|
||||
return false;
|
||||
}
|
||||
panelMode = RefreshMode::Partial;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gdeq031t10Display::ensurePanelReady(RefreshMode mode) {
|
||||
if (panelMode != mode) {
|
||||
// A mode change needs the full init path: it starts with reset(), which
|
||||
// restores register defaults (required when leaving partial mode, whose
|
||||
// VCOM/data-interval setting would otherwise linger).
|
||||
switch (mode) {
|
||||
case RefreshMode::Full: return initFull();
|
||||
case RefreshMode::Fast: return initFast();
|
||||
case RefreshMode::Slow: return initSlow();
|
||||
case RefreshMode::Partial: return initPartial();
|
||||
}
|
||||
return false;
|
||||
} else if (!panelPowerOn) {
|
||||
// Registers still hold the mode; only the charge pump was idled.
|
||||
if (!writeCommand(CMD_POWER_ON) || !waitWhileBusy()) {
|
||||
LOG_E(TAG, "Panel did not become ready after power-on");
|
||||
return false;
|
||||
}
|
||||
panelPowerOn = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gdeq031t10Display::powerOff() {
|
||||
// Command the panel off regardless of whether BUSY confirms it: retrying
|
||||
// forever here would just as likely hang, and a stuck-BUSY panel is
|
||||
// already unusable either way.
|
||||
bool ok = writeCommand(CMD_POWER_ON_OFF); // 0x02 standalone = power off
|
||||
if (!ok || !waitWhileBusy()) {
|
||||
LOG_E(TAG, "Panel did not confirm power-off");
|
||||
ok = false;
|
||||
}
|
||||
panelPowerOn = false;
|
||||
return ok;
|
||||
}
|
||||
|
||||
void Gdeq031t10Display::queueRefresh() {
|
||||
xSemaphoreTake(bufferMutex, portMAX_DELAY);
|
||||
// renderFramebuffer always holds a complete frame (single buffer, full
|
||||
// render mode), so the staged copy is self-contained.
|
||||
std::memcpy(pendingFramebuffer.get(), renderFramebuffer.get() + LVGL_I1_PALETTE_SIZE, FRAMEBUFFER_SIZE);
|
||||
framePending = true;
|
||||
xSemaphoreGive(bufferMutex);
|
||||
xTaskNotifyGive(refreshTask);
|
||||
}
|
||||
|
||||
void Gdeq031t10Display::refreshTaskMain(void* parameter) {
|
||||
auto* self = static_cast<Gdeq031t10Display*>(parameter);
|
||||
self->runRefreshTask();
|
||||
xSemaphoreGive(self->refreshTaskExited);
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
void Gdeq031t10Display::runRefreshTask() {
|
||||
while (true) {
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
if (refreshTaskShouldExit) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Drain staged frames latest-wins: everything LVGL flushed while the
|
||||
// previous refresh was in progress collapses into one panel update.
|
||||
// A frame staged while the panel is off stays staged; the power-on
|
||||
// path kicks this task to draw it.
|
||||
while (!refreshTaskShouldExit && powered) {
|
||||
xSemaphoreTake(bufferMutex, portMAX_DELAY);
|
||||
const bool havePending = framePending;
|
||||
if (havePending) {
|
||||
std::swap(pendingFramebuffer, taskFramebuffer);
|
||||
framePending = false;
|
||||
}
|
||||
xSemaphoreGive(bufferMutex);
|
||||
|
||||
if (!havePending) {
|
||||
break;
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
|
||||
// The pipeline went idle: drop the charge pump now rather than on a
|
||||
// timer. The mode registers survive a power-off, so the next refresh
|
||||
// only pays a short power-on wait (on this task, invisible to the UI).
|
||||
xSemaphoreTake(panelMutex, portMAX_DELAY);
|
||||
if (initialized && powered && panelPowerOn) {
|
||||
powerOff();
|
||||
}
|
||||
xSemaphoreGive(panelMutex);
|
||||
}
|
||||
}
|
||||
|
||||
void Gdeq031t10Display::refresh() {
|
||||
const uint8_t* renderBitmap = taskFramebuffer.get();
|
||||
constexpr int BYTES_PER_ROW = WIDTH / 8;
|
||||
|
||||
// Find the bounding box of bytes that differ from what the panel holds. The
|
||||
// panel stores the inverted (shadow) polarity, so compare against ~render.
|
||||
int firstCol = BYTES_PER_ROW, lastCol = -1, firstRow = HEIGHT, lastRow = -1;
|
||||
for (int row = 0; row < HEIGHT; row++) {
|
||||
const size_t base = static_cast<size_t>(row) * BYTES_PER_ROW;
|
||||
for (int col = 0; col < BYTES_PER_ROW; col++) {
|
||||
if (static_cast<uint8_t>(~renderBitmap[base + col]) != shadowFramebuffer[base + col]) {
|
||||
if (col < firstCol) firstCol = col;
|
||||
if (col > lastCol) lastCol = col;
|
||||
if (row < firstRow) firstRow = row;
|
||||
if (row > lastRow) lastRow = row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool nothingChanged = (lastCol < 0);
|
||||
if (nothingChanged && !forceFullRefresh) {
|
||||
return; // panel already shows this frame; don't refresh needlessly
|
||||
}
|
||||
|
||||
// A change covering most of the screen or an explicit request warrants a
|
||||
// full refresh; otherwise refresh just the box.
|
||||
const int changedRows = nothingChanged ? 0 : (lastRow - firstRow + 1);
|
||||
const bool largeChange = changedRows > (HEIGHT * 3 / 4);
|
||||
|
||||
xSemaphoreTake(panelMutex, portMAX_DELAY);
|
||||
if (!powered || !initialized) {
|
||||
// The display was turned off/stopped while this frame was staged; the
|
||||
// shadow mismatch makes the frame redraw after the next power-on.
|
||||
xSemaphoreGive(panelMutex);
|
||||
return;
|
||||
}
|
||||
if (forceFullRefresh || largeChange || nothingChanged) {
|
||||
// Explicit requests get the best-quality LUT; automatic ghost-clears use
|
||||
// the configured (typically faster) mode.
|
||||
refreshFull(forceFullRefresh ? RefreshMode::Full : currentRefreshMode.load());
|
||||
forceFullRefresh = false;
|
||||
partialRefreshCount = 0;
|
||||
ghostAreaValid = false;
|
||||
xSemaphoreGive(panelMutex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Grow the ghost union with this change so the gate sees where partial
|
||||
// updates have accumulated since the last clear.
|
||||
if (!ghostAreaValid) {
|
||||
ghostFirstCol = firstCol;
|
||||
ghostLastCol = lastCol;
|
||||
ghostFirstRow = firstRow;
|
||||
ghostLastRow = lastRow;
|
||||
ghostAreaValid = true;
|
||||
} else {
|
||||
ghostFirstCol = std::min(ghostFirstCol, firstCol);
|
||||
ghostLastCol = std::max(ghostLastCol, lastCol);
|
||||
ghostFirstRow = std::min(ghostFirstRow, firstRow);
|
||||
ghostLastRow = std::max(ghostLastRow, lastRow);
|
||||
}
|
||||
|
||||
if (partialRefreshCount >= MAX_PARTIAL_REFRESHES) {
|
||||
// Ghost-clear gate. If the accumulated churn is confined to a small
|
||||
// region (a blinking cursor, a line being typed into), scrub just that
|
||||
// window — a full-screen flash there is needless and distracting. Only
|
||||
// widespread churn earns a whole-panel refresh.
|
||||
const int unionWidth = (ghostLastCol - ghostFirstCol + 1) * 8;
|
||||
const int unionHeight = ghostLastRow - ghostFirstRow + 1;
|
||||
const bool unionIsLarge = unionWidth * unionHeight * 4 >= WIDTH * HEIGHT; // >= 25% of the panel
|
||||
if (unionIsLarge) {
|
||||
refreshFull(currentRefreshMode);
|
||||
} else {
|
||||
refreshWindow(ghostFirstCol, ghostLastCol, ghostFirstRow, ghostLastRow, true);
|
||||
}
|
||||
partialRefreshCount = 0;
|
||||
ghostAreaValid = false;
|
||||
} else {
|
||||
refreshWindow(firstCol, lastCol, firstRow, lastRow, false);
|
||||
partialRefreshCount++;
|
||||
}
|
||||
xSemaphoreGive(panelMutex);
|
||||
}
|
||||
|
||||
void Gdeq031t10Display::refreshFull(RefreshMode mode) {
|
||||
if (!ensurePanelReady(mode)) {
|
||||
LOG_E(TAG, "Skipping full refresh: panel not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t* renderBitmap = taskFramebuffer.get();
|
||||
|
||||
// shadowFramebuffer keeps the panel-polarity copy of the last frame for the
|
||||
// controller's old/new differential refresh; LVGL's I1 polarity is inverted.
|
||||
if (!writeCommand(CMD_DATA_START_OLD) || !writeData(shadowFramebuffer.get(), FRAMEBUFFER_SIZE)) {
|
||||
LOG_E(TAG, "Failed to send old frame data");
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < FRAMEBUFFER_SIZE; i++) {
|
||||
shadowFramebuffer[i] = static_cast<uint8_t>(~renderBitmap[i]);
|
||||
}
|
||||
if (!writeCommand(CMD_DATA_START_NEW) || !writeData(shadowFramebuffer.get(), FRAMEBUFFER_SIZE)) {
|
||||
LOG_E(TAG, "Failed to send new frame data");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!writeCommand(CMD_DISPLAY_REFRESH)) {
|
||||
LOG_E(TAG, "Failed to trigger display refresh");
|
||||
return;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1)); // datasheet requires >=200us settle before polling BUSY
|
||||
if (!waitWhileBusy()) {
|
||||
LOG_E(TAG, "Full refresh did not complete");
|
||||
}
|
||||
}
|
||||
|
||||
void Gdeq031t10Display::refreshWindow(int firstByteCol, int lastByteCol, int firstRow, int lastRow, bool scrubGhosts) {
|
||||
// Partial LUT (fast waveform via temperature force) on a sub-region only. The
|
||||
// RAM window must be byte-aligned in X, which it already is (byte columns).
|
||||
if (!ensurePanelReady(RefreshMode::Partial)) {
|
||||
LOG_E(TAG, "Skipping window refresh: panel not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t* renderBitmap = taskFramebuffer.get();
|
||||
constexpr int BYTES_PER_ROW = WIDTH / 8;
|
||||
const int widthBytes = lastByteCol - firstByteCol + 1;
|
||||
|
||||
const uint16_t x = static_cast<uint16_t>(firstByteCol * 8);
|
||||
const uint16_t xe = static_cast<uint16_t>(lastByteCol * 8 + 7);
|
||||
const uint16_t y = static_cast<uint16_t>(firstRow);
|
||||
const uint16_t ye = static_cast<uint16_t>(lastRow);
|
||||
|
||||
// Set the partial RAM window (GxEPD2 GDEQ031T10 sequence).
|
||||
bool ok = writeCommand(CMD_PARTIAL_IN);
|
||||
ok = ok && writeCommand(CMD_PARTIAL_WINDOW);
|
||||
ok = ok && writeData(static_cast<uint8_t>(x));
|
||||
ok = ok && writeData(static_cast<uint8_t>(xe));
|
||||
ok = ok && writeData(static_cast<uint8_t>(y >> 8));
|
||||
ok = ok && writeData(static_cast<uint8_t>(y & 0xFF));
|
||||
ok = ok && writeData(static_cast<uint8_t>(ye >> 8));
|
||||
ok = ok && writeData(static_cast<uint8_t>(ye & 0xFF));
|
||||
ok = ok && writeData(0x01);
|
||||
if (!ok) {
|
||||
LOG_E(TAG, "Failed to set partial refresh window");
|
||||
writeCommand(CMD_PARTIAL_OUT); // best-effort: leave partial-window mode
|
||||
return;
|
||||
}
|
||||
|
||||
// Old region: normally the region's current panel contents (shadow),
|
||||
// gathered contiguously. For a ghost scrub, feed the complement of the new
|
||||
// data instead: the controller then sees every pixel as changed and drives
|
||||
// each one through a transition, clearing accumulated partial-refresh
|
||||
// ghosting in this window only.
|
||||
size_t n = 0;
|
||||
for (int row = firstRow; row <= lastRow; row++) {
|
||||
const size_t base = static_cast<size_t>(row) * BYTES_PER_ROW + firstByteCol;
|
||||
if (scrubGhosts) {
|
||||
// New panel data is ~renderBitmap, so its complement is renderBitmap.
|
||||
std::memcpy(®ionBuffer[n], &renderBitmap[base], widthBytes);
|
||||
} else {
|
||||
std::memcpy(®ionBuffer[n], &shadowFramebuffer[base], widthBytes);
|
||||
}
|
||||
n += widthBytes;
|
||||
}
|
||||
if (!writeCommand(CMD_DATA_START_OLD) || !writeData(regionBuffer.get(), n)) {
|
||||
LOG_E(TAG, "Failed to send old window data");
|
||||
writeCommand(CMD_PARTIAL_OUT);
|
||||
return;
|
||||
}
|
||||
|
||||
// New region: inverted render, and update the shadow for this region as we go.
|
||||
n = 0;
|
||||
for (int row = firstRow; row <= lastRow; row++) {
|
||||
const size_t base = static_cast<size_t>(row) * BYTES_PER_ROW + firstByteCol;
|
||||
for (int c = 0; c < widthBytes; c++) {
|
||||
const uint8_t value = static_cast<uint8_t>(~renderBitmap[base + c]);
|
||||
regionBuffer[n++] = value;
|
||||
shadowFramebuffer[base + c] = value;
|
||||
}
|
||||
}
|
||||
if (!writeCommand(CMD_DATA_START_NEW) || !writeData(regionBuffer.get(), n)) {
|
||||
LOG_E(TAG, "Failed to send new window data");
|
||||
writeCommand(CMD_PARTIAL_OUT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (writeCommand(CMD_DISPLAY_REFRESH)) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
if (!waitWhileBusy()) {
|
||||
LOG_E(TAG, "Window refresh did not complete");
|
||||
}
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to trigger window refresh");
|
||||
}
|
||||
writeCommand(CMD_PARTIAL_OUT);
|
||||
}
|
||||
|
||||
void Gdeq031t10Display::flushCallback(lv_display_t* display, const lv_area_t* area, uint8_t* pixelMap) {
|
||||
// pixelMap points into renderFramebuffer (it was passed directly to
|
||||
// lv_display_set_buffers). Only stage the frame here: the physical refresh
|
||||
// takes up to ~1s, so it runs on the driver's refresh task instead of
|
||||
// blocking the LVGL task (which would stall all input handling).
|
||||
auto* self = static_cast<Gdeq031t10Display*>(lv_display_get_user_data(display));
|
||||
|
||||
if (lv_display_flush_is_last(display)) {
|
||||
self->queueRefresh();
|
||||
}
|
||||
|
||||
lv_display_flush_ready(display);
|
||||
}
|
||||
|
||||
void Gdeq031t10Display::themeApplyCallback(lv_theme_t* /*theme*/, lv_obj_t* obj) {
|
||||
// Keep textarea cursors solid (no blink): on e-paper each blink toggle is a
|
||||
// panel refresh. anim_duration 0 makes lv_textarea skip the blink animation.
|
||||
if (lv_obj_check_type(obj, &lv_textarea_class)) {
|
||||
lv_obj_set_style_anim_duration(obj, 0, LV_PART_CURSOR);
|
||||
}
|
||||
}
|
||||
|
||||
bool Gdeq031t10Display::start() {
|
||||
if (initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
gpio_config_t outputConfig = {
|
||||
.pin_bit_mask = (1ULL << configuration->pinDc) | (1ULL << configuration->pinReset),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&outputConfig);
|
||||
|
||||
gpio_config_t busyConfig = {
|
||||
.pin_bit_mask = 1ULL << configuration->pinBusy,
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&busyConfig);
|
||||
|
||||
spi_device_interface_config_t deviceConfig = {
|
||||
.mode = 0,
|
||||
.clock_speed_hz = configuration->clockSpeedHz,
|
||||
.spics_io_num = configuration->pinCs,
|
||||
.queue_size = 1,
|
||||
};
|
||||
|
||||
if (spi_bus_add_device(configuration->spiHost, &deviceConfig, &spiDevice) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to add SPI device");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (panelMutex == nullptr) {
|
||||
panelMutex = xSemaphoreCreateMutex();
|
||||
}
|
||||
if (bufferMutex == nullptr) {
|
||||
bufferMutex = xSemaphoreCreateMutex();
|
||||
}
|
||||
if (refreshTaskExited == nullptr) {
|
||||
refreshTaskExited = xSemaphoreCreateBinary();
|
||||
}
|
||||
|
||||
shadowFramebuffer = std::make_unique<uint8_t[]>(FRAMEBUFFER_SIZE);
|
||||
// LVGL needs room for the 8-byte I1 palette ahead of the 1bpp bitmap.
|
||||
renderFramebuffer = std::make_unique<uint8_t[]>(LVGL_I1_PALETTE_SIZE + FRAMEBUFFER_SIZE);
|
||||
// Scratch for gathering a windowed region (worst case = the whole frame).
|
||||
regionBuffer = std::make_unique<uint8_t[]>(FRAMEBUFFER_SIZE);
|
||||
pendingFramebuffer = std::make_unique<uint8_t[]>(FRAMEBUFFER_SIZE);
|
||||
taskFramebuffer = std::make_unique<uint8_t[]>(FRAMEBUFFER_SIZE);
|
||||
std::memset(shadowFramebuffer.get(), 0xFF, FRAMEBUFFER_SIZE);
|
||||
std::memset(renderFramebuffer.get(), 0xFF, LVGL_I1_PALETTE_SIZE + FRAMEBUFFER_SIZE);
|
||||
std::memset(pendingFramebuffer.get(), 0xFF, FRAMEBUFFER_SIZE);
|
||||
std::memset(taskFramebuffer.get(), 0xFF, FRAMEBUFFER_SIZE);
|
||||
|
||||
currentRefreshMode = configuration->defaultRefreshMode;
|
||||
initFull();
|
||||
|
||||
refreshTaskShouldExit = false;
|
||||
if (xTaskCreate(&refreshTaskMain, "epd_refresh", REFRESH_TASK_STACK_SIZE, this, REFRESH_TASK_PRIORITY, &refreshTask) != pdPASS) {
|
||||
LOG_E(TAG, "Failed to create refresh task");
|
||||
spi_bus_remove_device(spiDevice);
|
||||
spiDevice = nullptr;
|
||||
shadowFramebuffer.reset();
|
||||
renderFramebuffer.reset();
|
||||
regionBuffer.reset();
|
||||
pendingFramebuffer.reset();
|
||||
taskFramebuffer.reset();
|
||||
return false;
|
||||
}
|
||||
powered = true;
|
||||
initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gdeq031t10Display::stop() {
|
||||
if (!initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
stopLvgl();
|
||||
|
||||
// Join the refresh task before touching the panel: it may be mid-refresh.
|
||||
refreshTaskShouldExit = true;
|
||||
xTaskNotifyGive(refreshTask);
|
||||
xSemaphoreTake(refreshTaskExited, portMAX_DELAY);
|
||||
refreshTask = nullptr;
|
||||
|
||||
setPowerOn(false);
|
||||
|
||||
xSemaphoreTake(panelMutex, portMAX_DELAY);
|
||||
initialized = false;
|
||||
spi_bus_remove_device(spiDevice);
|
||||
spiDevice = nullptr;
|
||||
shadowFramebuffer.reset();
|
||||
renderFramebuffer.reset();
|
||||
regionBuffer.reset();
|
||||
pendingFramebuffer.reset();
|
||||
taskFramebuffer.reset();
|
||||
framePending = false;
|
||||
xSemaphoreGive(panelMutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Gdeq031t10Display::setPowerOn(bool turnOn) {
|
||||
if (turnOn == powered) {
|
||||
return;
|
||||
}
|
||||
|
||||
xSemaphoreTake(panelMutex, portMAX_DELAY);
|
||||
if (turnOn) {
|
||||
initFull(); // toggling RST also wakes the panel from deep sleep
|
||||
} else {
|
||||
if (panelPowerOn) {
|
||||
powerOff();
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
writeCommand(CMD_DEEP_SLEEP);
|
||||
writeData(DEEP_SLEEP_CHECK_CODE);
|
||||
// Deep sleep needs a reset to wake, which restores register defaults.
|
||||
panelMode.reset();
|
||||
}
|
||||
|
||||
powered = turnOn;
|
||||
xSemaphoreGive(panelMutex);
|
||||
|
||||
if (turnOn && refreshTask != nullptr) {
|
||||
// initFull left the charge pump on. Kick the refresh task: it redraws
|
||||
// any frame staged while the panel was off, then idles the pump down.
|
||||
xTaskNotifyGive(refreshTask);
|
||||
}
|
||||
}
|
||||
|
||||
void Gdeq031t10Display::requestFullRefresh() {
|
||||
forceFullRefresh = true;
|
||||
}
|
||||
|
||||
bool Gdeq031t10Display::startLvgl() {
|
||||
if (lvglDisplay != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
lvglDisplay = lv_display_create(Gdeq031t10Display::WIDTH, Gdeq031t10Display::HEIGHT);
|
||||
if (lvglDisplay == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lv_display_set_user_data(lvglDisplay, this);
|
||||
lv_display_set_color_format(lvglDisplay, LV_COLOR_FORMAT_I1);
|
||||
|
||||
// The default colour theme renders accent-coloured text/icons that threshold
|
||||
// to near-invisible on a 1bpp panel. Apply LVGL's monochrome theme (light
|
||||
// background, dark foreground) so UI content shows as solid black on white.
|
||||
lv_theme_t* baseTheme = lv_theme_mono_init(lvglDisplay, false, LV_FONT_DEFAULT);
|
||||
// Chain a theme on top of the mono theme that disables the textarea cursor
|
||||
// blink. A blinking cursor invalidates its region ~twice a second, and on
|
||||
// e-paper every invalidation is a panel refresh, so text-entry screens (e.g.
|
||||
// the Wi-Fi password field) flash continuously. The mono theme still applies
|
||||
// first (it's the parent); themeApplyCallback only pins the cursor solid.
|
||||
static lv_theme_t epaperTheme;
|
||||
epaperTheme = *baseTheme;
|
||||
lv_theme_set_parent(&epaperTheme, baseTheme);
|
||||
lv_theme_set_apply_cb(&epaperTheme, &Gdeq031t10Display::themeApplyCallback);
|
||||
lv_display_set_theme(lvglDisplay, &epaperTheme);
|
||||
lv_display_set_render_mode(lvglDisplay, LV_DISPLAY_RENDER_MODE_FULL);
|
||||
lv_display_set_buffers(lvglDisplay, renderFramebuffer.get(), nullptr, LVGL_I1_PALETTE_SIZE + FRAMEBUFFER_SIZE, LV_DISPLAY_RENDER_MODE_FULL);
|
||||
lv_display_set_flush_cb(lvglDisplay, flushCallback);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Gdeq031t10Display::stopLvgl() {
|
||||
if (lvglDisplay == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
lv_display_delete(lvglDisplay);
|
||||
lvglDisplay = nullptr;
|
||||
return true;
|
||||
}
|
||||
@ -1,225 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/hal/display/DisplayDevice.h>
|
||||
#include <Tactility/hal/touch/TouchDevice.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/spi_master.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
#include <lvgl.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
/**
|
||||
* Driver for the GoodDisplay GDEQ031T10 (UC8253-family controller) 3.1" 320x240
|
||||
* SPI e-paper panel, as used on the LilyGO T-Deck Pro/Max.
|
||||
*
|
||||
* Command sequence and timings are ported from the vendor reference driver in
|
||||
* Xinyuan-LilyGO/T-Deck-MAX (examples/Elink_paper/GDEQ031T10_Arduino).
|
||||
*/
|
||||
class Gdeq031t10Display final : public tt::hal::display::DisplayDevice {
|
||||
|
||||
public:
|
||||
|
||||
enum class RefreshMode {
|
||||
Full, // ~3s, best quality
|
||||
Fast, // ~1.0s
|
||||
Slow, // ~1.5s, fast LUT with extra settling
|
||||
Partial // ~0.5s, full-frame partial-mode refresh (more ghosting)
|
||||
};
|
||||
|
||||
class Configuration {
|
||||
public:
|
||||
|
||||
Configuration(
|
||||
spi_host_device_t spiHost,
|
||||
gpio_num_t pinCs,
|
||||
gpio_num_t pinDc,
|
||||
gpio_num_t pinReset,
|
||||
gpio_num_t pinBusy,
|
||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr,
|
||||
int clockSpeedHz = 4'000'000,
|
||||
RefreshMode defaultRefreshMode = RefreshMode::Full,
|
||||
bool mirror180 = false
|
||||
) : spiHost(spiHost),
|
||||
pinCs(pinCs),
|
||||
pinDc(pinDc),
|
||||
pinReset(pinReset),
|
||||
pinBusy(pinBusy),
|
||||
touch(std::move(touch)),
|
||||
clockSpeedHz(clockSpeedHz),
|
||||
defaultRefreshMode(defaultRefreshMode),
|
||||
mirror180(mirror180)
|
||||
{}
|
||||
|
||||
spi_host_device_t spiHost;
|
||||
gpio_num_t pinCs;
|
||||
gpio_num_t pinDc;
|
||||
gpio_num_t pinReset;
|
||||
gpio_num_t pinBusy;
|
||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
||||
int clockSpeedHz;
|
||||
RefreshMode defaultRefreshMode;
|
||||
/** Panel is mounted upside down relative to the reference orientation */
|
||||
bool mirror180;
|
||||
};
|
||||
|
||||
static constexpr uint16_t WIDTH = 240;
|
||||
static constexpr uint16_t HEIGHT = 320;
|
||||
static constexpr size_t FRAMEBUFFER_SIZE = (WIDTH * HEIGHT) / 8; // 1 bpp packed
|
||||
// LVGL 9 stores a 2-colour palette (2 x lv_color32_t = 8 bytes) at the start
|
||||
// of an LV_COLOR_FORMAT_I1 buffer, before the packed 1bpp bitmap.
|
||||
static constexpr size_t LVGL_I1_PALETTE_SIZE = 8;
|
||||
|
||||
private:
|
||||
|
||||
std::unique_ptr<Configuration> configuration;
|
||||
spi_device_handle_t spiDevice = nullptr;
|
||||
lv_display_t* _Nullable lvglDisplay = nullptr;
|
||||
/** Mirrors what the panel currently holds, required by the controller's
|
||||
* "old data" + "new data" double-buffered refresh protocol. */
|
||||
std::unique_ptr<uint8_t[]> shadowFramebuffer;
|
||||
/** Render target for LVGL; copied to the panel on flush */
|
||||
std::unique_ptr<uint8_t[]> renderFramebuffer;
|
||||
bool initialized = false;
|
||||
std::atomic<bool> powered = false;
|
||||
std::atomic<RefreshMode> currentRefreshMode = RefreshMode::Full;
|
||||
/** Number of windowed partial refreshes since the last full refresh. A full
|
||||
* refresh is forced once this reaches MAX_PARTIAL_REFRESHES to clear the
|
||||
* ghosting that partial updates accumulate. */
|
||||
uint8_t partialRefreshCount = 0;
|
||||
/** Forces the next refresh to be a full-screen refresh (set at boot and by
|
||||
* requestFullRefresh, read on the refresh task). */
|
||||
std::atomic<bool> forceFullRefresh = true;
|
||||
/** Scratch buffer used to gather a windowed region's bytes for one SPI write. */
|
||||
std::unique_ptr<uint8_t[]> regionBuffer;
|
||||
static constexpr uint8_t MAX_PARTIAL_REFRESHES = 8;
|
||||
/** Union bounding box (byte-column/row space) of all windowed partial
|
||||
* refreshes since the last ghost-clear. When the partial-refresh gate
|
||||
* expires, this decides between a localized scrub and a full-screen
|
||||
* refresh: small repeated updates (a text cursor, a typing line) shouldn't
|
||||
* flash the whole panel. */
|
||||
bool ghostAreaValid = false;
|
||||
int ghostFirstCol = 0;
|
||||
int ghostLastCol = 0;
|
||||
int ghostFirstRow = 0;
|
||||
int ghostLastRow = 0;
|
||||
/** Waveform mode the panel registers currently hold. Empty when the
|
||||
* registers are in an unknown state (before first init, or after deep sleep,
|
||||
* which requires a reset that restores defaults). */
|
||||
std::optional<RefreshMode> panelMode;
|
||||
/** True while the panel's charge pump is on (CMD_POWER_ON issued, no
|
||||
* power-off since). */
|
||||
bool panelPowerOn = false;
|
||||
/** Serializes panel/SPI access between the refresh task and external
|
||||
* callers (setPowerOn, stop). */
|
||||
SemaphoreHandle_t panelMutex = nullptr;
|
||||
|
||||
// The physical refresh takes hundreds of ms to over a second of ink
|
||||
// movement. Running it inside LVGL's flush callback would block the LVGL
|
||||
// task (and with it all input handling) for that long, so flushes only
|
||||
// stage the frame and a dedicated task drives the panel. Frames coalesce
|
||||
// latest-wins: however many flushes arrive during a refresh, only the
|
||||
// newest staged frame is drawn next.
|
||||
static constexpr uint32_t REFRESH_TASK_PRIORITY = 3; // below LVGL (6), above idle
|
||||
static constexpr uint32_t REFRESH_TASK_STACK_SIZE = 4096;
|
||||
TaskHandle_t refreshTask = nullptr;
|
||||
/** Signalled by the refresh task right before it exits; stop() joins on it. */
|
||||
SemaphoreHandle_t refreshTaskExited = nullptr;
|
||||
/** Guards pendingFramebuffer and framePending (LVGL flush vs refresh task). */
|
||||
SemaphoreHandle_t bufferMutex = nullptr;
|
||||
/** Latest complete frame staged by the LVGL flush callback. */
|
||||
std::unique_ptr<uint8_t[]> pendingFramebuffer;
|
||||
/** The refresh task's working copy of the frame it is drawing (swapped with
|
||||
* pendingFramebuffer under bufferMutex; no copy on the task side). */
|
||||
std::unique_ptr<uint8_t[]> taskFramebuffer;
|
||||
bool framePending = false;
|
||||
std::atomic<bool> refreshTaskShouldExit = false;
|
||||
|
||||
/** Returns false if the SPI transfer failed; the panel/shadow state should
|
||||
* not be advanced as if it succeeded. */
|
||||
bool writeCommand(uint8_t command);
|
||||
bool writeData(const uint8_t* data, size_t length);
|
||||
bool writeData(uint8_t data) { return writeData(&data, 1); }
|
||||
/** Waits for the BUSY pin to report ready, up to a fixed timeout. Returns
|
||||
* false (and logs) if the panel never became ready, e.g. bad wiring or no
|
||||
* panel attached — callers must not assume the operation completed. */
|
||||
bool waitWhileBusy() const;
|
||||
void reset() const;
|
||||
|
||||
bool initFull();
|
||||
bool initFast();
|
||||
bool initSlow();
|
||||
bool initPartial();
|
||||
|
||||
/** Puts the panel in the given waveform mode with the charge pump on,
|
||||
* skipping the (expensive) init sequence when it already is. */
|
||||
bool ensurePanelReady(RefreshMode mode);
|
||||
|
||||
bool powerOff();
|
||||
/** Stages the LVGL-rendered frame for the refresh task (called on flush). */
|
||||
void queueRefresh();
|
||||
static void refreshTaskMain(void* parameter);
|
||||
void runRefreshTask();
|
||||
void refresh();
|
||||
void refreshFull(RefreshMode mode);
|
||||
/** Windowed partial refresh. With scrubGhosts the controller is fed "old"
|
||||
* data that is the complement of the new frame, so it drives every pixel in
|
||||
* the window through a transition — a localized ghost-clear that only
|
||||
* flashes the window itself. */
|
||||
void refreshWindow(int firstByteCol, int lastByteCol, int firstRow, int lastRow, bool scrubGhosts);
|
||||
|
||||
static void flushCallback(lv_display_t* display, const lv_area_t* area, uint8_t* pixelMap);
|
||||
/** Theme hook: disables the textarea cursor blink (see startLvgl). */
|
||||
static void themeApplyCallback(lv_theme_t* theme, lv_obj_t* obj);
|
||||
|
||||
public:
|
||||
|
||||
explicit Gdeq031t10Display(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
|
||||
assert(configuration != nullptr);
|
||||
}
|
||||
|
||||
~Gdeq031t10Display() override {
|
||||
if (panelMutex != nullptr) {
|
||||
vSemaphoreDelete(panelMutex);
|
||||
}
|
||||
if (bufferMutex != nullptr) {
|
||||
vSemaphoreDelete(bufferMutex);
|
||||
}
|
||||
if (refreshTaskExited != nullptr) {
|
||||
vSemaphoreDelete(refreshTaskExited);
|
||||
}
|
||||
}
|
||||
|
||||
std::string getName() const override { return "GDEQ031T10"; }
|
||||
|
||||
std::string getDescription() const override { return "GoodDisplay GDEQ031T10 e-paper display"; }
|
||||
|
||||
bool start() override;
|
||||
bool stop() override;
|
||||
|
||||
void setPowerOn(bool turnOn) override;
|
||||
bool isPoweredOn() const override { return powered; }
|
||||
bool supportsPowerControl() const override { return true; }
|
||||
|
||||
void requestFullRefresh() override;
|
||||
|
||||
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable getTouchDevice() override {
|
||||
return configuration->touch;
|
||||
}
|
||||
|
||||
bool supportsLvgl() const override { return true; }
|
||||
bool startLvgl() override;
|
||||
bool stopLvgl() override;
|
||||
lv_display_t* _Nullable getLvglDisplay() const override { return lvglDisplay; }
|
||||
|
||||
bool supportsDisplayDriver() const override { return false; }
|
||||
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable getDisplayDriver() override { return nullptr; }
|
||||
|
||||
/** Sets the refresh mode used for automatic (non-full) refreshes from now on,
|
||||
* until changed again by a subsequent call. */
|
||||
void setRefreshMode(RefreshMode mode) { currentRefreshMode = mode; }
|
||||
};
|
||||
@ -1,19 +1,14 @@
|
||||
# Audio codec module
|
||||
|
||||
Defines `AUDIO_CODEC_TYPE` and the codec-agnostic `AudioCodecApi` (open/close/
|
||||
read/write/volume/mute/native-rate/native-channels/capabilities) that every
|
||||
audio codec driver in this repo implements (ES8311, ES8388, ES7210, AW88298,
|
||||
the dummy I2S amps, the PDM mic). This is intentionally low-level: no
|
||||
resampling or mixing, one codec device per chip. Most app code should go
|
||||
through `audio_stream` (see `Drivers/audio-stream-module`) instead of talking
|
||||
to a codec device directly.
|
||||
Defines codec-agnostic `AudioCodecApi` (open/close/read/write/volume/mute/native-rate/native-channels/capabilities)
|
||||
that every audio codec driver in this repo implements (ES8311, ES8388, ES7210, AW88298, the dummy I2S amps, the PDM mic).
|
||||
This is intentionally low-level: no resampling or mixing, one codec device per chip. Most app code should go through
|
||||
`audio_stream` (see `Drivers/audio-stream-module`) instead of talking to a codec device directly.
|
||||
|
||||
Also provides `audio_codec_adapters.h`: glue that lets a codec driver build an
|
||||
`esp_codec_dev` control/data/GPIO interface (`audio_codec_ctrl_if_t` /
|
||||
`audio_codec_data_if_t` / `audio_codec_gpio_if_t`) directly from Tactility's
|
||||
own I2C controller, I2S controller, and GPIO pin devices, so a codec driver
|
||||
only ever needs `device_get_parent()`/`device_find_by_name()` plus a handful
|
||||
of devicetree properties -- it never touches the platform I2C/I2S/GPIO APIs
|
||||
directly.
|
||||
Also provides `audio_codec_adapters.h`: glue that lets a codec driver build an `esp_codec_dev` control/data/GPIO
|
||||
interface (`audio_codec_ctrl_if_t` / `audio_codec_data_if_t` / `audio_codec_gpio_if_t`) directly from Tactility's
|
||||
own I2C controller, I2S controller, and GPIO pin devices, so a codec driver only ever needs
|
||||
`device_get_parent()`/`device_find_by_name()` plus a handful of devicetree properties -- it never touches the platform
|
||||
I2C/I2S/GPIO APIs directly.
|
||||
|
||||
License: [Apache v2.0](LICENSE-Apache-2.0.md)
|
||||
|
||||
@ -44,8 +44,7 @@ Module audio_stream_module = {
|
||||
.name = "audio-stream",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = audio_stream_module_symbols,
|
||||
.internal = nullptr
|
||||
.symbols = audio_stream_module_symbols
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -7,24 +6,17 @@ extern "C" {
|
||||
|
||||
extern Driver aw88298_driver;
|
||||
|
||||
static error_t start() {
|
||||
check(driver_construct_add(&aw88298_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
check(driver_remove_destruct(&aw88298_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
extern const ModuleSymbol aw88298_module_symbols[];
|
||||
|
||||
static Driver* const aw88298_drivers[] = {
|
||||
&aw88298_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module aw88298_module = {
|
||||
.name = "aw88298",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = aw88298_module_symbols,
|
||||
.internal = nullptr
|
||||
.drivers = aw88298_drivers,
|
||||
.symbols = aw88298_module_symbols
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -7,22 +6,14 @@ extern "C" {
|
||||
|
||||
extern Driver aw9523b_driver;
|
||||
|
||||
static error_t start() {
|
||||
check(driver_construct_add(&aw9523b_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
check(driver_remove_destruct(&aw9523b_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
static Driver* const aw9523b_drivers[] = {
|
||||
&aw9523b_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module aw9523b_module = {
|
||||
.name = "aw9523b",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = aw9523b_drivers
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <drivers/axp192.h>
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -10,6 +9,13 @@ extern Driver axp192_driver;
|
||||
extern Driver axp192_power_supply_driver;
|
||||
extern Driver axp192_backlight_driver;
|
||||
|
||||
static Driver* const axp192_drivers[] = {
|
||||
&axp192_driver,
|
||||
&axp192_power_supply_driver,
|
||||
&axp192_backlight_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct ModuleSymbol axp192_module_symbols[] = {
|
||||
DEFINE_MODULE_SYMBOL(axp192_is_rail_enabled),
|
||||
DEFINE_MODULE_SYMBOL(axp192_set_rail_enabled),
|
||||
@ -27,28 +33,9 @@ const struct ModuleSymbol axp192_module_symbols[] = {
|
||||
MODULE_SYMBOL_TERMINATOR
|
||||
};
|
||||
|
||||
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(&axp192_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&axp192_power_supply_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&axp192_backlight_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(&axp192_backlight_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&axp192_power_supply_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&axp192_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Module axp192_module = {
|
||||
.name = "axp192",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.drivers = axp192_drivers,
|
||||
.symbols = axp192_module_symbols,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <drivers/axp2101.h>
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -10,6 +9,13 @@ extern Driver axp2101_driver;
|
||||
extern Driver axp2101_power_supply_driver;
|
||||
extern Driver axp2101_backlight_driver;
|
||||
|
||||
static Driver* const axp2101_drivers[] = {
|
||||
&axp2101_driver,
|
||||
&axp2101_power_supply_driver,
|
||||
&axp2101_backlight_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct ModuleSymbol axp2101_module_symbols[] = {
|
||||
DEFINE_MODULE_SYMBOL(axp2101_is_dcdc_enabled),
|
||||
DEFINE_MODULE_SYMBOL(axp2101_set_dcdc_enabled),
|
||||
@ -28,30 +34,10 @@ const struct ModuleSymbol axp2101_module_symbols[] = {
|
||||
MODULE_SYMBOL_TERMINATOR
|
||||
};
|
||||
|
||||
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(&axp2101_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&axp2101_power_supply_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&axp2101_backlight_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(&axp2101_backlight_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&axp2101_power_supply_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&axp2101_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Module axp2101_module = {
|
||||
.name = "axp2101",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = axp2101_module_symbols,
|
||||
.internal = nullptr
|
||||
.drivers = axp2101_drivers,
|
||||
.symbols = axp2101_module_symbols
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -7,26 +6,14 @@ extern "C" {
|
||||
|
||||
extern Driver axs5106_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(&axs5106_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(&axs5106_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
static Driver* const axs5106_drivers[] = {
|
||||
&axs5106_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module axs5106_module = {
|
||||
.name = "axs5106",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = axs5106_drivers
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -7,26 +6,14 @@ extern "C" {
|
||||
|
||||
extern Driver bm8563_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(&bm8563_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(&bm8563_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
static Driver* const bm8563_drivers[] = {
|
||||
&bm8563_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module bm8563_module = {
|
||||
.name = "bm8563",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = bm8563_drivers
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -7,28 +6,17 @@ extern "C" {
|
||||
|
||||
extern Driver bmi270_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(&bmi270_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(&bmi270_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
static Driver* const bmi270_drivers[] = {
|
||||
&bmi270_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
extern const ModuleSymbol bmi270_module_symbols[];
|
||||
|
||||
Module bmi270_module = {
|
||||
.name = "bmi270",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = bmi270_module_symbols,
|
||||
.internal = nullptr
|
||||
.drivers = bmi270_drivers,
|
||||
.symbols = bmi270_module_symbols
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -8,28 +7,15 @@ extern "C" {
|
||||
extern Driver bq24295_driver;
|
||||
extern Driver bq24295_power_supply_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(&bq24295_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&bq24295_power_supply_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(&bq24295_power_supply_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&bq24295_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
static Driver* const bq24295_drivers[] = {
|
||||
&bq24295_driver,
|
||||
&bq24295_power_supply_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module bq24295_module = {
|
||||
.name = "bq24295",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = bq24295_drivers
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -8,28 +7,15 @@ extern "C" {
|
||||
extern Driver bq25896_driver;
|
||||
extern Driver bq25896_power_supply_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(&bq25896_power_supply_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&bq25896_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(&bq25896_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&bq25896_power_supply_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
static Driver* const bq25896_drivers[] = {
|
||||
&bq25896_driver,
|
||||
&bq25896_power_supply_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module bq25896_module = {
|
||||
.name = "bq25896",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = bq25896_drivers
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -8,28 +7,15 @@ extern "C" {
|
||||
extern Driver bq27220_driver;
|
||||
extern Driver bq27220_power_supply_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(&bq27220_power_supply_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&bq27220_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(&bq27220_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&bq27220_power_supply_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
static Driver* const bq27220_drivers[] = {
|
||||
&bq27220_driver,
|
||||
&bq27220_power_supply_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module bq27220_module = {
|
||||
.name = "bq27220",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = bq27220_drivers
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -7,26 +6,14 @@ extern "C" {
|
||||
|
||||
extern Driver button_control_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(&button_control_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(&button_control_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
static Driver* const button_control_drivers[] = {
|
||||
&button_control_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module button_control_module = {
|
||||
.name = "button-control",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = button_control_drivers
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -7,26 +6,14 @@ extern "C" {
|
||||
|
||||
extern Driver ch422g_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(&ch422g_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(&ch422g_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
static Driver* const ch422g_drivers[] = {
|
||||
&ch422g_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module ch422g_module = {
|
||||
.name = "ch422g",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = ch422g_drivers
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -7,26 +6,14 @@ extern "C" {
|
||||
|
||||
extern Driver cst328_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(&cst328_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(&cst328_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
static Driver* const cst328_drivers[] = {
|
||||
&cst328_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module cst328_module = {
|
||||
.name = "cst328",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = cst328_drivers
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -7,26 +6,14 @@ extern "C" {
|
||||
|
||||
extern Driver cst66xx_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(&cst66xx_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(&cst66xx_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
static Driver* const cst66xx_drivers[] = {
|
||||
&cst66xx_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module cst66xx_module = {
|
||||
.name = "cst66xx",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = cst66xx_drivers
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -7,26 +6,14 @@ extern "C" {
|
||||
|
||||
extern Driver cst816s_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(&cst816s_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(&cst816s_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
static Driver* const cst816s_drivers[] = {
|
||||
&cst816s_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module cst816s_module = {
|
||||
.name = "cst816s",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
.drivers = cst816s_drivers
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user