Compare commits

...

9 Commits

Author SHA1 Message Date
Adolfo Reyna 642847be41 fix(mcp): implement startMcpScreensaver - missing method caused linker error
- Was declared in DisplayIdleService.h but not defined in clean refactored branch
- Implementation taken from original 8970143f: locks LVGL, checks overlay, ensures backlight, creates McpScreensaver
- Fixes build of feature/local-all-rebased on es3c28p
2026-07-23 22:56:17 -04:00
Adolfo Reyna 0367b95f27 feat(app): allow external apps to hide statusbar via manifest flags
- Add parseAppFlagsString() in AppManifestParsing.cpp to parse comma-separated flags (HideStatusBar, Hidden, None)
- Parse [app]flags in V1 and app.flags in V2 manifests
- Loader already supports HideStatusBar, GuiService hides statusbarWidget when set
- Enables fullscreen for ELF apps (e.g. BibleVerse, BookPlayer) without firmware recompilation of internal manifests
- Add tests for flag parsing (38 tests passing)
- Tested on es3c28p /dev/cu.usbmodem1101 @ 192.168.68.133 with HelloWorld app
2026-07-23 22:56:17 -04:00
Adolfo Reyna 17773825fa fix(audio): ES8311 BOTH complementary open + audio-stream close ref-count + mic unmute
- es8311 driver open() now allows OUTPUT->INPUT complementary (promotes to BOTH) when same native rate 44100
- audio-stream close_stream() ref-counts shared BOTH codec (don't close if other direction still open)
- MCP recordVoice + VoiceRecorder app explicitly unmute and set 100% gain

Fixes mic input not working - was returning ERROR_RESOURCE when output already open
2026-07-23 22:56:17 -04:00
Adolfo Reyna 3115a685e2 fix(files): launch mp3 player from file explorer - audio support
- SupportedFiles: add isSupportedAudioFile (.mp3/.wav/.ogg/.flac)
- Files View::viewFile: if audio file, loader->start one.tactility.mp3player with file bundle
- Tactility.cpp: hide mcpoverride (screensaver internal), keep mcpsettings visible
- Fixes MP3 no longer running from file explorer, plus audio fast playback already fixed via audio-stream (Mp3Player/BookPlayer/VoiceRecorder use audio_stream_open_output not direct i2s_controller_write)
- MCP dev coexistence already fixed: DisplayIdle lock inversion 3629ffef, WebServer serverEnabled=ws||mcp, unauth /api/mcp, video 8081/8083 vs dev 6666
2026-07-23 22:56:17 -04:00
Adolfo Reyna ad4a15dce8 feat(mcp): restore MCP service with display charging toggle
- Restore MCP system (McpSystem 1900+ LOC, McpHandler, McpScreensaver, McpSettings, McpOverride apps)
- Use audio-stream instead of direct I2S for MP3 playback (fixes fast playback, uses native resampler)
- Register McpSettings + McpOverride in Tactility.cpp so Settings shows MCP Screen
- DisplaySettings: add ScreensaverType::McpScreen and disableScreensaverWhenCharging flag
- Display app: add 'Disable on charging' toggle, handles McpScreen screensaver type
- DisplayIdle: skip screensaver while charging, support McpScreen screensaver, isDeviceCharging check via PowerDevice
- WebServer: coexistence with MCP (McpHandler)
- Avoid formatting churn from previous commits

Squashed from range eaa248b0..8970143f (6 commits) into single clean commit
2026-07-23 22:56:17 -04:00
Ken Van Hoeylandt 29e80cfd65 T-Deck Max & Pro support updates (#582)
- Created kernel driver for T-Deck Max & Pro display (needs work, doesn't refresh reliably on T-Deck Pro)
- Created T-Deck Pro device implementation (incubating)
2026-07-24 00:17:48 +02:00
Ken Van Hoeylandt 429125734a Add drivers to module and make start/stop optional (#580) 2026-07-23 23:28:09 +02:00
Shadowtrance 08eac48e64 Fix Tab5 Display Issues (#579)
And add PPA rotation to lvgl_display
2026-07-22 23:58:34 +02:00
Ken Van Hoeylandt 8b92aa8e5a GPIO descriptor fixes (#578) 2026-07-22 22:43:34 +02:00
304 changed files with 8370 additions and 3982 deletions
+2 -7
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
struct Module btt_panda_touch_module = { Module btt_panda_touch_module = {
.name = "btt-panda-touch", .name = "btt-panda-touch"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
-5
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_2432s024c_module = { Module cyd_2432s024c_module = {
.name = "cyd-2432s024c", .name = "cyd-2432s024c",
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+2 -7
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
struct Module cyd_2432s024r_module = { Module cyd_2432s024r_module = {
.name = "cyd-2432s024r", .name = "cyd-2432s024r"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+2 -7
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
struct Module cyd_2432s028r_module = { Module cyd_2432s028r_module = {
.name = "cyd-2432s028r", .name = "cyd-2432s028r"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+2 -7
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
struct Module cyd_2432s028rv3_module = { Module cyd_2432s028rv3_module = {
.name = "cyd-2432s028rv3", .name = "cyd-2432s028rv3"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -6
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_2432s032c_module = { Module cyd_2432s032c_module = {
.name = "cyd-2432s032c", .name = "cyd-2432s032c"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -6
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_3248s035c_module = { Module cyd_3248s035c_module = {
.name = "cyd-3248s035c", .name = "cyd-3248s035c"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -6
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_4848s040c_module = { Module cyd_4848s040c_module = {
.name = "cyd-4848s040c", .name = "cyd-4848s040c"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -6
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_8048s043c_module = { Module cyd_8048s043c_module = {
.name = "cyd-8048s043c", .name = "cyd-8048s043c"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -6
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_e32r28t_module = { Module cyd_e32r28t_module = {
.name = "cyd-e32r28t", .name = "cyd-e32r28t"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -6
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_e32r32p_module = { Module cyd_e32r32p_module = {
.name = "cyd-e32r32p", .name = "cyd-e32r32p"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
@@ -1,13 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module elecrow_crowpanel_advance_28_module = { Module elecrow_crowpanel_advance_28_module = {
.name = "elecrow-crowpanel-advance-28", .name = "elecrow-crowpanel-advance-28"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr
}; };
} }
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module elecrow_crowpanel_advance_35_module = { Module elecrow_crowpanel_advance_35_module = {
.name = "elecrow-crowpanel-advance-35", .name = "elecrow-crowpanel-advance-35"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
@@ -86,9 +86,7 @@
vsync-back-porch = <8>; vsync-back-porch = <8>;
vsync-front-porch = <8>; vsync-front-porch = <8>;
pclk-active-neg; pclk-active-neg;
num-fbs = <2>; bounce-buffer-size-px = <4000>;
double-fb;
bounce-buffer-size-px = <8000>;
pin-hsync = <&gpio0 40 GPIO_FLAG_NONE>; pin-hsync = <&gpio0 40 GPIO_FLAG_NONE>;
pin-vsync = <&gpio0 41 GPIO_FLAG_NONE>; pin-vsync = <&gpio0 41 GPIO_FLAG_NONE>;
pin-de = <&gpio0 42 GPIO_FLAG_NONE>; pin-de = <&gpio0 42 GPIO_FLAG_NONE>;
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module elecrow_crowpanel_advance_50_module = { Module elecrow_crowpanel_advance_50_module = {
.name = "elecrow-crowpanel-advance-50", .name = "elecrow-crowpanel-advance-50"
.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> #include <tactility/module.h>
extern "C" { extern "C" {
Module elecrow_crowpanel_basic_28_module = { Module elecrow_crowpanel_basic_28_module = {
.name = "elecrow-crowpanel-basic-28", .name = "elecrow-crowpanel-basic-28"
.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> #include <tactility/module.h>
extern "C" { extern "C" {
Module elecrow_crowpanel_basic_35_module = { Module elecrow_crowpanel_basic_35_module = {
.name = "elecrow-crowpanel-basic-35", .name = "elecrow-crowpanel-basic-35"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
@@ -89,9 +89,7 @@
vsync-back-porch = <12>; vsync-back-porch = <12>;
vsync-front-porch = <8>; vsync-front-porch = <8>;
pclk-active-neg; pclk-active-neg;
num-fbs = <2>; bounce-buffer-size-px = <4000>;
double-fb;
bounce-buffer-size-px = <8000>;
pin-hsync = <&gpio0 39 GPIO_FLAG_NONE>; pin-hsync = <&gpio0 39 GPIO_FLAG_NONE>;
pin-vsync = <&gpio0 41 GPIO_FLAG_NONE>; pin-vsync = <&gpio0 41 GPIO_FLAG_NONE>;
pin-de = <&gpio0 40 GPIO_FLAG_NONE>; pin-de = <&gpio0 40 GPIO_FLAG_NONE>;
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module elecrow_crowpanel_basic_50_module = { Module elecrow_crowpanel_basic_50_module = {
.name = "elecrow-crowpanel-basic-50", .name = "elecrow-crowpanel-basic-50"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -6
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module es3c28p_module = { Module es3c28p_module = {
.name = "es3c28p", .name = "es3c28p"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -6
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module generic_esp32_module = { Module generic_esp32_module = {
.name = "generic-esp32", .name = "generic-esp32"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -6
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module generic_esp32c6_module = { Module generic_esp32c6_module = {
.name = "generic-esp32c6", .name = "generic-esp32c6"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -6
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module generic_esp32p4_module = { Module generic_esp32p4_module = {
.name = "generic-esp32p4", .name = "generic-esp32p4"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -6
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module generic_esp32s3_module = { Module generic_esp32s3_module = {
.name = "generic-esp32s3", .name = "generic-esp32s3"
.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> #include <tactility/module.h>
extern "C" { extern "C" {
Module guition_jc1060p470ciwy_module = { Module guition_jc1060p470ciwy_module = {
.name = "guition-jc1060p470ciwy", .name = "guition-jc1060p470ciwy"
.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> #include <tactility/module.h>
extern "C" { extern "C" {
Module guition_jc2432w328c_module = { Module guition_jc2432w328c_module = {
.name = "guition-jc2432w328c", .name = "guition-jc2432w328c"
.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> #include <tactility/module.h>
extern "C" { extern "C" {
Module guition_jc8048w550c_module = { Module guition_jc8048w550c_module = {
.name = "guition-jc8048w550c", .name = "guition-jc8048w550c"
.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> #include <tactility/module.h>
extern "C" { extern "C" {
Module heltec_wifi_lora_32_v3_module = { Module heltec_wifi_lora_32_v3_module = {
.name = "heltec-wifi-lora-32-v3", .name = "heltec-wifi-lora-32-v3"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -1
View File
@@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
INCLUDE_DIRS "source" INCLUDE_DIRS "source"
REQUIRES Tactility GDEQ031T10 driver REQUIRES TactilityKernel
) )
+8 -5
View File
@@ -1,6 +1,6 @@
general.vendor=LilyGO general.vendor=LilyGO
general.name=T-Deck Max 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 general.incubating=true
apps.launcherAppId=Launcher 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. # Internal until the SD card is verified working on this board; switch to SD then.
storage.userDataLocation=Internal storage.userDataLocation=Internal
dependencies.useDeprecatedHal=false
display.size=3.1" display.size=3.1"
display.shape=rectangle display.shape=rectangle
display.dpi=128 display.dpi=128
# The GDEQ031T10 e-paper is monochrome, but its driver renders via lvgl.colorDepth=8
# 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
# Monochrome theme: the default colour theme renders accent-coloured UI that # Monochrome theme: the default colour theme renders accent-coloured UI that
# thresholds to invisible on a 1bpp panel (enables CONFIG_LV_USE_THEME_MONO). # thresholds to invisible on a 1bpp panel (enables CONFIG_LV_USE_THEME_MONO).
lvgl.theme=Mono lvgl.theme=Mono
cdn.warningMessage=Display not reliably working. Use at your own risk.
sdkconfig.CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=0
+1
View File
@@ -2,6 +2,7 @@ dependencies:
- Platforms/platform-esp32 - Platforms/platform-esp32
- Drivers/xl9555-module - Drivers/xl9555-module
- Drivers/cst66xx-module - Drivers/cst66xx-module
- Drivers/gdeq031t10-module
- Drivers/tca8418-module - Drivers/tca8418-module
- Drivers/sy6970-module - Drivers/sy6970-module
- Drivers/bq27220-module - Drivers/bq27220-module
@@ -11,6 +11,7 @@
#include <tactility/bindings/pwm_backlight.h> #include <tactility/bindings/pwm_backlight.h>
#include <bindings/xl9555.h> #include <bindings/xl9555.h>
#include <bindings/cst66xx.h> #include <bindings/cst66xx.h>
#include <bindings/gdeq031t10.h>
#include <bindings/tca8418.h> #include <bindings/tca8418.h>
#include <bindings/sy6970.h> #include <bindings/sy6970.h>
#include <bindings/bq27220.h> #include <bindings/bq27220.h>
@@ -143,6 +144,17 @@
// exceeds the default ~4 KB transfer limit. Raise the bus limit to fit it. // exceeds the default ~4 KB transfer limit. Raise the bus limit to fit it.
max-transfer-size = <65536>; 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 { sdcard@1 {
compatible = "espressif,esp32-sdspi"; compatible = "espressif,esp32-sdspi";
status = "disabled"; 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 -16
View File
@@ -2,22 +2,8 @@
extern "C" { extern "C" {
static error_t start() { Module lilygo_tdeck_max_module = {
// Empty for now .name = "lilygo-tdeck-max"
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
}; };
} }
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M hardware.esptoolFlashFreq=120M
hardware.bluetooth=true hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD storage.userDataLocation=SD
display.size=2.8" display.size=2.8"
@@ -19,9 +19,6 @@
constexpr auto* TAG = "tdeck-plus"; constexpr auto* TAG = "tdeck-plus";
// Legacy placeholder (required until legacy HAL is cleaned up everywhere)
extern const tt::hal::Configuration hardwareConfiguration = {};
extern "C" { extern "C" {
void subscribe_events() { void subscribe_events() {
+7
View File
@@ -0,0 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "source"
REQUIRES TactilityKernel
)
@@ -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
View 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
@@ -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>;
};
};
};
@@ -0,0 +1,9 @@
#include <tactility/module.h>
extern "C" {
struct Module lilygo_tdeck_pro_module = {
.name = "lilygo-tdeck-pro"
};
}
+2
View File
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M hardware.esptoolFlashFreq=120M
hardware.bluetooth=true hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD storage.userDataLocation=SD
display.size=2.8" display.size=2.8"
+1 -6
View File
@@ -18,9 +18,6 @@
constexpr auto* TAG = "tdeck"; constexpr auto* TAG = "tdeck";
// Legacy placeholder (required until legacy HAL is cleaned up everywhere)
extern const tt::hal::Configuration hardwareConfiguration = {};
extern "C" { extern "C" {
void subscribe_events() { void subscribe_events() {
@@ -63,9 +60,7 @@ static error_t stop() {
Module lilygo_tdeck_module = { Module lilygo_tdeck_module = {
.name = "lilygo-tdeck", .name = "lilygo-tdeck",
.start = start, .start = start,
.stop = stop, .stop = stop
.symbols = nullptr,
.internal = nullptr
}; };
} }
+6 -5
View File
@@ -30,16 +30,17 @@ static error_t start() {
} }
static error_t stop() { static error_t stop() {
// Empty for now if (gpio_set_level(POWER_ON_PIN, 0) != ESP_OK) {
return ERROR_RESOURCE;
}
return ERROR_NONE; return ERROR_NONE;
} }
struct Module lilygo_tdisplay_s3_module = { Module lilygo_tdisplay_s3_module = {
.name = "lilygo-tdisplay-s3", .name = "lilygo-tdisplay-s3",
.start = start, .start = start,
.stop = stop, .stop = stop
.symbols = nullptr,
.internal = nullptr
}; };
} }
+2 -16
View File
@@ -2,22 +2,8 @@
extern "C" { extern "C" {
static error_t start() { Module lilygo_tdisplay_module = {
// Empty for now .name = "lilygo-tdisplay"
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
}; };
} }
+2 -16
View File
@@ -2,22 +2,8 @@
extern "C" { extern "C" {
static error_t start() { Module lilygo_tdongle_s3_module = {
// Empty for now .name = "lilygo-tdongle-s3"
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
}; };
} }
+2 -16
View File
@@ -2,22 +2,8 @@
extern "C" { extern "C" {
static error_t start() { Module lilygo_thmi_module = {
// Empty for now .name = "lilygo-thmi"
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
}; };
} }
@@ -13,6 +13,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=40M hardware.esptoolFlashFreq=40M
hardware.bluetooth=true hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD storage.userDataLocation=SD
display.size=2.33" display.size=2.33"
+8 -16
View File
@@ -15,18 +15,15 @@
constexpr auto* TAG = "T-Lora Pager"; 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" { extern "C" {
static void subscribe_events() { tt::kernel::SystemEventSubscription event_subscription;
static error_t start() {
LOG_I(TAG, LOG_MESSAGE_POWER_ON_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 // 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. // registers it as an LVGL input device, which requires LVGL to be up first.
lvgl_lock(); lvgl_lock();
@@ -50,23 +47,18 @@ static void subscribe_events() {
} }
} }
}); });
}
static error_t start() {
subscribe_events();
return ERROR_NONE; return ERROR_NONE;
} }
static error_t stop() { static error_t stop() {
tt::kernel::unsubscribeSystemEvent(event_subscription);
return ERROR_NONE; return ERROR_NONE;
} }
struct Module lilygo_tlora_pager_module = { Module lilygo_tlora_pager_module = {
.name = "lilygo-tlora-pager", .name = "lilygo-tlora-pager",
.start = start, .start = start,
.stop = stop, .stop = stop
.symbols = nullptr,
.internal = nullptr
}; };
} }
@@ -2,22 +2,8 @@
extern "C" { extern "C" {
static error_t start() { Module m5stack_cardputer_adv_module = {
// Empty for now .name = "m5stack-cardputer-adv"
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
}; };
} }
+1 -15
View File
@@ -2,22 +2,8 @@
extern "C" { 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 = { Module m5stack_cardputer_module = {
.name = "m5stack-cardputer", .name = "m5stack-cardputer"
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -6
View File
@@ -1,4 +1,3 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
@@ -7,11 +6,7 @@ extern "C" {
// and axp192-module's start()), replacing what used to be done by hand here via a // and axp192-module's start()), replacing what used to be done by hand here via a
// DEVICE_EVENT_STARTED device_listener. // DEVICE_EVENT_STARTED device_listener.
Module m5stack_core2_module = { Module m5stack_core2_module = {
.name = "m5stack-core2", .name = "m5stack-core2"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
@@ -205,5 +205,4 @@
backlight = <&display_backlight>; backlight = <&display_backlight>;
}; };
}; };
}; };
+2 -9
View File
@@ -1,16 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
#include <cstring>
extern "C" { extern "C" {
struct Module m5stack_cores3_module = { Module m5stack_cores3_module = {
.name = "m5stack-cores3", .name = "m5stack-cores3"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
@@ -1,8 +1,10 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "papers3_power.h" #include "papers3_power.h"
#include <tactility/check.h> #include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/drivers/gpio.h>
#include <tactility/drivers/gpio_controller.h> #include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/power_supply.h> #include <tactility/drivers/power_supply.h>
#include <tactility/drivers/pwm.h> #include <tactility/drivers/pwm.h>
@@ -13,7 +15,7 @@
#include <new> #include <new>
#define TAG "Papers3Power" constexpr auto* TAG = "Papers3Power";
#define GET_CONFIG(device) (static_cast<const Papers3PowerConfig*>((device)->config)) #define GET_CONFIG(device) (static_cast<const Papers3PowerConfig*>((device)->config))
// Power-off signal timing, ported from the old deprecated-HAL PaperS3Power::powerOff(). // Power-off signal timing, ported from the old deprecated-HAL PaperS3Power::powerOff().
@@ -192,17 +194,8 @@ static void destroy_power_supply_child(Device* child) {
// region Driver lifecycle // region Driver lifecycle
static error_t acquire_input(const GpioPinSpec& pin, GpioDescriptor** out_descriptor) { static error_t acquire_input(const GpioPinSpec& pin, GpioDescriptor** out_descriptor) {
auto* descriptor = gpio_descriptor_acquire(pin.gpio_controller, pin.pin, GPIO_OWNER_GPIO); *out_descriptor = gpio_descriptor_acquire(pin.gpio_controller, pin.pin, pin.flags | GPIO_FLAG_DIRECTION_INPUT, GPIO_OWNER_GPIO);
if (descriptor == nullptr) { return (*out_descriptor != nullptr) ? ERROR_NONE : ERROR_RESOURCE;
return ERROR_RESOURCE;
}
error_t error = gpio_descriptor_set_flags(descriptor, pin.flags | GPIO_FLAG_DIRECTION_INPUT);
if (error != ERROR_NONE) {
gpio_descriptor_release(descriptor);
return error;
}
*out_descriptor = descriptor;
return ERROR_NONE;
} }
static error_t start(Device* device) { static error_t start(Device* device) {
@@ -226,13 +219,8 @@ static error_t start(Device* device) {
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->power_off_descriptor = gpio_descriptor_acquire(config->pin_power_off.gpio_controller, config->pin_power_off.pin, GPIO_OWNER_GPIO); internal->power_off_descriptor = gpio_descriptor_acquire(config->pin_power_off.gpio_controller, config->pin_power_off.pin, config->pin_power_off.flags | GPIO_FLAG_DIRECTION_OUTPUT, GPIO_OWNER_GPIO);
if (internal->power_off_descriptor == nullptr || if (internal->power_off_descriptor == nullptr) {
gpio_descriptor_set_flags(internal->power_off_descriptor, config->pin_power_off.flags | GPIO_FLAG_DIRECTION_OUTPUT) != ERROR_NONE) {
LOG_E(TAG, "Failed to configure power-off pin");
if (internal->power_off_descriptor != nullptr) {
gpio_descriptor_release(internal->power_off_descriptor);
}
gpio_descriptor_release(internal->usb_detect_descriptor); gpio_descriptor_release(internal->usb_detect_descriptor);
gpio_descriptor_release(internal->charge_status_descriptor); gpio_descriptor_release(internal->charge_status_descriptor);
delete internal; delete internal;
+7 -19
View File
@@ -1,6 +1,4 @@
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
@@ -9,26 +7,16 @@ extern Driver papers3_power_driver;
extern Driver papers3_power_supply_driver; extern Driver papers3_power_supply_driver;
extern Driver papers3_display_driver; extern Driver papers3_display_driver;
static error_t start() { static Driver* const papers3_drivers[] = {
check(driver_construct_add(&papers3_power_supply_driver) == ERROR_NONE); &papers3_display_driver,
check(driver_construct_add(&papers3_power_driver) == ERROR_NONE); &papers3_power_driver,
check(driver_construct_add(&papers3_display_driver) == ERROR_NONE); &papers3_power_supply_driver,
return ERROR_NONE; nullptr
} };
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;
}
Module m5stack_papers3_module = { Module m5stack_papers3_module = {
.name = "m5stack-papers3", .name = "m5stack-papers3",
.start = start, .drivers = papers3_drivers
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }
@@ -7,8 +7,10 @@
#include <tactility/bindings/esp32_grove.h> #include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h> #include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_uart.h> #include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/gpio_hog.h>
#include <bindings/aw88298.h> #include <bindings/aw88298.h>
#include <bindings/aw9523b.h> #include <bindings/aw9523b.h>
#include <bindings/axp2101.h> #include <bindings/axp2101.h>
@@ -20,8 +22,6 @@
#include <bindings/ili9341.h> #include <bindings/ili9341.h>
#include <bindings/ina226.h> #include <bindings/ina226.h>
#include <bindings/py32ioexpander.h> #include <bindings/py32ioexpander.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/gpio_hog.h>
// Reference: https://docs.m5stack.com/en/StackChan // Reference: https://docs.m5stack.com/en/StackChan
/ { / {
@@ -101,7 +101,7 @@
aw9523_sdcard_switch { aw9523_sdcard_switch {
compatible = "gpio-hog"; compatible = "gpio-hog";
pin = <&aw9523b 4 GPIO_FLAG_NONE>; pin = <&aw9523b 4 GPIO_FLAG_NONE>;
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>; mode = <GPIO_HOG_MODE_INPUT>;
}; };
aw9523_boost_enable { aw9523_boost_enable {
+1 -3
View File
@@ -56,9 +56,7 @@ static error_t stop() {
Module m5stack_stackchan_module = { Module m5stack_stackchan_module = {
.name = "m5stack-stackchan", .name = "m5stack-stackchan",
.start = start, .start = start,
.stop = stop, .stop = stop
.symbols = nullptr,
.internal = nullptr
}; };
} }
+2 -15
View File
@@ -7,7 +7,7 @@ constexpr auto* TAG = "StickCPlus2";
extern "C" { extern "C" {
static bool init_power() { static error_t init_power() {
// CH552 applies 4 V to GPIO 0, which reduces Wi-Fi sensitivity. // 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: // Setting output to high adds a bias of 3.3 V and suppresses over-voltage:
gpio_set_direction(GPIO_NUM_0, GPIO_MODE_OUTPUT); 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_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT);
gpio_set_level(GPIO_NUM_4, 1); 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; return ERROR_NONE;
} }
Module m5stack_stickc_plus2_module = { Module m5stack_stickc_plus2_module = {
.name = "m5stack-stickc-plus2", .name = "m5stack-stickc-plus2",
.start = start, .start = init_power
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }
+1 -6
View File
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module m5stack_sticks3_module = { Module m5stack_sticks3_module = {
.name = "m5stack-sticks3", .name = "m5stack-sticks3"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
@@ -36,14 +36,14 @@ static void apply_gt911_int_workaround() {
LOG_W(TAG, "display_detect: gpio0 not found, GT911 INT workaround not applied"); LOG_W(TAG, "display_detect: gpio0 not found, GT911 INT workaround not applied");
return; return;
} }
auto* int_pin = gpio_descriptor_acquire(gpio0, 23, GPIO_OWNER_GPIO); auto* int_pin = gpio_descriptor_acquire(gpio0, 23, GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_ACTIVE_LOW, GPIO_OWNER_GPIO);
device_put(gpio0); device_put(gpio0);
if (int_pin == nullptr) { if (int_pin == nullptr) {
LOG_W(TAG, "display_detect: failed to acquire GPIO23 for GT911 INT workaround"); LOG_W(TAG, "display_detect: failed to acquire GPIO23 for GT911 INT workaround");
return; return;
} }
gpio_descriptor_set_flags(int_pin, GPIO_FLAG_DIRECTION_OUTPUT | GPIO_FLAG_PULL_UP); gpio_descriptor_set_flags(int_pin, GPIO_FLAG_DIRECTION_OUTPUT | GPIO_FLAG_PULL_UP);
gpio_descriptor_set_level(int_pin, false); gpio_descriptor_set_level(int_pin, true);
gpio_descriptor_release(int_pin); gpio_descriptor_release(int_pin);
} }
@@ -67,8 +67,6 @@ static void create_gt911_touch(Device* i2c0) {
// Reset is pulsed via io_expander0 (detect.cpp's pulse_display_reset_pins), not a direct SoC GPIO. // Reset is pulsed via io_expander0 (detect.cpp's pulse_display_reset_pins), not a direct SoC GPIO.
.pin_reset = GPIO_PIN_SPEC_NONE, .pin_reset = GPIO_PIN_SPEC_NONE,
.pin_interrupt = GPIO_PIN_SPEC_NONE, .pin_interrupt = GPIO_PIN_SPEC_NONE,
.reset_active_high = false,
.interrupt_active_high = false,
}; };
gt911_device.config = &gt911_config; gt911_device.config = &gt911_config;
@@ -103,7 +101,6 @@ void tab5_create_devices_v1(Device* i2c0) {
// LCD reset is pulsed via io_expander0 (detect.cpp's pulse_display_reset_pins), not a // LCD reset is pulsed via io_expander0 (detect.cpp's pulse_display_reset_pins), not a
// direct SoC GPIO. // direct SoC GPIO.
.pin_reset = GPIO_PIN_SPEC_NONE, .pin_reset = GPIO_PIN_SPEC_NONE,
.reset_active_high = false,
.ldo_channel = 3, .ldo_channel = 3,
.ldo_voltage_mv = 2500, .ldo_voltage_mv = 2500,
.dsi_bus_id = 0, .dsi_bus_id = 0,
@@ -116,10 +113,10 @@ void tab5_create_devices_v1(Device* i2c0) {
.vsync_pulse_width = 4, .vsync_pulse_width = 4,
.vsync_back_porch = 20, .vsync_back_porch = 20,
.vsync_front_porch = 20, .vsync_front_porch = 20,
.num_fbs = 1, .num_fbs = 2,
.use_dma2d = false, .use_dma2d = true,
.disable_lp = false, .disable_lp = false,
.allow_tearing = true, // matches old lvgl_port_display_dsi_cfg_t.avoid_tearing = 0 .allow_tearing = true, // matches old lvgl_port_display_dsi_cfg_t.avoid_tearing = 0 (disabled = don't wait)
.init_sequence = display_init_bytes.data(), .init_sequence = display_init_bytes.data(),
.init_sequence_length = (uint32_t)display_init_bytes.size(), .init_sequence_length = (uint32_t)display_init_bytes.size(),
.backlight = backlight, .backlight = backlight,
@@ -50,8 +50,6 @@ static void create_st7123_touch(Device* i2c0) {
// Reset is pulsed via io_expander0 (detect.cpp's pulse_display_reset_pins), not a direct SoC GPIO. // Reset is pulsed via io_expander0 (detect.cpp's pulse_display_reset_pins), not a direct SoC GPIO.
.pin_reset = GPIO_PIN_SPEC_NONE, .pin_reset = GPIO_PIN_SPEC_NONE,
.pin_interrupt = pin_interrupt, .pin_interrupt = pin_interrupt,
.reset_active_high = false,
.interrupt_active_high = false,
}; };
st7123_touch_device.config = &st7123_touch_config; st7123_touch_device.config = &st7123_touch_config;
@@ -82,7 +80,6 @@ void tab5_create_devices_v2(Device* i2c0) {
.mirror_x = false, .mirror_x = false,
.mirror_y = false, .mirror_y = false,
.pin_reset = GPIO_PIN_SPEC_NONE, .pin_reset = GPIO_PIN_SPEC_NONE,
.reset_active_high = false,
.ldo_channel = 3, .ldo_channel = 3,
.ldo_voltage_mv = 2500, .ldo_voltage_mv = 2500,
.dsi_bus_id = 0, .dsi_bus_id = 0,
@@ -95,10 +92,10 @@ void tab5_create_devices_v2(Device* i2c0) {
.vsync_pulse_width = 2, .vsync_pulse_width = 2,
.vsync_back_porch = 8, .vsync_back_porch = 8,
.vsync_front_porch = 220, .vsync_front_porch = 220,
.num_fbs = 1, .num_fbs = 2,
.use_dma2d = false, .use_dma2d = true,
.disable_lp = false, .disable_lp = false,
.allow_tearing = true, .allow_tearing = true, // matches old lvgl_port_display_dsi_cfg_t.avoid_tearing = 0 (disabled = don't wait)
.init_sequence = display_init_bytes.data(), .init_sequence = display_init_bytes.data(),
.init_sequence_length = (uint32_t)display_init_bytes.size(), .init_sequence_length = (uint32_t)display_init_bytes.size(),
.backlight = backlight, .backlight = backlight,
@@ -1,6 +1,5 @@
#include "detect.h" #include "detect.h"
#include "../../../../TactilityKernel/include/tactility/log.h"
#include "devices_common.h" #include "devices_common.h"
#include "devices_v1.h" #include "devices_v1.h"
#include "devices_v2.h" #include "devices_v2.h"
@@ -8,6 +7,7 @@
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/device_listener.h> #include <tactility/device_listener.h>
#include <tactility/drivers/gpio.h>
#include <tactility/drivers/gpio_controller.h> #include <tactility/drivers/gpio_controller.h>
#include <tactility/log.h> #include <tactility/log.h>
@@ -36,8 +36,8 @@ constexpr auto GPIO_EXP0_PIN_LCD_RESET = 4;
constexpr auto GPIO_EXP0_PIN_TOUCH_RESET = 5; constexpr auto GPIO_EXP0_PIN_TOUCH_RESET = 5;
bool pulse_display_reset_pins(Device* io_expander0) { bool pulse_display_reset_pins(Device* io_expander0) {
auto* lcd_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_LCD_RESET, GPIO_OWNER_GPIO); auto* lcd_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_LCD_RESET, GPIO_FLAG_DIRECTION_OUTPUT | GPIO_FLAG_ACTIVE_LOW, GPIO_OWNER_GPIO);
auto* touch_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_TOUCH_RESET, GPIO_OWNER_GPIO); auto* touch_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_TOUCH_RESET, GPIO_FLAG_DIRECTION_OUTPUT | GPIO_FLAG_ACTIVE_LOW, GPIO_OWNER_GPIO);
if (lcd_reset_pin == nullptr || touch_reset_pin == nullptr) { if (lcd_reset_pin == nullptr || touch_reset_pin == nullptr) {
LOG_E(TAG, "display_detect: failed to acquire LCD/touch reset pins on io_expander0"); LOG_E(TAG, "display_detect: failed to acquire LCD/touch reset pins on io_expander0");
if (lcd_reset_pin != nullptr) { if (lcd_reset_pin != nullptr) {
@@ -51,11 +51,11 @@ bool pulse_display_reset_pins(Device* io_expander0) {
gpio_descriptor_set_flags(lcd_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT); gpio_descriptor_set_flags(lcd_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_flags(touch_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT); gpio_descriptor_set_flags(touch_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_level(lcd_reset_pin, false);
gpio_descriptor_set_level(touch_reset_pin, false);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_descriptor_set_level(lcd_reset_pin, true); gpio_descriptor_set_level(lcd_reset_pin, true);
gpio_descriptor_set_level(touch_reset_pin, true); gpio_descriptor_set_level(touch_reset_pin, true);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_descriptor_set_level(lcd_reset_pin, false);
gpio_descriptor_set_level(touch_reset_pin, false);
gpio_descriptor_release(lcd_reset_pin); gpio_descriptor_release(lcd_reset_pin);
gpio_descriptor_release(touch_reset_pin); gpio_descriptor_release(touch_reset_pin);
@@ -1,6 +1,7 @@
#include "tab5_headphone_detect.h" #include "tab5_headphone_detect.h"
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/drivers/gpio.h>
#include <tactility/drivers/gpio_controller.h> #include <tactility/drivers/gpio_controller.h>
#include <tactility/log.h> #include <tactility/log.h>
@@ -35,7 +36,7 @@ static void headphone_detect_callback(TimerHandle_t /*timer*/) {
return; // Not ready yet, will retry on next tick return; // Not ready yet, will retry on next tick
} }
auto* hp_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_HEADPHONE_DETECT, GPIO_OWNER_GPIO); auto* hp_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_HEADPHONE_DETECT, GPIO_FLAG_DIRECTION_INPUT, GPIO_OWNER_GPIO);
if (!hp_pin) { if (!hp_pin) {
LOG_W(TAG, "hp_detect: HP_DET pin busy"); LOG_W(TAG, "hp_detect: HP_DET pin busy");
return; return;
@@ -53,7 +54,7 @@ static void headphone_detect_callback(TimerHandle_t /*timer*/) {
LOG_D(TAG, "hp_detect: HP_DET=%d", (int)hp); LOG_D(TAG, "hp_detect: HP_DET=%d", (int)hp);
if (!hp_detect_initialized || hp != hp_detect_last) { if (!hp_detect_initialized || hp != hp_detect_last) {
auto* spk_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO); auto* spk_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_FLAG_DIRECTION_OUTPUT, GPIO_OWNER_GPIO);
if (!spk_pin) { if (!spk_pin) {
LOG_W(TAG, "hp_detect: SPK_EN pin busy, will retry"); LOG_W(TAG, "hp_detect: SPK_EN pin busy, will retry");
return; return;
@@ -3,6 +3,7 @@
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/drivers/gpio.h>
#include <tactility/drivers/gpio_controller.h> #include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/power_supply.h> #include <tactility/drivers/power_supply.h>
#include <tactility/log.h> #include <tactility/log.h>
@@ -51,15 +52,12 @@ static error_t ps_set_allowed_to_charge(Device* device, bool allowed) {
auto* internal = static_cast<Tab5PowerControlInternal*>(device_get_driver_data(device)); auto* internal = static_cast<Tab5PowerControlInternal*>(device_get_driver_data(device));
auto* io_expander1 = device_get_parent(device); auto* io_expander1 = device_get_parent(device);
auto* pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_CHG_EN, GPIO_OWNER_GPIO); auto* pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_CHG_EN, GPIO_FLAG_DIRECTION_OUTPUT, GPIO_OWNER_GPIO);
if (pin == nullptr) { if (pin == nullptr) {
LOG_W(TAG, "Failed to acquire CHG_EN pin"); LOG_W(TAG, "Failed to acquire CHG_EN pin");
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
error_t error = gpio_descriptor_set_flags(pin, GPIO_FLAG_DIRECTION_OUTPUT); auto error = gpio_descriptor_set_level(pin, allowed);
if (error == ERROR_NONE) {
error = gpio_descriptor_set_level(pin, allowed);
}
gpio_descriptor_release(pin); gpio_descriptor_release(pin);
if (error != ERROR_NONE) { if (error != ERROR_NONE) {
LOG_W(TAG, "Failed to set CHG_EN pin"); LOG_W(TAG, "Failed to set CHG_EN pin");
@@ -83,15 +81,12 @@ static error_t ps_set_quick_charge_enabled(Device* device, bool enabled) {
auto* internal = static_cast<Tab5PowerControlInternal*>(device_get_driver_data(device)); auto* internal = static_cast<Tab5PowerControlInternal*>(device_get_driver_data(device));
auto* io_expander1 = device_get_parent(device); auto* io_expander1 = device_get_parent(device);
auto* pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_NCHG_QC_EN, GPIO_OWNER_GPIO); auto* pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_NCHG_QC_EN, GPIO_FLAG_DIRECTION_OUTPUT, GPIO_OWNER_GPIO);
if (pin == nullptr) { if (pin == nullptr) {
LOG_W(TAG, "Failed to acquire nCHG_QC_EN pin"); LOG_W(TAG, "Failed to acquire nCHG_QC_EN pin");
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
error_t error = gpio_descriptor_set_flags(pin, GPIO_FLAG_DIRECTION_OUTPUT); auto error = gpio_descriptor_set_level(pin, !enabled); // active-low
if (error == ERROR_NONE) {
error = gpio_descriptor_set_level(pin, !enabled); // active-low
}
gpio_descriptor_release(pin); gpio_descriptor_release(pin);
if (error != ERROR_NONE) { if (error != ERROR_NONE) {
LOG_W(TAG, "Failed to set nCHG_QC_EN pin"); LOG_W(TAG, "Failed to set nCHG_QC_EN pin");
@@ -108,7 +103,7 @@ static bool ps_supports_power_off(Device*) {
static error_t ps_power_off(Device* device) { static error_t ps_power_off(Device* device) {
auto* io_expander1 = device_get_parent(device); auto* io_expander1 = device_get_parent(device);
auto* pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_DEVICE_POWER, GPIO_OWNER_GPIO); auto* pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_DEVICE_POWER, GPIO_FLAG_DIRECTION_OUTPUT, GPIO_OWNER_GPIO);
if (pin == nullptr) { if (pin == nullptr) {
LOG_E(TAG, "Failed to acquire DEVICE_POWER pin"); LOG_E(TAG, "Failed to acquire DEVICE_POWER pin");
return ERROR_RESOURCE; return ERROR_RESOURCE;
@@ -153,9 +148,8 @@ static error_t start(Device* device) {
// as an idle-low output up front so it's in a known state (previously done by Configuration.cpp's // as an idle-low output up front so it's in a known state (previously done by Configuration.cpp's
// initExpander1(), now owned here - see that function's comment for why it no longer touches it). // initExpander1(), now owned here - see that function's comment for why it no longer touches it).
auto* io_expander1 = device_get_parent(device); auto* io_expander1 = device_get_parent(device);
auto* device_power_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_DEVICE_POWER, GPIO_OWNER_GPIO); auto* device_power_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_DEVICE_POWER, GPIO_FLAG_DIRECTION_OUTPUT, GPIO_OWNER_GPIO);
if (device_power_pin != nullptr) { if (device_power_pin != nullptr) {
gpio_descriptor_set_flags(device_power_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_level(device_power_pin, false); gpio_descriptor_set_level(device_power_pin, false);
gpio_descriptor_release(device_power_pin); gpio_descriptor_release(device_power_pin);
} else { } else {
+12 -27
View File
@@ -1,4 +1,5 @@
#include <tactility/module.h> #include <tactility/module.h>
#include <tactility/check.h> #include <tactility/check.h>
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/device_listener.h> #include <tactility/device_listener.h>
@@ -15,7 +16,7 @@
#include "devices/tab5_power_control.h" #include "devices/tab5_power_control.h"
#include "devices/tab_5_camera.h" #include "devices/tab_5_camera.h"
#define TAG "Tab5" constexpr auto* TAG = "Tab5";
// PI4IOE5V6408-0 (0x43) bit 1 // PI4IOE5V6408-0 (0x43) bit 1
constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1; constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1;
@@ -25,14 +26,11 @@ constexpr auto GPIO_EXP0_PIN_HEADPHONE_DETECT = 7;
static void tab5_init_expander0(Device* io_expander0) { static void tab5_init_expander0(Device* io_expander0) {
// Speaker and heapdhone pins are managed at runtime, so we can't have them as a hog in the dts file // Speaker and heapdhone pins are managed at runtime, so we can't have them as a hog in the dts file
// We have to init them manually. // We have to init them manually.
auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO); auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_FLAG_DIRECTION_OUTPUT, GPIO_OWNER_GPIO);
check(speaker_enable_pin); check(speaker_enable_pin);
auto* headphone_detect_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_HEADPHONE_DETECT, GPIO_OWNER_GPIO); auto* headphone_detect_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_HEADPHONE_DETECT, GPIO_FLAG_DIRECTION_INPUT, GPIO_OWNER_GPIO);
check(headphone_detect_pin); check(headphone_detect_pin);
gpio_descriptor_set_flags(speaker_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_flags(headphone_detect_pin, GPIO_FLAG_DIRECTION_INPUT);
gpio_descriptor_set_level(speaker_enable_pin, false); gpio_descriptor_set_level(speaker_enable_pin, false);
gpio_descriptor_release(speaker_enable_pin); gpio_descriptor_release(speaker_enable_pin);
@@ -40,7 +38,7 @@ static void tab5_init_expander0(Device* io_expander0) {
} }
static void tab5_enable_speaker_amp(Device* io_expander0) { static void tab5_enable_speaker_amp(Device* io_expander0) {
auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO); auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_FLAG_DIRECTION_OUTPUT, GPIO_OWNER_GPIO);
check(speaker_enable_pin, "Failed to acquire speaker enable pin"); check(speaker_enable_pin, "Failed to acquire speaker enable pin");
error_t error = gpio_descriptor_set_level(speaker_enable_pin, true); error_t error = gpio_descriptor_set_level(speaker_enable_pin, true);
gpio_descriptor_release(speaker_enable_pin); gpio_descriptor_release(speaker_enable_pin);
@@ -67,44 +65,31 @@ static void on_io_expander0_started(Device* device, DeviceEvent event, void* con
extern "C" { extern "C" {
static error_t start() { 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_detect_start();
tab5_camera_init(); tab5_camera_init();
device_listener_add(on_io_expander0_started, nullptr); device_listener_add(on_io_expander0_started, nullptr);
tab5_headphone_detect_start(); tab5_headphone_detect_start();
return ERROR_NONE; return ERROR_NONE;
} }
static error_t stop() { static error_t stop() {
tab5_headphone_detect_stop(); tab5_headphone_detect_stop();
device_listener_remove(on_io_expander0_started); device_listener_remove(on_io_expander0_started);
tab5_detect_stop(); 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; return ERROR_NONE;
} }
static Driver* const tab5_drivers[] = {
&tab5_keyboard_driver,
&tab5_power_control_driver,
nullptr
};
Module m5stack_tab5_module = { Module m5stack_tab5_module = {
.name = "m5stack-tab5", .name = "m5stack-tab5",
.start = start, .start = start,
.stop = stop, .stop = stop,
.symbols = nullptr, .drivers = tab5_drivers
.internal = nullptr
}; };
} }
+1 -2
View File
@@ -46,8 +46,7 @@ sdkconfig.CONFIG_ESP_HOSTED_ENABLE_PEER_DATA_TRANSFER=y
sdkconfig.CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS=8 sdkconfig.CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS=8
# Performance: larger L2 cache reduces PSRAM stalls for draw/DPI buffers # Performance: larger L2 cache reduces PSRAM stalls for draw/DPI buffers
sdkconfig.CONFIG_CACHE_L2_CACHE_256KB=y sdkconfig.CONFIG_CACHE_L2_CACHE_256KB=y
# Performance: use P4's PPA (pixel processing accelerator for rotation) # Performance: cache-line-aligned draw buffers, required for PPA/DMA2D coherency (see lvgl-module's lvgl_display_alloc_buffer())
sdkconfig.CONFIG_LVGL_PORT_ENABLE_PPA=y
sdkconfig.CONFIG_LV_DRAW_BUF_ALIGN=64 sdkconfig.CONFIG_LV_DRAW_BUF_ALIGN=64
sdkconfig.CONFIG_LV_DEF_REFR_PERIOD=15 sdkconfig.CONFIG_LV_DEF_REFR_PERIOD=15
# SC202CS (SC2356) MIPI CSI camera sensor + ISP pipeline (AE/AWB/demosaicing) # SC202CS (SC2356) MIPI CSI camera sensor + ISP pipeline (AE/AWB/demosaicing)
+1 -3
View File
@@ -21,10 +21,8 @@ bool BatteryManager::initGpioExpander() {
return false; return false;
} }
expanderPowerPin = gpio_descriptor_acquire(expander, expanderpin::EXPANDER_POWER, GPIO_OWNER_GPIO); expanderPowerPin = gpio_descriptor_acquire(expander, expanderpin::EXPANDER_POWER, GPIO_FLAG_DIRECTION_OUTPUT, GPIO_OWNER_GPIO);
check(expanderPowerPin != nullptr); check(expanderPowerPin != nullptr);
gpio_descriptor_set_flags(expanderPowerPin, GPIO_FLAG_DIRECTION_OUTPUT);
device_put(expander); device_put(expander);
return true; return true;
@@ -90,21 +90,19 @@ static int32_t nav_buttons_thread_main(UnphoneNavButtonsInternal* internal) {
// region Pin acquisition // region Pin acquisition
static error_t acquire_button(const GpioPinSpec& pin, void (*callback)(void*), void* arg, GpioDescriptor** out_descriptor) { static error_t acquire_button(const GpioPinSpec& pin, void (*callback)(void*), void* arg, GpioDescriptor** out_descriptor) {
auto* descriptor = gpio_descriptor_acquire(pin.gpio_controller, pin.pin, GPIO_OWNER_GPIO);
if (descriptor == nullptr) {
return ERROR_RESOURCE;
}
// Digital pull-up; the buttons pull the pin low while pressed. Listen for the release // Digital pull-up; the buttons pull the pin low while pressed. Listen for the release
// (positive edge) rather than the press - if we listen to the press, these buttons can // (positive edge) rather than the press - if we listen to the press, these buttons can
// generate more than one signal when held down (mirrors the original unPhone init). // generate more than one signal when held down (mirrors the original unPhone init).
gpio_flags_t flags = GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_PULL_UP; gpio_flags_t flags = GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_PULL_UP;
flags = GPIO_FLAG_INTERRUPT_TO_OPTIONS(flags, GPIO_INTERRUPT_POS_EDGE); flags = GPIO_FLAG_INTERRUPT_TO_OPTIONS(flags, GPIO_INTERRUPT_POS_EDGE);
error_t error = gpio_descriptor_set_flags(descriptor, flags); auto* descriptor = gpio_descriptor_acquire(pin.gpio_controller, pin.pin, flags, GPIO_OWNER_GPIO);
if (error == ERROR_NONE) { if (descriptor == nullptr) {
error = gpio_descriptor_add_callback(descriptor, callback, arg); return ERROR_RESOURCE;
} }
auto error = gpio_descriptor_add_callback(descriptor, callback, arg);
if (error == ERROR_NONE) { if (error == ERROR_NONE) {
error = gpio_descriptor_enable_interrupt(descriptor); error = gpio_descriptor_enable_interrupt(descriptor);
} }
@@ -25,18 +25,12 @@ extern Module unphone_module;
static error_t start(Device* device) { static error_t start(Device* device) {
const auto* config = GET_CONFIG(device); const auto* config = GET_CONFIG(device);
auto* descriptor = gpio_descriptor_acquire(config->pin.gpio_controller, config->pin.pin, GPIO_OWNER_GPIO); auto* descriptor = gpio_descriptor_acquire(config->pin.gpio_controller, config->pin.pin, config->pin.flags | GPIO_FLAG_DIRECTION_INPUT, GPIO_OWNER_GPIO);
if (descriptor == nullptr) { if (descriptor == nullptr) {
LOG_E(TAG, "Failed to acquire GPIO descriptor"); LOG_E(TAG, "Failed to acquire GPIO descriptor");
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
if (gpio_descriptor_set_flags(descriptor, config->pin.flags | GPIO_FLAG_DIRECTION_INPUT) != ERROR_NONE) {
LOG_E(TAG, "Failed to configure power switch pin as input");
gpio_descriptor_release(descriptor);
return ERROR_RESOURCE;
}
gpio_num_t native_pin; gpio_num_t native_pin;
if (gpio_descriptor_get_native_pin_number(descriptor, &native_pin) != ERROR_NONE) { if (gpio_descriptor_get_native_pin_number(descriptor, &native_pin) != ERROR_NONE) {
LOG_E(TAG, "Power switch pin has no native pin number"); LOG_E(TAG, "Power switch pin has no native pin number");
+8 -11
View File
@@ -38,29 +38,26 @@ static void on_device_event(Device* device, DeviceEvent event, void* context) {
} }
static error_t start() { 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); device_listener_add(on_device_event, nullptr);
return ERROR_NONE; return ERROR_NONE;
} }
static error_t stop() { static error_t stop() {
device_listener_remove(&on_device_event); 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; 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", .name = "unphone",
.start = start, .start = start,
.stop = stop, .stop = stop,
.symbols = nullptr, .drivers = unphone_drivers
.internal = nullptr
}; };
} }
@@ -2,22 +2,8 @@
extern "C" { extern "C" {
static error_t start() { Module waveshare_esp32_s3_geek_module = {
// Empty for now .name = "waveshare-esp32-s3-geek"
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
}; };
} }
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module waveshare_s3_lcd_13_module = { Module waveshare_s3_lcd_13_module = {
.name = "waveshare-s3-lcd-13", .name = "waveshare-s3-lcd-13"
.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> #include <tactility/module.h>
extern "C" { extern "C" {
Module waveshare_s3_touch_lcd_128_module = { Module waveshare_s3_touch_lcd_128_module = {
.name = "waveshare-s3-touch-lcd-128", .name = "waveshare-s3-touch-lcd-128"
.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> #include <tactility/module.h>
extern "C" { extern "C" {
Module waveshare_s3_touch_lcd_147_module = { Module waveshare_s3_touch_lcd_147_module = {
.name = "waveshare-s3-touch-lcd-147", .name = "waveshare-s3-touch-lcd-147"
.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> #include <tactility/module.h>
extern "C" { extern "C" {
Module waveshare_s3_touch_lcd_43_module = { Module waveshare_s3_touch_lcd_43_module = {
.name = "waveshare-s3-touch-lcd-43", .name = "waveshare-s3-touch-lcd-43"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
@@ -96,9 +96,8 @@
vsync-back-porch = <8>; vsync-back-porch = <8>;
vsync-front-porch = <8>; vsync-front-porch = <8>;
pclk-active-neg; pclk-active-neg;
num-fbs = <2>; num-fbs = <1>;
double-fb; bounce-buffer-size-px = <4000>;
bounce-buffer-size-px = <8000>;
pin-hsync = <&gpio0 46 GPIO_FLAG_NONE>; pin-hsync = <&gpio0 46 GPIO_FLAG_NONE>;
pin-vsync = <&gpio0 3 GPIO_FLAG_NONE>; pin-vsync = <&gpio0 3 GPIO_FLAG_NONE>;
pin-de = <&gpio0 5 GPIO_FLAG_NONE>; pin-de = <&gpio0 5 GPIO_FLAG_NONE>;
@@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module wireless_tag_wt32_sc01_plus_module = { Module wireless_tag_wt32_sc01_plus_module = {
.name = "wireless-tag-wt32-sc01-plus", .name = "wireless-tag-wt32-sc01-plus"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }
+3
View File
@@ -13,6 +13,9 @@
## Higher Priority ## 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) - Create `#define` for empty module (for modules that fully rely on device.properties and don't define drivers or have start/stop logic)
- Get rid of TactilityC in favour of TactilityKernel and kernel modules - Get rid of TactilityC in favour of TactilityKernel and kernel modules
- Improve SPI kernel driver (implement read, write, transactions) - Improve SPI kernel driver (implement read, write, transactions)
-5
View File
@@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lcd esp_lcd_touch esp_lvgl_port
)
-3
View File
@@ -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;
}
-44
View File
@@ -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;
};
-5
View File
@@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility driver
)
-35
View File
@@ -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(&regionBuffer[n], &renderBitmap[base], widthBytes);
} else {
std::memcpy(&regionBuffer[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; }
};
+9 -14
View File
@@ -1,19 +1,14 @@
# Audio codec module # Audio codec module
Defines `AUDIO_CODEC_TYPE` and the codec-agnostic `AudioCodecApi` (open/close/ Defines codec-agnostic `AudioCodecApi` (open/close/read/write/volume/mute/native-rate/native-channels/capabilities)
read/write/volume/mute/native-rate/native-channels/capabilities) that every that every audio codec driver in this repo implements (ES8311, ES8388, ES7210, AW88298, the dummy I2S amps, the PDM mic).
audio codec driver in this repo implements (ES8311, ES8388, ES7210, AW88298, This is intentionally low-level: no resampling or mixing, one codec device per chip. Most app code should go through
the dummy I2S amps, the PDM mic). This is intentionally low-level: no `audio_stream` (see `Drivers/audio-stream-module`) instead of talking to a codec device directly.
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 Also provides `audio_codec_adapters.h`: glue that lets a codec driver build an `esp_codec_dev` control/data/GPIO
`esp_codec_dev` control/data/GPIO interface (`audio_codec_ctrl_if_t` / interface (`audio_codec_ctrl_if_t` / `audio_codec_data_if_t` / `audio_codec_gpio_if_t`) directly from Tactility's
`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
own I2C controller, I2S controller, and GPIO pin devices, so a codec driver `device_get_parent()`/`device_find_by_name()` plus a handful of devicetree properties -- it never touches the platform
only ever needs `device_get_parent()`/`device_find_by_name()` plus a handful I2C/I2S/GPIO APIs directly.
of devicetree properties -- it never touches the platform I2C/I2S/GPIO APIs
directly.
License: [Apache v2.0](LICENSE-Apache-2.0.md) License: [Apache v2.0](LICENSE-Apache-2.0.md)
@@ -17,7 +17,7 @@ struct GpioAdapterContext {
static struct GpioAdapterContext* g_context = NULL; static struct GpioAdapterContext* g_context = NULL;
static struct GpioDescriptor* acquire_descriptor(int16_t gpio) { static struct GpioDescriptor* acquire_descriptor(int16_t gpio, gpio_flags_t flags) {
if (g_context == NULL || gpio < 0 || (size_t) gpio >= g_context->pin_count) { if (g_context == NULL || gpio < 0 || (size_t) gpio >= g_context->pin_count) {
return NULL; return NULL;
} }
@@ -31,24 +31,31 @@ static struct GpioDescriptor* acquire_descriptor(int16_t gpio) {
return NULL; return NULL;
} }
struct GpioDescriptor* descriptor = gpio_descriptor_acquire(spec->gpio_controller, spec->pin, GPIO_OWNER_GPIO); struct GpioDescriptor* descriptor = gpio_descriptor_acquire(
spec->gpio_controller, spec->pin, spec->flags | flags, GPIO_OWNER_GPIO
);
if (descriptor != NULL) { if (descriptor != NULL) {
gpio_descriptor_set_flags(descriptor, spec->flags);
g_context->descriptors[gpio] = descriptor; g_context->descriptors[gpio] = descriptor;
} }
return descriptor; return descriptor;
} }
static int gpio_setup(int16_t gpio, audio_gpio_dir_t dir, audio_gpio_mode_t mode) { static int gpio_setup(int16_t gpio, audio_gpio_dir_t dir, audio_gpio_mode_t mode) {
struct GpioDescriptor* descriptor = acquire_descriptor(gpio); gpio_flags_t flags = (dir == AUDIO_GPIO_DIR_OUT) ? GPIO_FLAG_DIRECTION_OUTPUT : GPIO_FLAG_DIRECTION_INPUT;
if ((mode & AUDIO_GPIO_MODE_PULL_UP) != 0) {
flags |= GPIO_FLAG_PULL_UP;
}
if ((mode & AUDIO_GPIO_MODE_PULL_DOWN) != 0) {
flags |= GPIO_FLAG_PULL_DOWN;
}
struct GpioDescriptor* descriptor = acquire_descriptor(gpio, flags);
if (descriptor == NULL) { if (descriptor == NULL) {
return ESP_CODEC_DEV_NOT_FOUND; return ESP_CODEC_DEV_NOT_FOUND;
} }
gpio_flags_t flags = GPIO_FLAG_NONE;
gpio_descriptor_get_flags(descriptor, &flags); gpio_descriptor_get_flags(descriptor, &flags);
flags &= ~(GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_DIRECTION_OUTPUT | GPIO_FLAG_PULL_UP | GPIO_FLAG_PULL_DOWN); flags &= ~(GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_DIRECTION_OUTPUT | GPIO_FLAG_PULL_UP | GPIO_FLAG_PULL_DOWN);
flags |= (dir == AUDIO_GPIO_DIR_OUT) ? GPIO_FLAG_DIRECTION_OUTPUT : GPIO_FLAG_DIRECTION_INPUT; flags |= (dir == AUDIO_GPIO_DIR_OUT) ? GPIO_FLAG_DIRECTION_OUTPUT : GPIO_FLAG_DIRECTION_INPUT;
if ((mode & AUDIO_GPIO_MODE_PULL_UP) != 0) { if ((mode & AUDIO_GPIO_MODE_PULL_UP) != 0) {
flags |= GPIO_FLAG_PULL_UP; flags |= GPIO_FLAG_PULL_UP;
@@ -61,7 +68,7 @@ static int gpio_setup(int16_t gpio, audio_gpio_dir_t dir, audio_gpio_mode_t mode
} }
static int gpio_set(int16_t gpio, bool high) { static int gpio_set(int16_t gpio, bool high) {
struct GpioDescriptor* descriptor = acquire_descriptor(gpio); struct GpioDescriptor* descriptor = acquire_descriptor(gpio, GPIO_FLAG_DIRECTION_OUTPUT);
if (descriptor == NULL) { if (descriptor == NULL) {
return ESP_CODEC_DEV_NOT_FOUND; return ESP_CODEC_DEV_NOT_FOUND;
} }
@@ -72,7 +79,7 @@ static int gpio_set(int16_t gpio, bool high) {
// acquire and a legitimate low reading are indistinguishable to the caller; log so the // acquire and a legitimate low reading are indistinguishable to the caller; log so the
// failure is at least visible. // failure is at least visible.
static bool gpio_get(int16_t gpio) { static bool gpio_get(int16_t gpio) {
struct GpioDescriptor* descriptor = acquire_descriptor(gpio); struct GpioDescriptor* descriptor = acquire_descriptor(gpio, GPIO_FLAG_DIRECTION_INPUT);
if (descriptor == NULL) { if (descriptor == NULL) {
LOG_E(TAG, "gpio_get: failed to acquire descriptor for pin %d", gpio); LOG_E(TAG, "gpio_get: failed to acquire descriptor for pin %d", gpio);
return false; return false;

Some files were not shown because too many files have changed in this diff Show More