Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd99a8d7c1 | |||
| 2720556d6a | |||
| 2fbc44466a | |||
| 2d768ef3a1 | |||
| bd30aa046a |
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module btt_panda_touch_module = {
|
struct Module btt_panda_touch_module = {
|
||||||
.name = "btt-panda-touch",
|
.name = "btt-panda-touch",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Module cyd_2432s024c_module = {
|
Module cyd_2432s024c_module = {
|
||||||
.name = "cyd-2432s024c",
|
.name = "cyd-2432s024c",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module cyd_2432s024r_module = {
|
struct Module cyd_2432s024r_module = {
|
||||||
.name = "cyd-2432s024r",
|
.name = "cyd-2432s024r",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module cyd_2432s028r_module = {
|
struct Module cyd_2432s028r_module = {
|
||||||
.name = "cyd-2432s028r",
|
.name = "cyd-2432s028r",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module cyd_2432s028rv3_module = {
|
struct Module cyd_2432s028rv3_module = {
|
||||||
.name = "cyd-2432s028rv3",
|
.name = "cyd-2432s028rv3",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module cyd_2432s032c_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module cyd_2432s032c_module = {
|
|
||||||
.name = "cyd-2432s032c",
|
.name = "cyd-2432s032c",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module cyd_3248s035c_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module cyd_3248s035c_module = {
|
|
||||||
.name = "cyd-3248s035c",
|
.name = "cyd-3248s035c",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module cyd_4848s040c_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module cyd_4848s040c_module = {
|
|
||||||
.name = "cyd-4848s040c",
|
.name = "cyd-4848s040c",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module cyd_8048s043c_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module cyd_8048s043c_module = {
|
|
||||||
.name = "cyd-8048s043c",
|
.name = "cyd-8048s043c",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module cyd_e32r28t_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module cyd_e32r28t_module = {
|
|
||||||
.name = "cyd-e32r28t",
|
.name = "cyd-e32r28t",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module cyd_e32r32p_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module cyd_e32r32p_module = {
|
|
||||||
.name = "cyd-e32r32p",
|
.name = "cyd-e32r32p",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module elecrow_crowpanel_advance_28_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module elecrow_crowpanel_advance_28_module = {
|
|
||||||
.name = "elecrow-crowpanel-advance-28",
|
.name = "elecrow-crowpanel-advance-28",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr
|
.symbols = nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Module elecrow_crowpanel_advance_35_module = {
|
Module elecrow_crowpanel_advance_35_module = {
|
||||||
.name = "elecrow-crowpanel-advance-35",
|
.name = "elecrow-crowpanel-advance-35",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module elecrow_crowpanel_advance_50_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module elecrow_crowpanel_advance_50_module = {
|
|
||||||
.name = "elecrow-crowpanel-advance-50",
|
.name = "elecrow-crowpanel-advance-50",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module elecrow_crowpanel_basic_28_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module elecrow_crowpanel_basic_28_module = {
|
|
||||||
.name = "elecrow-crowpanel-basic-28",
|
.name = "elecrow-crowpanel-basic-28",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Module elecrow_crowpanel_basic_35_module = {
|
Module elecrow_crowpanel_basic_35_module = {
|
||||||
.name = "elecrow-crowpanel-basic-35",
|
.name = "elecrow-crowpanel-basic-35",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module elecrow_crowpanel_basic_50_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module elecrow_crowpanel_basic_50_module = {
|
|
||||||
.name = "elecrow-crowpanel-basic-50",
|
.name = "elecrow-crowpanel-basic-50",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS "source/module.cpp"
|
||||||
|
REQUIRES TactilityKernel
|
||||||
|
)
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
general.vendor=CYD
|
||||||
|
general.name=ES3C28P
|
||||||
|
|
||||||
|
apps.launcherAppId=Launcher
|
||||||
|
|
||||||
|
hardware.target=ESP32S3
|
||||||
|
hardware.flashSize=16MB
|
||||||
|
hardware.spiRam=true
|
||||||
|
hardware.spiRamMode=OCT
|
||||||
|
hardware.spiRamSpeed=120M
|
||||||
|
hardware.esptoolFlashFreq=120M
|
||||||
|
hardware.tinyUsb=true
|
||||||
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
display.size=2.8"
|
||||||
|
display.shape=rectangle
|
||||||
|
display.dpi=143
|
||||||
|
|
||||||
|
lvgl.colorDepth=16
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
|
dependencies.useDeprecatedHal=false
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
dependencies:
|
||||||
|
- Platforms/platform-esp32
|
||||||
|
- Drivers/ili9341-module
|
||||||
|
- Drivers/ft5x06-module
|
||||||
|
- Drivers/es8311-module
|
||||||
|
- Drivers/audio-stream-module
|
||||||
|
dts: es3c28p.dts
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/dts-v1/;
|
||||||
|
|
||||||
|
#include <tactility/bindings/root.h>
|
||||||
|
#include <tactility/bindings/esp32_adc_oneshot.h>
|
||||||
|
#include <tactility/bindings/esp32_ble.h>
|
||||||
|
#include <tactility/bindings/esp32_gpio.h>
|
||||||
|
#include <tactility/bindings/esp32_i2c.h>
|
||||||
|
#include <tactility/bindings/esp32_i2s.h>
|
||||||
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/esp32_sdmmc.h>
|
||||||
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
|
#include <tactility/bindings/esp32_wifi_pinned.h>
|
||||||
|
#include <tactility/bindings/battery_sense.h>
|
||||||
|
#include <tactility/bindings/gpio_hog.h>
|
||||||
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
|
#include <bindings/ili9341.h>
|
||||||
|
#include <bindings/ft5x06.h>
|
||||||
|
#include <bindings/es8311.h>
|
||||||
|
|
||||||
|
/ {
|
||||||
|
compatible = "root";
|
||||||
|
model = "LCDWIKI ES3C28P";
|
||||||
|
|
||||||
|
wifi0 {
|
||||||
|
compatible = "espressif,esp32-wifi-pinned";
|
||||||
|
status = "disabled";
|
||||||
|
};
|
||||||
|
|
||||||
|
ble0 {
|
||||||
|
compatible = "espressif,esp32-ble";
|
||||||
|
status = "disabled";
|
||||||
|
};
|
||||||
|
|
||||||
|
gpio0 {
|
||||||
|
compatible = "espressif,esp32-gpio";
|
||||||
|
gpio-count = <49>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* FM8002E speaker amp enable, active-low on GPIO1 */
|
||||||
|
amp_enable {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&gpio0 1 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_OUTPUT_LOW>;
|
||||||
|
};
|
||||||
|
|
||||||
|
adc0 {
|
||||||
|
compatible = "espressif,esp32-adc-oneshot";
|
||||||
|
unit-id = <ADC_UNIT_1>;
|
||||||
|
clk-src = <ADC_RTC_CLK_SRC_DEFAULT>;
|
||||||
|
channels = <ADC_CHANNEL_8 ADC_ATTEN_DB_12 ADC_BITWIDTH_DEFAULT>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* BAT+ through 200K/200K divider into GPIO9 ADC1_CH8 */
|
||||||
|
battery-sense {
|
||||||
|
compatible = "battery-sense";
|
||||||
|
io-channel = <&adc0 0>;
|
||||||
|
reference-voltage-mv = <3300>;
|
||||||
|
multiplier = <2000>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* i2s0 must be before i2c0 so codec can reference it */
|
||||||
|
i2s0 {
|
||||||
|
compatible = "espressif,esp32-i2s";
|
||||||
|
port = <I2S_NUM_0>;
|
||||||
|
pin-bclk = <&gpio0 5 GPIO_FLAG_NONE>;
|
||||||
|
pin-ws = <&gpio0 7 GPIO_FLAG_NONE>;
|
||||||
|
pin-data-out = <&gpio0 8 GPIO_FLAG_NONE>;
|
||||||
|
pin-data-in = <&gpio0 6 GPIO_FLAG_NONE>;
|
||||||
|
pin-mclk = <&gpio0 4 GPIO_FLAG_NONE>;
|
||||||
|
};
|
||||||
|
|
||||||
|
i2c0 {
|
||||||
|
compatible = "espressif,esp32-i2c";
|
||||||
|
port = <I2C_NUM_0>;
|
||||||
|
clock-frequency = <100000>;
|
||||||
|
pin-sda = <&gpio0 16 GPIO_FLAG_NONE>;
|
||||||
|
pin-scl = <&gpio0 15 GPIO_FLAG_NONE>;
|
||||||
|
|
||||||
|
ft5x06@38 {
|
||||||
|
compatible = "focaltech,ft5x06";
|
||||||
|
reg = <0x38>;
|
||||||
|
x-max = <240>;
|
||||||
|
y-max = <320>;
|
||||||
|
swap-xy;
|
||||||
|
mirror-x;
|
||||||
|
pin-reset = <&gpio0 18 GPIO_FLAG_NONE>;
|
||||||
|
pin-interrupt = <&gpio0 17 GPIO_FLAG_NONE>;
|
||||||
|
};
|
||||||
|
|
||||||
|
es8311: es8311@18 {
|
||||||
|
compatible = "everest,es8311";
|
||||||
|
reg = <0x18>;
|
||||||
|
i2s = <&i2s0>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
display_backlight_pwm {
|
||||||
|
compatible = "espressif,esp32-pwm-ledc";
|
||||||
|
pin = <&gpio0 45 GPIO_FLAG_NONE>;
|
||||||
|
period-ns = <500000>;
|
||||||
|
ledc-timer = <0>;
|
||||||
|
ledc-channel = <0>;
|
||||||
|
};
|
||||||
|
|
||||||
|
display_backlight {
|
||||||
|
compatible = "pwm-backlight";
|
||||||
|
status = "disabled";
|
||||||
|
pwm = <&display_backlight_pwm>;
|
||||||
|
};
|
||||||
|
|
||||||
|
spi0 {
|
||||||
|
compatible = "espressif,esp32-spi";
|
||||||
|
host = <SPI2_HOST>;
|
||||||
|
pin-mosi = <&gpio0 11 GPIO_FLAG_NONE>;
|
||||||
|
pin-sclk = <&gpio0 12 GPIO_FLAG_NONE>;
|
||||||
|
cs-gpios = <&gpio0 10 GPIO_FLAG_NONE>;
|
||||||
|
|
||||||
|
display@0 {
|
||||||
|
compatible = "ilitek,ili9341";
|
||||||
|
horizontal-resolution = <320>;
|
||||||
|
vertical-resolution = <240>;
|
||||||
|
swap-xy;
|
||||||
|
invert-color;
|
||||||
|
bgr-order;
|
||||||
|
pixel-clock-hz = <40000000>;
|
||||||
|
pin-dc = <&gpio0 46 GPIO_FLAG_NONE>;
|
||||||
|
backlight = <&display_backlight>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sdmmc0 {
|
||||||
|
compatible = "espressif,esp32-sdmmc";
|
||||||
|
pin-clk = <&gpio0 38 GPIO_FLAG_NONE>;
|
||||||
|
pin-cmd = <&gpio0 40 GPIO_FLAG_NONE>;
|
||||||
|
pin-d0 = <&gpio0 39 GPIO_FLAG_NONE>;
|
||||||
|
pin-d1 = <&gpio0 41 GPIO_FLAG_NONE>;
|
||||||
|
pin-d2 = <&gpio0 48 GPIO_FLAG_NONE>;
|
||||||
|
pin-d3 = <&gpio0 47 GPIO_FLAG_NONE>;
|
||||||
|
slot = <SDMMC_HOST_SLOT_1>;
|
||||||
|
bus-width = <4>;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
|
#include <tactility/module.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
Module es3c28p_module = {
|
||||||
|
.name = "es3c28p",
|
||||||
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
|
.symbols = nullptr,
|
||||||
|
.internal = nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module generic_esp32_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module generic_esp32_module = {
|
|
||||||
.name = "generic-esp32",
|
.name = "generic-esp32",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module generic_esp32c6_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module generic_esp32c6_module = {
|
|
||||||
.name = "generic-esp32c6",
|
.name = "generic-esp32c6",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module generic_esp32p4_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module generic_esp32p4_module = {
|
|
||||||
.name = "generic-esp32p4",
|
.name = "generic-esp32p4",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module generic_esp32s3_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module generic_esp32s3_module = {
|
|
||||||
.name = "generic-esp32s3",
|
.name = "generic-esp32s3",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Module guition_jc1060p470ciwy_module = {
|
Module guition_jc1060p470ciwy_module = {
|
||||||
.name = "guition-jc1060p470ciwy",
|
.name = "guition-jc1060p470ciwy",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module guition_jc2432w328c_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module guition_jc2432w328c_module = {
|
|
||||||
.name = "guition-jc2432w328c",
|
.name = "guition-jc2432w328c",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module guition_jc8048w550c_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module guition_jc8048w550c_module = {
|
|
||||||
.name = "guition-jc8048w550c",
|
.name = "guition-jc8048w550c",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module heltec_wifi_lora_32_v3_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module heltec_wifi_lora_32_v3_module = {
|
|
||||||
.name = "heltec-wifi-lora-32-v3",
|
.name = "heltec-wifi-lora-32-v3",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module m5stack_stackchan_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module m5stack_stackchan_module = {
|
|
||||||
.name = "m5stack-stackchan",
|
.name = "m5stack-stackchan",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
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 esp_lvgl_port esp_lcd ST7789 PwmBacklight ButtonControl vfs fatfs m5pm1-module
|
REQUIRES TactilityKernel
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
#include "devices/Display.h"
|
|
||||||
#include "devices/Power.h"
|
|
||||||
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <drivers/m5pm1.h>
|
|
||||||
|
|
||||||
#include <Tactility/hal/Configuration.h>
|
|
||||||
#include <ButtonControl.h>
|
|
||||||
#include <PwmBacklight.h>
|
|
||||||
|
|
||||||
using namespace tt::hal;
|
|
||||||
|
|
||||||
static constexpr auto* TAG = "StickS3";
|
|
||||||
|
|
||||||
// Audio codec register programming is owned by the es8311-module driver (registered as an
|
|
||||||
// AUDIO_CODEC_TYPE device, see m5stack,sticks3.dts). This board still needs to drive the
|
|
||||||
// AW8737 speaker amplifier's enable line via m5pm1 (PM1_G3), which is board wiring glue
|
|
||||||
// rather than codec configuration, so it stays here.
|
|
||||||
static void enableSpeakerAmplifier() {
|
|
||||||
auto* m5pm1 = device_find_by_name("m5pm1");
|
|
||||||
if (m5pm1 == nullptr) {
|
|
||||||
LOG_W(TAG, "m5pm1 not found -- speaker amp not enabled");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
error_t error = m5pm1_set_speaker_enable(m5pm1, true);
|
|
||||||
if (error != ERROR_NONE) {
|
|
||||||
LOG_E(TAG, "Failed to enable speaker amplifier: %s", error_to_string(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool initBoot() {
|
|
||||||
enableSpeakerAmplifier();
|
|
||||||
return driver::pwmbacklight::init(GPIO_NUM_38, 512);
|
|
||||||
}
|
|
||||||
|
|
||||||
static DeviceVector createDevices() {
|
|
||||||
return {
|
|
||||||
createPower(),
|
|
||||||
ButtonControl::createTwoButtonControl(11, 12), // top button, side button
|
|
||||||
createDisplay(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
extern const Configuration hardwareConfiguration = {
|
|
||||||
.initBoot = initBoot,
|
|
||||||
.createDevices = createDevices
|
|
||||||
};
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#include "Display.h"
|
|
||||||
|
|
||||||
#include <PwmBacklight.h>
|
|
||||||
#include <St7789Display.h>
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
|
||||||
St7789Display::Configuration panel_configuration = {
|
|
||||||
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
|
|
||||||
.verticalResolution = LCD_VERTICAL_RESOLUTION,
|
|
||||||
.gapX = 52,
|
|
||||||
.gapY = 40,
|
|
||||||
.swapXY = false,
|
|
||||||
.mirrorX = false,
|
|
||||||
.mirrorY = false,
|
|
||||||
.invertColor = true,
|
|
||||||
.bufferSize = LCD_BUFFER_SIZE,
|
|
||||||
.touch = nullptr,
|
|
||||||
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
|
|
||||||
.resetPin = LCD_PIN_RESET,
|
|
||||||
.lvglSwapBytes = false
|
|
||||||
};
|
|
||||||
|
|
||||||
auto spi_configuration = std::make_shared<St7789Display::SpiConfiguration>(St7789Display::SpiConfiguration {
|
|
||||||
.spiHostDevice = LCD_SPI_HOST,
|
|
||||||
.csPin = LCD_PIN_CS,
|
|
||||||
.dcPin = LCD_PIN_DC,
|
|
||||||
.pixelClockFrequency = 40'000'000,
|
|
||||||
.transactionQueueDepth = 10
|
|
||||||
});
|
|
||||||
|
|
||||||
return std::make_shared<St7789Display>(panel_configuration, spi_configuration);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Tactility/hal/display/DisplayDevice.h"
|
|
||||||
#include <memory>
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <driver/spi_common.h>
|
|
||||||
|
|
||||||
constexpr auto LCD_SPI_HOST = SPI2_HOST;
|
|
||||||
constexpr auto LCD_PIN_CS = GPIO_NUM_41;
|
|
||||||
constexpr auto LCD_PIN_DC = GPIO_NUM_45;
|
|
||||||
constexpr auto LCD_PIN_RESET = GPIO_NUM_21;
|
|
||||||
constexpr auto LCD_HORIZONTAL_RESOLUTION = 135;
|
|
||||||
constexpr auto LCD_VERTICAL_RESOLUTION = 240;
|
|
||||||
constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 3;
|
|
||||||
constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT;
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
#include "Power.h"
|
|
||||||
|
|
||||||
#include <Tactility/hal/power/PowerDevice.h>
|
|
||||||
#include <drivers/m5pm1.h>
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
using namespace tt::hal::power;
|
|
||||||
|
|
||||||
static constexpr auto* TAG = "StickS3Power";
|
|
||||||
|
|
||||||
static constexpr float MIN_BATTERY_VOLTAGE_MV = 3300.0f;
|
|
||||||
static constexpr float MAX_BATTERY_VOLTAGE_MV = 4200.0f;
|
|
||||||
|
|
||||||
class StickS3Power final : public PowerDevice {
|
|
||||||
public:
|
|
||||||
explicit StickS3Power(::Device* m5pm1Device) : m5pm1(m5pm1Device) {}
|
|
||||||
|
|
||||||
std::string getName() const override { return "M5Stack StickS3 Power"; }
|
|
||||||
std::string getDescription() const override { return "Battery monitoring via M5PM1 over I2C"; }
|
|
||||||
|
|
||||||
bool supportsMetric(MetricType type) const override {
|
|
||||||
switch (type) {
|
|
||||||
using enum MetricType;
|
|
||||||
case BatteryVoltage:
|
|
||||||
case ChargeLevel:
|
|
||||||
case IsCharging:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool getMetric(MetricType type, MetricData& data) override {
|
|
||||||
switch (type) {
|
|
||||||
using enum MetricType;
|
|
||||||
|
|
||||||
case BatteryVoltage: {
|
|
||||||
uint16_t mv = 0;
|
|
||||||
if (m5pm1_get_battery_voltage(m5pm1, &mv) != ERROR_NONE) return false;
|
|
||||||
data.valueAsUint32 = mv;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ChargeLevel: {
|
|
||||||
uint16_t mv = 0;
|
|
||||||
if (m5pm1_get_battery_voltage(m5pm1, &mv) != ERROR_NONE) return false;
|
|
||||||
float voltage = static_cast<float>(mv);
|
|
||||||
if (voltage >= MAX_BATTERY_VOLTAGE_MV) {
|
|
||||||
data.valueAsUint8 = 100;
|
|
||||||
} else if (voltage <= MIN_BATTERY_VOLTAGE_MV) {
|
|
||||||
data.valueAsUint8 = 0;
|
|
||||||
} else {
|
|
||||||
float factor = (voltage - MIN_BATTERY_VOLTAGE_MV) / (MAX_BATTERY_VOLTAGE_MV - MIN_BATTERY_VOLTAGE_MV);
|
|
||||||
data.valueAsUint8 = static_cast<uint8_t>(factor * 100.0f);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case IsCharging: {
|
|
||||||
bool charging = false;
|
|
||||||
if (m5pm1_is_charging(m5pm1, &charging) != ERROR_NONE) {
|
|
||||||
LOG_W(TAG, "Failed to read charging status");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
data.valueAsBool = charging;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool supportsPowerOff() const override { return true; }
|
|
||||||
|
|
||||||
void powerOff() override {
|
|
||||||
LOG_W(TAG, "Powering off via M5PM1");
|
|
||||||
if (m5pm1_shutdown(m5pm1) != ERROR_NONE) {
|
|
||||||
LOG_E(TAG, "Failed to send power-off command");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
::Device* m5pm1;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<PowerDevice> createPower() {
|
|
||||||
auto* m5pm1 = device_find_by_name("m5pm1");
|
|
||||||
if (m5pm1 == nullptr) {
|
|
||||||
LOG_E(TAG, "m5pm1 device not found");
|
|
||||||
}
|
|
||||||
return std::make_shared<StickS3Power>(m5pm1);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <Tactility/hal/power/PowerDevice.h>
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::power::PowerDevice> createPower();
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#include <tactility/module.h>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
// Empty for now
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
// Empty for now
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module m5stack_sticks3_module = {
|
|
||||||
.name = "m5stack-sticks3",
|
|
||||||
.start = start,
|
|
||||||
.stop = stop,
|
|
||||||
.symbols = nullptr,
|
|
||||||
.internal = nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,8 @@ hardware.esptoolFlashFreq=80M
|
|||||||
hardware.tinyUsb=true
|
hardware.tinyUsb=true
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
dependencies.useDeprecatedHal=false
|
||||||
|
|
||||||
storage.userDataLocation=Internal
|
storage.userDataLocation=Internal
|
||||||
|
|
||||||
display.size=1.14"
|
display.size=1.14"
|
||||||
|
|||||||
@@ -4,4 +4,6 @@ dependencies:
|
|||||||
- Drivers/m5pm1-module
|
- Drivers/m5pm1-module
|
||||||
- Drivers/es8311-module
|
- Drivers/es8311-module
|
||||||
- Drivers/audio-stream-module
|
- Drivers/audio-stream-module
|
||||||
|
- Drivers/st7789-module
|
||||||
|
- Drivers/button-control-module
|
||||||
dts: m5stack,sticks3.dts
|
dts: m5stack,sticks3.dts
|
||||||
|
|||||||
@@ -11,7 +11,11 @@
|
|||||||
#include <bindings/m5pm1.h>
|
#include <bindings/m5pm1.h>
|
||||||
#include <bindings/es8311.h>
|
#include <bindings/es8311.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/display_placeholder.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
|
|
||||||
|
#include <bindings/st7789.h>
|
||||||
|
#include <bindings/button_control.h>
|
||||||
|
|
||||||
/ {
|
/ {
|
||||||
compatible = "root";
|
compatible = "root";
|
||||||
@@ -53,6 +57,8 @@
|
|||||||
m5pm1 {
|
m5pm1 {
|
||||||
compatible = "m5stack,m5pm1";
|
compatible = "m5stack,m5pm1";
|
||||||
reg = <0x6E>;
|
reg = <0x6E>;
|
||||||
|
power-supply-reference-voltage-mv = <4200>;
|
||||||
|
speaker-amp-enable-at-boot;
|
||||||
};
|
};
|
||||||
|
|
||||||
bmi270 {
|
bmi270 {
|
||||||
@@ -75,6 +81,22 @@
|
|||||||
pin-scl = <&gpio0 10 GPIO_FLAG_NONE>;
|
pin-scl = <&gpio0 10 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
display_backlight_pwm {
|
||||||
|
compatible = "espressif,esp32-pwm-ledc";
|
||||||
|
pin = <&gpio0 38 GPIO_FLAG_NONE>;
|
||||||
|
period-ns = <1953125>;
|
||||||
|
ledc-timer = <0>;
|
||||||
|
ledc-channel = <0>;
|
||||||
|
};
|
||||||
|
|
||||||
|
display_backlight {
|
||||||
|
compatible = "pwm-backlight";
|
||||||
|
// Off by default so display power-on won't show the screen from before the last power loss.
|
||||||
|
// The display backlight is turned on during the boot process.
|
||||||
|
status = "disabled";
|
||||||
|
pwm = <&display_backlight_pwm>;
|
||||||
|
};
|
||||||
|
|
||||||
spi0 {
|
spi0 {
|
||||||
compatible = "espressif,esp32-spi";
|
compatible = "espressif,esp32-spi";
|
||||||
host = <SPI2_HOST>;
|
host = <SPI2_HOST>;
|
||||||
@@ -82,11 +104,26 @@
|
|||||||
pin-mosi = <&gpio0 39 GPIO_FLAG_NONE>;
|
pin-mosi = <&gpio0 39 GPIO_FLAG_NONE>;
|
||||||
pin-sclk = <&gpio0 40 GPIO_FLAG_NONE>;
|
pin-sclk = <&gpio0 40 GPIO_FLAG_NONE>;
|
||||||
|
|
||||||
display {
|
display@0 {
|
||||||
compatible = "display-placeholder";
|
compatible = "sitronix,st7789";
|
||||||
|
horizontal-resolution = <135>;
|
||||||
|
vertical-resolution = <240>;
|
||||||
|
gap-x = <52>;
|
||||||
|
gap-y = <40>;
|
||||||
|
invert-color;
|
||||||
|
pixel-clock-hz = <40000000>;
|
||||||
|
pin-dc = <&gpio0 45 GPIO_FLAG_NONE>;
|
||||||
|
pin-reset = <&gpio0 21 GPIO_FLAG_NONE>;
|
||||||
|
backlight = <&display_backlight>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
buttons {
|
||||||
|
compatible = "tactility,button-control";
|
||||||
|
pin-primary = <&gpio0 11 GPIO_FLAG_NONE>;
|
||||||
|
pin-secondary = <&gpio0 12 GPIO_FLAG_NONE>;
|
||||||
|
};
|
||||||
|
|
||||||
uart_grove: uart1 {
|
uart_grove: uart1 {
|
||||||
compatible = "espressif,esp32-uart";
|
compatible = "espressif,esp32-uart";
|
||||||
status = "disabled";
|
status = "disabled";
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
|
#include <tactility/module.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
Module m5stack_sticks3_module = {
|
||||||
|
.name = "m5stack-sticks3",
|
||||||
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
|
.symbols = nullptr,
|
||||||
|
.internal = nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c esp_lcd_st7123 esp_lcd_touch_st7123 GT911 PwmBacklight driver esp_driver_i2c vfs fatfs ina226-module
|
REQUIRES Tactility esp_lvgl_port esp_lcd esp_lcd_ili9881c esp_lcd_st7123 esp_lcd_touch_st7123 esp_lcd_touch_gt911 driver esp_driver_i2c vfs fatfs ina226-module ili9881c-module st7123-module gt911-module
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
#include "devices/Display.h"
|
|
||||||
#include "devices/Power.h"
|
|
||||||
#include "devices/Tab5Keyboard.h"
|
|
||||||
|
|
||||||
#include <tactility/drivers/gpio_controller.h>
|
|
||||||
#include <tactility/drivers/i2c_controller.h>
|
|
||||||
|
|
||||||
#include <Tactility/hal/Configuration.h>
|
|
||||||
|
|
||||||
#include <esp_clock_output.h>
|
|
||||||
|
|
||||||
using namespace tt::hal;
|
|
||||||
|
|
||||||
static constexpr auto* TAG = "Tab5";
|
|
||||||
|
|
||||||
static DeviceVector createDevices() {
|
|
||||||
auto* i2c2 = device_find_by_name("i2c2");
|
|
||||||
check(i2c2, "i2c2 not found");
|
|
||||||
return {
|
|
||||||
createPower(),
|
|
||||||
createDisplay(),
|
|
||||||
std::make_shared<Tab5Keyboard>(i2c2)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
PI4IOE5V6408-0 (0x43)
|
|
||||||
- Bit 0: RF internal/external switch
|
|
||||||
- Bit 1: Speaker enable
|
|
||||||
- Bit 2: External 5V bus enable
|
|
||||||
- Bit 3: /
|
|
||||||
- Bit 4: LCD reset
|
|
||||||
- Bit 5: Touch reset
|
|
||||||
- Bit 6: Camera reset
|
|
||||||
- Bit 7: Headphone detect
|
|
||||||
*/
|
|
||||||
constexpr auto GPIO_EXP0_PIN_RF_INTERNAL_EXTERNAL = 0;
|
|
||||||
constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1;
|
|
||||||
constexpr auto GPIO_EXP0_PIN_EXTERNAL_5V_BUS_ENABLE = 2;
|
|
||||||
constexpr auto GPIO_EXP0_PIN_LCD_RESET = 4;
|
|
||||||
constexpr auto GPIO_EXP0_PIN_TOUCH_RESET = 5;
|
|
||||||
constexpr auto GPIO_EXP0_PIN_CAMERA_RESET = 6;
|
|
||||||
constexpr auto GPIO_EXP0_PIN_HEADPHONE_DETECT = 7;
|
|
||||||
|
|
||||||
/*
|
|
||||||
PI4IOE5V6408-1 (0x44)
|
|
||||||
- Bit 0: C6 WLAN enable
|
|
||||||
- Bit 1: /
|
|
||||||
- Bit 2: /
|
|
||||||
- Bit 3: USB-A 5V enable
|
|
||||||
- Bit 4: Device power: PWROFF_PLUSE
|
|
||||||
- Bit 5: IP2326: nCHG_QC_EN
|
|
||||||
- Bit 6: IP2326: CHG_STAT_LED
|
|
||||||
- Bit 7: IP2326: CHG_EN
|
|
||||||
*/
|
|
||||||
constexpr auto GPIO_EXP1_PIN_C6_WLAN_ENABLE = 0;
|
|
||||||
constexpr auto GPIO_EXP1_PIN_USB_A_5V_ENABLE = 3;
|
|
||||||
constexpr auto GPIO_EXP1_PIN_DEVICE_POWER = 4;
|
|
||||||
constexpr auto GPIO_EXP1_PIN_IP2326_NCHG_QC_EN = 5;
|
|
||||||
constexpr auto GPIO_EXP1_PIN_IP2326_CHG_STAT_LED = 6;
|
|
||||||
constexpr auto GPIO_EXP1_PIN_IP2326_CHG_EN = 7;
|
|
||||||
|
|
||||||
static void initExpander0(::Device* io_expander0) {
|
|
||||||
auto* rf_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_RF_INTERNAL_EXTERNAL, GPIO_OWNER_GPIO);
|
|
||||||
check(rf_pin);
|
|
||||||
auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO);
|
|
||||||
check(speaker_enable_pin);
|
|
||||||
auto* external_5v_bus_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_EXTERNAL_5V_BUS_ENABLE, GPIO_OWNER_GPIO);
|
|
||||||
check(external_5v_bus_enable_pin);
|
|
||||||
auto* lcd_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_LCD_RESET, GPIO_OWNER_GPIO);
|
|
||||||
check(lcd_reset_pin);
|
|
||||||
auto* touch_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_TOUCH_RESET, GPIO_OWNER_GPIO);
|
|
||||||
check(touch_reset_pin);
|
|
||||||
auto* camera_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_CAMERA_RESET, GPIO_OWNER_GPIO);
|
|
||||||
check(camera_reset_pin);
|
|
||||||
auto* headphone_detect_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_HEADPHONE_DETECT, GPIO_OWNER_GPIO);
|
|
||||||
check(headphone_detect_pin);
|
|
||||||
|
|
||||||
gpio_descriptor_set_flags(rf_pin, GPIO_FLAG_DIRECTION_OUTPUT);
|
|
||||||
gpio_descriptor_set_flags(speaker_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT);
|
|
||||||
gpio_descriptor_set_flags(external_5v_bus_enable_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(camera_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT);
|
|
||||||
gpio_descriptor_set_flags(headphone_detect_pin, GPIO_FLAG_DIRECTION_INPUT);
|
|
||||||
|
|
||||||
gpio_descriptor_set_level(rf_pin, false);
|
|
||||||
gpio_descriptor_set_level(speaker_enable_pin, false);
|
|
||||||
gpio_descriptor_set_level(external_5v_bus_enable_pin, true);
|
|
||||||
gpio_descriptor_set_level(lcd_reset_pin, false);
|
|
||||||
gpio_descriptor_set_level(touch_reset_pin, false);
|
|
||||||
gpio_descriptor_set_level(camera_reset_pin, true);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
|
||||||
// Enable touch and lcd, but not the camera
|
|
||||||
gpio_descriptor_set_level(lcd_reset_pin, true);
|
|
||||||
gpio_descriptor_set_level(touch_reset_pin, true);
|
|
||||||
|
|
||||||
gpio_descriptor_release(rf_pin);
|
|
||||||
gpio_descriptor_release(speaker_enable_pin);
|
|
||||||
gpio_descriptor_release(external_5v_bus_enable_pin);
|
|
||||||
gpio_descriptor_release(lcd_reset_pin);
|
|
||||||
gpio_descriptor_release(touch_reset_pin);
|
|
||||||
gpio_descriptor_release(camera_reset_pin);
|
|
||||||
gpio_descriptor_release(headphone_detect_pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void initExpander1(::Device* io_expander1) {
|
|
||||||
|
|
||||||
auto* c6_wlan_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_C6_WLAN_ENABLE, GPIO_OWNER_GPIO);
|
|
||||||
check(c6_wlan_enable_pin);
|
|
||||||
auto* usb_a_5v_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_USB_A_5V_ENABLE, GPIO_OWNER_GPIO);
|
|
||||||
check(usb_a_5v_enable_pin);
|
|
||||||
auto* device_power_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_DEVICE_POWER, GPIO_OWNER_GPIO);
|
|
||||||
check(device_power_pin);
|
|
||||||
auto* ip2326_ncharge_qc_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_NCHG_QC_EN, GPIO_OWNER_GPIO);
|
|
||||||
check(ip2326_ncharge_qc_enable_pin);
|
|
||||||
auto* ip2326_charge_state_led_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_CHG_STAT_LED, GPIO_OWNER_GPIO);
|
|
||||||
check(ip2326_charge_state_led_pin);
|
|
||||||
auto* ip2326_charge_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_CHG_EN, GPIO_OWNER_GPIO);
|
|
||||||
check(ip2326_charge_enable_pin);
|
|
||||||
|
|
||||||
gpio_descriptor_set_flags(c6_wlan_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT);
|
|
||||||
gpio_descriptor_set_flags(usb_a_5v_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT);
|
|
||||||
gpio_descriptor_set_flags(device_power_pin, GPIO_FLAG_DIRECTION_OUTPUT);
|
|
||||||
gpio_descriptor_set_flags(ip2326_ncharge_qc_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT);
|
|
||||||
gpio_descriptor_set_flags(ip2326_charge_state_led_pin, GPIO_FLAG_DIRECTION_OUTPUT);
|
|
||||||
gpio_descriptor_set_flags(ip2326_charge_enable_pin, GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_PULL_UP);
|
|
||||||
|
|
||||||
gpio_descriptor_set_level(c6_wlan_enable_pin, true);
|
|
||||||
gpio_descriptor_set_level(usb_a_5v_enable_pin, true);
|
|
||||||
gpio_descriptor_set_level(device_power_pin, false);
|
|
||||||
gpio_descriptor_set_level(ip2326_ncharge_qc_enable_pin, false);
|
|
||||||
gpio_descriptor_set_level(ip2326_charge_state_led_pin, false);
|
|
||||||
|
|
||||||
gpio_descriptor_release(c6_wlan_enable_pin);
|
|
||||||
gpio_descriptor_release(usb_a_5v_enable_pin);
|
|
||||||
gpio_descriptor_release(device_power_pin);
|
|
||||||
gpio_descriptor_release(ip2326_ncharge_qc_enable_pin);
|
|
||||||
gpio_descriptor_release(ip2326_charge_state_led_pin);
|
|
||||||
gpio_descriptor_release(ip2326_charge_enable_pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Audio codec register programming is owned by the es8388-module/es7210-module drivers
|
|
||||||
// (registered as AUDIO_CODEC_TYPE devices, see m5stack,tab5.dts). This board still needs
|
|
||||||
// to drive the external speaker amplifier's enable line via io_expander0, which is board
|
|
||||||
// wiring glue rather than codec configuration, so it stays here.
|
|
||||||
static error_t enableSpeakerAmplifier(::Device* io_expander0) {
|
|
||||||
auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO);
|
|
||||||
check(speaker_enable_pin, "Failed to acquire speaker enable pin");
|
|
||||||
error_t error = gpio_descriptor_set_level(speaker_enable_pin, true);
|
|
||||||
gpio_descriptor_release(speaker_enable_pin);
|
|
||||||
if (error != ERROR_NONE) {
|
|
||||||
LOG_E(TAG, "Failed to enable amplifier: %s", error_to_string(error));
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static esp_clock_output_mapping_handle_t camera_osc_handle = nullptr;
|
|
||||||
|
|
||||||
static void initCameraOsc() {
|
|
||||||
if (camera_osc_handle != nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 24 MHz clock on GPIO 36 for the SC2356 MIPI CSI sensor, required before esp_video_init.
|
|
||||||
// Uses SPLL (480 MHz) via esp_clock_output with divider 20 = 24 MHz exactly.
|
|
||||||
// This avoids any LEDC clock source conflict with the display backlight.
|
|
||||||
if (esp_clock_output_start(CLKOUT_SIG_SPLL, GPIO_NUM_36, &camera_osc_handle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Camera OSC clock output start failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (esp_clock_output_set_divider(camera_osc_handle, 20) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Camera OSC clock divider set failed");
|
|
||||||
esp_clock_output_stop(camera_osc_handle);
|
|
||||||
camera_osc_handle = nullptr;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LOG_I(TAG, "Camera OSC 24MHz started on GPIO 36 (SPLL/20)");
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool initBoot() {
|
|
||||||
auto* io_expander0 = device_find_by_name("io_expander0");
|
|
||||||
check(io_expander0, "io_expander0 not found");
|
|
||||||
auto* io_expander1 = device_find_by_name("io_expander1");
|
|
||||||
check(io_expander1, "io_expander1 not found");
|
|
||||||
|
|
||||||
initCameraOsc();
|
|
||||||
initExpander0(io_expander0);
|
|
||||||
initExpander1(io_expander1);
|
|
||||||
|
|
||||||
error_t error = enableSpeakerAmplifier(io_expander0);
|
|
||||||
if (error != ERROR_NONE) {
|
|
||||||
LOG_E(TAG, "Failed to enable speaker amplifier");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern const Configuration hardwareConfiguration = {
|
|
||||||
.initBoot = initBoot,
|
|
||||||
.createDevices = createDevices
|
|
||||||
};
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#include "Detect.h"
|
|
||||||
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/drivers/i2c_controller.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
#include <esp_lcd_touch_gt911.h>
|
|
||||||
#include <esp_lcd_touch_st7123.h>
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "Tab5Detect";
|
|
||||||
|
|
||||||
Tab5Variant detectVariant() {
|
|
||||||
// Allow time for touch IC to fully boot after expander reset in initBoot().
|
|
||||||
// 100ms is enough for I2C ACK (probe) but cold power-on needs ~300ms before
|
|
||||||
// register reads (read_fw_info) succeed reliably.
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(300));
|
|
||||||
|
|
||||||
auto* i2c0 = device_find_by_name("i2c0");
|
|
||||||
check(i2c0);
|
|
||||||
|
|
||||||
constexpr auto PROBE_TIMEOUT = pdMS_TO_TICKS(50);
|
|
||||||
|
|
||||||
for (int attempt = 0; attempt < 3; ++attempt) {
|
|
||||||
// GT911 address depends on INT pin state during reset:
|
|
||||||
// GPIO 23 has a pull-up resistor to 3V3, so INT is high at reset → GT911 uses 0x5D (primary)
|
|
||||||
// It may also appear at 0x14 (backup) if the pin happened to be driven low
|
|
||||||
if (i2c_controller_has_device_at_address(i2c0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, PROBE_TIMEOUT) == ERROR_NONE ||
|
|
||||||
i2c_controller_has_device_at_address(i2c0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP, PROBE_TIMEOUT) == ERROR_NONE) {
|
|
||||||
LOG_I(TAG, "Detected GT911 touch — using ILI9881C display");
|
|
||||||
return Tab5Variant::Ili9881c_Gt911;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probe for ST7123 touch (new variant)
|
|
||||||
if (i2c_controller_has_device_at_address(i2c0, ESP_LCD_TOUCH_IO_I2C_ST7123_ADDRESS, PROBE_TIMEOUT) == ERROR_NONE) {
|
|
||||||
LOG_I(TAG, "Detected ST7123 touch — using ST7123 display");
|
|
||||||
return Tab5Variant::St7123;
|
|
||||||
}
|
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_W(TAG, "No known touch controller detected, defaulting to ST7123");
|
|
||||||
return Tab5Variant::St7123;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
enum class Tab5Variant {
|
|
||||||
Ili9881c_Gt911, // Older variant
|
|
||||||
St7123, // Newer variant (default)
|
|
||||||
};
|
|
||||||
|
|
||||||
[[nodiscard]] Tab5Variant detectVariant();
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
#include "Detect.h"
|
|
||||||
#include "Display.h"
|
|
||||||
#include "Ili9881cDisplay.h"
|
|
||||||
#include "St7123Display.h"
|
|
||||||
#include "St7123Touch.h"
|
|
||||||
|
|
||||||
#include <Gt911Touch.h>
|
|
||||||
#include <PwmBacklight.h>
|
|
||||||
#include <Tactility/hal/gpio/Gpio.h>
|
|
||||||
#include <tactility/check.h>
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "Tab5Display";
|
|
||||||
|
|
||||||
// LCD reset is wired to the PI4IOE5V6408 IO expander (io_expander0, bit 4), pulsed in
|
|
||||||
// Configuration.cpp's initExpander0() before display creation - not a direct SoC GPIO.
|
|
||||||
constexpr auto LCD_PIN_RESET = GPIO_NUM_NC;
|
|
||||||
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_22;
|
|
||||||
|
|
||||||
static std::shared_ptr<tt::hal::touch::TouchDevice> createGt911Touch() {
|
|
||||||
auto* i2c = device_find_by_name("i2c0");
|
|
||||||
check(i2c);
|
|
||||||
auto configuration = std::make_unique<Gt911Touch::Configuration>(
|
|
||||||
i2c,
|
|
||||||
720,
|
|
||||||
1280,
|
|
||||||
false, // swapXY
|
|
||||||
false, // mirrorX
|
|
||||||
false, // mirrorY
|
|
||||||
GPIO_NUM_NC, // reset pin
|
|
||||||
GPIO_NUM_NC // "GPIO_NUM_23 cannot be used due to resistor to 3V3"
|
|
||||||
// https://github.com/espressif/esp-bsp/blob/ad668c765cbad177495a122181df0a70ff9f8f61/bsp/m5stack_tab5/src/m5stack_tab5.c#L76234
|
|
||||||
);
|
|
||||||
return std::make_shared<Gt911Touch>(std::move(configuration));
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::shared_ptr<tt::hal::touch::TouchDevice> createSt7123Touch() {
|
|
||||||
auto* i2c = device_find_by_name("i2c0");
|
|
||||||
check(i2c, "i2c0 not found");
|
|
||||||
auto configuration = std::make_unique<St7123Touch::Configuration>(
|
|
||||||
i2c,
|
|
||||||
720,
|
|
||||||
1280,
|
|
||||||
false, // swapXY
|
|
||||||
false, // mirrorX
|
|
||||||
false, // mirrorY
|
|
||||||
GPIO_NUM_23 // interrupt pin
|
|
||||||
);
|
|
||||||
return std::make_shared<St7123Touch>(std::move(configuration));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
|
||||||
// Initialize PWM backlight
|
|
||||||
if (!driver::pwmbacklight::init(LCD_PIN_BACKLIGHT, 5000, LEDC_TIMER_1, LEDC_CHANNEL_0)) {
|
|
||||||
LOG_W(TAG, "Failed to initialize backlight");
|
|
||||||
}
|
|
||||||
|
|
||||||
Tab5Variant variant = detectVariant();
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
|
||||||
|
|
||||||
if (variant == Tab5Variant::St7123) {
|
|
||||||
touch = createSt7123Touch();
|
|
||||||
} else {
|
|
||||||
touch = createGt911Touch();
|
|
||||||
|
|
||||||
// Work-around to init GT911 touch: interrupt pin must be set to low
|
|
||||||
// Note: There is a resistor to 3V3 on interrupt pin which is blocking GT911 touch
|
|
||||||
// See https://github.com/espressif/esp-bsp/blob/ad668c765cbad177495a122181df0a70ff9f8f61/bsp/m5stack_tab5/src/m5stack_tab5.c#L777
|
|
||||||
tt::hal::gpio::configure(23, tt::hal::gpio::Mode::Output, true, false);
|
|
||||||
tt::hal::gpio::setLevel(23, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto configuration = std::make_shared<EspLcdConfiguration>(EspLcdConfiguration {
|
|
||||||
.horizontalResolution = 720,
|
|
||||||
.verticalResolution = 1280,
|
|
||||||
.gapX = 0,
|
|
||||||
.gapY = 0,
|
|
||||||
.monochrome = false,
|
|
||||||
.swapXY = false,
|
|
||||||
.mirrorX = false,
|
|
||||||
.mirrorY = false,
|
|
||||||
.invertColor = false,
|
|
||||||
.bufferSize = 0, // 0 = default (1/10 of screen)
|
|
||||||
.swRotate = true,
|
|
||||||
.buffSpiram = true,
|
|
||||||
.touch = touch,
|
|
||||||
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
|
|
||||||
.resetPin = LCD_PIN_RESET,
|
|
||||||
.lvglColorFormat = LV_COLOR_FORMAT_RGB565,
|
|
||||||
.lvglSwapBytes = false,
|
|
||||||
.rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB,
|
|
||||||
.bitsPerPixel = 16
|
|
||||||
});
|
|
||||||
|
|
||||||
if (variant == Tab5Variant::St7123) {
|
|
||||||
return std::static_pointer_cast<tt::hal::display::DisplayDevice>(
|
|
||||||
std::make_shared<St7123Display>(configuration)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return std::static_pointer_cast<tt::hal::display::DisplayDevice>(
|
|
||||||
std::make_shared<Ili9881cDisplay>(configuration)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/display/DisplayDevice.h>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
#include "Ili9881cDisplay.h"
|
|
||||||
#include "ili9881_init_data.h"
|
|
||||||
|
|
||||||
#include <esp_lcd_ili9881c.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "ILI9881C";
|
|
||||||
|
|
||||||
Ili9881cDisplay::~Ili9881cDisplay() {
|
|
||||||
// TODO: This should happen during ::stop(), but this isn't currently exposed
|
|
||||||
if (mipiDsiBus != nullptr) {
|
|
||||||
esp_lcd_del_dsi_bus(mipiDsiBus);
|
|
||||||
mipiDsiBus = nullptr;
|
|
||||||
}
|
|
||||||
if (ldoChannel != nullptr) {
|
|
||||||
esp_ldo_release_channel(ldoChannel);
|
|
||||||
ldoChannel = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Ili9881cDisplay::createMipiDsiBus() {
|
|
||||||
esp_ldo_channel_config_t ldo_mipi_phy_config = {
|
|
||||||
.chan_id = 3,
|
|
||||||
.voltage_mv = 2500,
|
|
||||||
.flags = {
|
|
||||||
.adjustable = 0,
|
|
||||||
.owned_by_hw = 0,
|
|
||||||
.bypass = 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldoChannel) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to acquire LDO channel for MIPI DSI PHY");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "Powered on");
|
|
||||||
|
|
||||||
// Create bus
|
|
||||||
// TODO: use MIPI_DSI_PHY_CLK_SRC_DEFAULT() in future ESP-IDF 6.0.0 update with esp_lcd_jd9165 library version 2.x
|
|
||||||
const esp_lcd_dsi_bus_config_t bus_config = {
|
|
||||||
.bus_id = 0,
|
|
||||||
.num_data_lanes = 2,
|
|
||||||
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT,
|
|
||||||
.lane_bit_rate_mbps = 1000
|
|
||||||
};
|
|
||||||
|
|
||||||
if (esp_lcd_new_dsi_bus(&bus_config, &mipiDsiBus) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create bus");
|
|
||||||
esp_ldo_release_channel(ldoChannel);
|
|
||||||
ldoChannel = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "Bus created");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Ili9881cDisplay::createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) {
|
|
||||||
// Initialize MIPI DSI bus if not already done
|
|
||||||
if (mipiDsiBus == nullptr) {
|
|
||||||
if (!createMipiDsiBus()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use DBI interface to send LCD commands and parameters
|
|
||||||
esp_lcd_dbi_io_config_t dbi_config = ILI9881C_PANEL_IO_DBI_CONFIG();
|
|
||||||
|
|
||||||
if (esp_lcd_new_panel_io_dbi(mipiDsiBus, &dbi_config, &ioHandle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create panel IO");
|
|
||||||
esp_lcd_del_dsi_bus(mipiDsiBus);
|
|
||||||
mipiDsiBus = nullptr;
|
|
||||||
esp_ldo_release_channel(ldoChannel);
|
|
||||||
ldoChannel = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_lcd_panel_dev_config_t Ili9881cDisplay::createPanelConfig(std::shared_ptr<EspLcdConfiguration> espLcdConfiguration, gpio_num_t resetPin) {
|
|
||||||
return {
|
|
||||||
.reset_gpio_num = resetPin,
|
|
||||||
.rgb_ele_order = espLcdConfiguration->rgbElementOrder,
|
|
||||||
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
|
|
||||||
.bits_per_pixel = static_cast<uint8_t>(espLcdConfiguration->bitsPerPixel),
|
|
||||||
.flags = {
|
|
||||||
.reset_active_high = 0
|
|
||||||
},
|
|
||||||
.vendor_config = nullptr // Will be set in createPanelHandle
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Ili9881cDisplay::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) {
|
|
||||||
// Based on BSP: https://github.com/espressif/esp-bsp/blob/master/bsp/m5stack_tab5/README.md
|
|
||||||
// TODO: undo static
|
|
||||||
esp_lcd_dpi_panel_config_t dpi_config = {
|
|
||||||
.virtual_channel = 0,
|
|
||||||
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
|
|
||||||
.dpi_clock_freq_mhz = 60,
|
|
||||||
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
|
|
||||||
.in_color_format = LCD_COLOR_FMT_RGB565,
|
|
||||||
.out_color_format = LCD_COLOR_FMT_RGB565,
|
|
||||||
.num_fbs = 2,
|
|
||||||
.video_timing =
|
|
||||||
{
|
|
||||||
.h_size = 720,
|
|
||||||
.v_size = 1280,
|
|
||||||
.hsync_pulse_width = 40,
|
|
||||||
.hsync_back_porch = 140,
|
|
||||||
.hsync_front_porch = 40,
|
|
||||||
.vsync_pulse_width = 4,
|
|
||||||
.vsync_back_porch = 20,
|
|
||||||
.vsync_front_porch = 20,
|
|
||||||
},
|
|
||||||
.flags = {
|
|
||||||
.use_dma2d = 1, // TODO: true?
|
|
||||||
.disable_lp = 0,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ili9881c_vendor_config_t vendor_config = {
|
|
||||||
.init_cmds = ili9881_init_data,
|
|
||||||
.init_cmds_size = std::size(ili9881_init_data),
|
|
||||||
.mipi_config = {
|
|
||||||
.dsi_bus = mipiDsiBus,
|
|
||||||
.dpi_config = &dpi_config,
|
|
||||||
.lane_num = 2,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a mutable copy of panelConfig to set vendor_config
|
|
||||||
esp_lcd_panel_dev_config_t mutable_panel_config = panelConfig;
|
|
||||||
mutable_panel_config.vendor_config = &vendor_config;
|
|
||||||
|
|
||||||
if (esp_lcd_new_panel_ili9881c(ioHandle, &mutable_panel_config, &panelHandle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create panel");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "Panel created successfully");
|
|
||||||
// Defer reset/init to base class applyConfiguration to avoid double initialization
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
lvgl_port_display_dsi_cfg_t Ili9881cDisplay::getLvglPortDisplayDsiConfig(esp_lcd_panel_io_handle_t /*ioHandle*/, esp_lcd_panel_handle_t /*panelHandle*/) {
|
|
||||||
// Disable avoid_tearing to prevent stalls/blank flashes when other tasks (e.g. flash writes) block timing
|
|
||||||
return lvgl_port_display_dsi_cfg_t{
|
|
||||||
.flags = {
|
|
||||||
.avoid_tearing = 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <EspLcdDisplayV2.h>
|
|
||||||
|
|
||||||
#include <esp_lcd_mipi_dsi.h>
|
|
||||||
#include <esp_ldo_regulator.h>
|
|
||||||
|
|
||||||
class Ili9881cDisplay final : public EspLcdDisplayV2 {
|
|
||||||
|
|
||||||
esp_lcd_dsi_bus_handle_t mipiDsiBus = nullptr;
|
|
||||||
esp_ldo_channel_handle_t ldoChannel = nullptr;
|
|
||||||
|
|
||||||
bool createMipiDsiBus();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) override;
|
|
||||||
|
|
||||||
esp_lcd_panel_dev_config_t createPanelConfig(std::shared_ptr<EspLcdConfiguration> espLcdConfiguration, gpio_num_t resetPin) override;
|
|
||||||
|
|
||||||
bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) override;
|
|
||||||
|
|
||||||
bool useDsiPanel() const override { return true; }
|
|
||||||
|
|
||||||
lvgl_port_display_dsi_cfg_t getLvglPortDisplayDsiConfig(esp_lcd_panel_io_handle_t /*ioHandle*/, esp_lcd_panel_handle_t /*panelHandle*/) override;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
Ili9881cDisplay(
|
|
||||||
const std::shared_ptr<EspLcdConfiguration>& configuration
|
|
||||||
) : EspLcdDisplayV2(configuration) {}
|
|
||||||
|
|
||||||
~Ili9881cDisplay() override;
|
|
||||||
|
|
||||||
std::string getName() const override { return "ILI9881C"; }
|
|
||||||
|
|
||||||
std::string getDescription() const override { return "ILI9881C MIPI-DSI display"; }
|
|
||||||
};
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
#include "Power.h"
|
|
||||||
|
|
||||||
#include <Tactility/hal/power/PowerDevice.h>
|
|
||||||
#include <drivers/ina226.h>
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/drivers/gpio_controller.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
|
|
||||||
using namespace tt::hal::power;
|
|
||||||
|
|
||||||
static constexpr auto* TAG = "Tab5Power";
|
|
||||||
|
|
||||||
// NP-F550 is a 2S Li-ion pack; INA226 measures the pack voltage on BAT_IN
|
|
||||||
// before the DC-DC converter. Per-cell range 3.2-4.2V → pack range 6.4-8.4V.
|
|
||||||
static constexpr float MIN_BATTERY_VOLTAGE_MV = 6400.0f;
|
|
||||||
static constexpr float MAX_BATTERY_VOLTAGE_MV = 8400.0f;
|
|
||||||
|
|
||||||
// INA226 convention: negative raw current = charging, positive = discharging.
|
|
||||||
// After negation in getMetric(Current), >50mA means charging into the battery.
|
|
||||||
static constexpr float CHARGING_CURRENT_THRESHOLD_AMPS = 0.05f;
|
|
||||||
|
|
||||||
// GPIO expander 1 (0x44) pin 4: PWROFF_PULSE
|
|
||||||
static constexpr int GPIO_EXP1_PIN_DEVICE_POWER = 4;
|
|
||||||
// GPIO expander 1 (0x44) pin 5: IP2326 nCHG_QC_EN (active-low: LOW = QC enabled)
|
|
||||||
static constexpr int GPIO_EXP1_PIN_IP2326_NCHG_QC_EN = 5;
|
|
||||||
// GPIO expander 1 (0x44) pin 7: IP2326 CHG_EN (HIGH = charging enabled, LOW = disabled)
|
|
||||||
static constexpr int GPIO_EXP1_PIN_IP2326_CHG_EN = 7;
|
|
||||||
|
|
||||||
class Tab5Power final : public PowerDevice {
|
|
||||||
public:
|
|
||||||
Tab5Power(::Device* ina226Device, ::Device* ioExpander1Device)
|
|
||||||
: ina226(ina226Device), ioExpander1(ioExpander1Device) {
|
|
||||||
// Initialize CHG_EN as output HIGH (charging enabled at startup).
|
|
||||||
setAllowedToCharge(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getName() const override { return "M5Stack Tab5 Power"; }
|
|
||||||
std::string getDescription() const override { return "Battery monitoring via INA226 over I2C"; }
|
|
||||||
|
|
||||||
bool supportsMetric(MetricType type) const override {
|
|
||||||
switch (type) {
|
|
||||||
using enum MetricType;
|
|
||||||
case BatteryVoltage:
|
|
||||||
case ChargeLevel:
|
|
||||||
case Current:
|
|
||||||
case IsCharging:
|
|
||||||
return ina226 != nullptr;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool getMetric(MetricType type, MetricData& data) override {
|
|
||||||
switch (type) {
|
|
||||||
using enum MetricType;
|
|
||||||
|
|
||||||
case BatteryVoltage: {
|
|
||||||
if (ina226 == nullptr) return false;
|
|
||||||
float volts = 0.0f;
|
|
||||||
if (ina226_read_bus_voltage(ina226, &volts) != ERROR_NONE) return false;
|
|
||||||
data.valueAsUint32 = static_cast<uint32_t>(volts * 1000.0f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ChargeLevel: {
|
|
||||||
if (ina226 == nullptr) return false;
|
|
||||||
float volts = 0.0f;
|
|
||||||
if (ina226_read_bus_voltage(ina226, &volts) != ERROR_NONE) return false;
|
|
||||||
float voltage_mv = volts * 1000.0f;
|
|
||||||
if (voltage_mv >= MAX_BATTERY_VOLTAGE_MV) {
|
|
||||||
data.valueAsUint8 = 100;
|
|
||||||
} else if (voltage_mv <= MIN_BATTERY_VOLTAGE_MV) {
|
|
||||||
data.valueAsUint8 = 0;
|
|
||||||
} else {
|
|
||||||
float factor = (voltage_mv - MIN_BATTERY_VOLTAGE_MV) / (MAX_BATTERY_VOLTAGE_MV - MIN_BATTERY_VOLTAGE_MV);
|
|
||||||
data.valueAsUint8 = static_cast<uint8_t>(factor * 100.0f);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Current: {
|
|
||||||
if (ina226 == nullptr) return false;
|
|
||||||
float amps = 0.0f;
|
|
||||||
if (ina226_read_shunt_current(ina226, &s) != ERROR_NONE) return false;
|
|
||||||
// INA226 convention: negative = charging, positive = discharging.
|
|
||||||
// Negate so the HAL value is positive when charging, negative when discharging.
|
|
||||||
data.valueAsInt32 = static_cast<int32_t>(-amps * 1000.0f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case IsCharging: {
|
|
||||||
if (ina226 == nullptr) return false;
|
|
||||||
float amps = 0.0f;
|
|
||||||
if (ina226_read_shunt_current(ina226, &s) != ERROR_NONE) return false;
|
|
||||||
// Raw INA226: negative = charging. Threshold in raw terms = -0.05A.
|
|
||||||
data.valueAsBool = amps < -CHARGING_CURRENT_THRESHOLD_AMPS;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool supportsChargeControl() const override { return ioExpander1 != nullptr; }
|
|
||||||
|
|
||||||
bool isAllowedToCharge() const override { return chargingAllowed; }
|
|
||||||
|
|
||||||
void setAllowedToCharge(bool allowed) override {
|
|
||||||
if (ioExpander1 == nullptr) return;
|
|
||||||
auto* pin = gpio_descriptor_acquire(ioExpander1, GPIO_EXP1_PIN_IP2326_CHG_EN, GPIO_OWNER_GPIO);
|
|
||||||
if (pin == nullptr) {
|
|
||||||
LOG_W(TAG, "Failed to acquire CHG_EN pin");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (gpio_descriptor_set_flags(pin, GPIO_FLAG_DIRECTION_OUTPUT) != ERROR_NONE) {
|
|
||||||
LOG_W(TAG, "Failed to set CHG_EN pin direction");
|
|
||||||
gpio_descriptor_release(pin);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (gpio_descriptor_set_level(pin, allowed) != ERROR_NONE) {
|
|
||||||
LOG_W(TAG, "Failed to set CHG_EN pin level");
|
|
||||||
gpio_descriptor_release(pin);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gpio_descriptor_release(pin);
|
|
||||||
chargingAllowed = allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool supportsQuickCharge() const override { return ioExpander1 != nullptr; }
|
|
||||||
|
|
||||||
bool isQuickChargeEnabled() const override { return quickChargeEnabled; }
|
|
||||||
|
|
||||||
void setQuickChargeEnabled(bool enabled) override {
|
|
||||||
if (ioExpander1 == nullptr) return;
|
|
||||||
auto* pin = gpio_descriptor_acquire(ioExpander1, GPIO_EXP1_PIN_IP2326_NCHG_QC_EN, GPIO_OWNER_GPIO);
|
|
||||||
if (pin == nullptr) {
|
|
||||||
LOG_W(TAG, "Failed to acquire nCHG_QC_EN pin");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (gpio_descriptor_set_flags(pin, GPIO_FLAG_DIRECTION_OUTPUT) != ERROR_NONE) {
|
|
||||||
LOG_W(TAG, "Failed to set nCHG_QC_EN pin direction");
|
|
||||||
gpio_descriptor_release(pin);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (gpio_descriptor_set_level(pin, !enabled) != ERROR_NONE) {
|
|
||||||
LOG_W(TAG, "Failed to set nCHG_QC_EN pin level");
|
|
||||||
gpio_descriptor_release(pin);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gpio_descriptor_release(pin);
|
|
||||||
quickChargeEnabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool supportsPowerOff() const override { return ioExpander1 != nullptr; }
|
|
||||||
|
|
||||||
void powerOff() override {
|
|
||||||
if (ioExpander1 == nullptr) return;
|
|
||||||
auto* pin = gpio_descriptor_acquire(ioExpander1, GPIO_EXP1_PIN_DEVICE_POWER, GPIO_OWNER_GPIO);
|
|
||||||
if (pin == nullptr) {
|
|
||||||
LOG_E(TAG, "Failed to acquire DEVICE_POWER pin");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
gpio_descriptor_set_level(pin, true);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
|
||||||
gpio_descriptor_set_level(pin, false);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
|
||||||
}
|
|
||||||
gpio_descriptor_release(pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
::Device* ina226;
|
|
||||||
::Device* ioExpander1;
|
|
||||||
bool chargingAllowed = true;
|
|
||||||
bool quickChargeEnabled = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<PowerDevice> createPower() {
|
|
||||||
auto* ina226 = device_find_by_name("ina226");
|
|
||||||
if (ina226 == nullptr) {
|
|
||||||
LOG_E(TAG, "ina226 device not found");
|
|
||||||
}
|
|
||||||
auto* io_expander1 = device_find_by_name("io_expander1");
|
|
||||||
if (io_expander1 == nullptr) {
|
|
||||||
LOG_E(TAG, "io_expander1 not found");
|
|
||||||
}
|
|
||||||
return std::make_shared<Tab5Power>(ina226, io_expander1);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <Tactility/hal/power/PowerDevice.h>
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::power::PowerDevice> createPower();
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
#include "St7123Display.h"
|
|
||||||
#include "st7123_init_data.h"
|
|
||||||
|
|
||||||
#include <esp_lcd_st7123.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "St7123";
|
|
||||||
|
|
||||||
St7123Display::~St7123Display() {
|
|
||||||
// TODO: This should happen during ::stop(), but this isn't currently exposed
|
|
||||||
if (mipiDsiBus != nullptr) {
|
|
||||||
esp_lcd_del_dsi_bus(mipiDsiBus);
|
|
||||||
mipiDsiBus = nullptr;
|
|
||||||
}
|
|
||||||
if (ldoChannel != nullptr) {
|
|
||||||
esp_ldo_release_channel(ldoChannel);
|
|
||||||
ldoChannel = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool St7123Display::createMipiDsiBus() {
|
|
||||||
esp_ldo_channel_config_t ldo_mipi_phy_config = {
|
|
||||||
.chan_id = 3,
|
|
||||||
.voltage_mv = 2500,
|
|
||||||
.flags = {
|
|
||||||
.adjustable = 0,
|
|
||||||
.owned_by_hw = 0,
|
|
||||||
.bypass = 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldoChannel) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to acquire LDO channel for MIPI DSI PHY");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "Powered on");
|
|
||||||
|
|
||||||
const esp_lcd_dsi_bus_config_t bus_config = {
|
|
||||||
.bus_id = 0,
|
|
||||||
.num_data_lanes = 2,
|
|
||||||
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT,
|
|
||||||
.lane_bit_rate_mbps = 965 // ST7123 lane bitrate per M5Stack BSP
|
|
||||||
};
|
|
||||||
|
|
||||||
if (esp_lcd_new_dsi_bus(&bus_config, &mipiDsiBus) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create bus");
|
|
||||||
esp_ldo_release_channel(ldoChannel);
|
|
||||||
ldoChannel = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "Bus created");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool St7123Display::createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) {
|
|
||||||
if (mipiDsiBus == nullptr) {
|
|
||||||
if (!createMipiDsiBus()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DBI interface for LCD commands/parameters (8-bit cmd/param per ST7123 spec)
|
|
||||||
esp_lcd_dbi_io_config_t dbi_config = {
|
|
||||||
.virtual_channel = 0,
|
|
||||||
.lcd_cmd_bits = 8,
|
|
||||||
.lcd_param_bits = 8,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (esp_lcd_new_panel_io_dbi(mipiDsiBus, &dbi_config, &ioHandle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create panel IO");
|
|
||||||
esp_lcd_del_dsi_bus(mipiDsiBus);
|
|
||||||
mipiDsiBus = nullptr;
|
|
||||||
esp_ldo_release_channel(ldoChannel);
|
|
||||||
ldoChannel = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_lcd_panel_dev_config_t St7123Display::createPanelConfig(std::shared_ptr<EspLcdConfiguration> espLcdConfiguration, gpio_num_t resetPin) {
|
|
||||||
return {
|
|
||||||
.reset_gpio_num = resetPin,
|
|
||||||
.rgb_ele_order = espLcdConfiguration->rgbElementOrder,
|
|
||||||
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
|
|
||||||
.bits_per_pixel = 16,
|
|
||||||
.flags = {
|
|
||||||
.reset_active_high = 0
|
|
||||||
},
|
|
||||||
.vendor_config = nullptr
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool St7123Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) {
|
|
||||||
esp_lcd_dpi_panel_config_t dpi_config = {
|
|
||||||
.virtual_channel = 0,
|
|
||||||
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
|
|
||||||
.dpi_clock_freq_mhz = 70,
|
|
||||||
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
|
|
||||||
.num_fbs = 2,
|
|
||||||
.video_timing = {
|
|
||||||
.h_size = 720,
|
|
||||||
.v_size = 1280,
|
|
||||||
.hsync_pulse_width = 2,
|
|
||||||
.hsync_back_porch = 40,
|
|
||||||
.hsync_front_porch = 40,
|
|
||||||
.vsync_pulse_width = 2,
|
|
||||||
.vsync_back_porch = 8,
|
|
||||||
.vsync_front_porch = 220,
|
|
||||||
},
|
|
||||||
.flags = {
|
|
||||||
.use_dma2d = 1,
|
|
||||||
.disable_lp = 0,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
st7123_vendor_config_t vendor_config = {
|
|
||||||
.init_cmds = st7123_init_data,
|
|
||||||
.init_cmds_size = std::size(st7123_init_data),
|
|
||||||
.mipi_config = {
|
|
||||||
.dsi_bus = mipiDsiBus,
|
|
||||||
.dpi_config = &dpi_config,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a mutable copy of panelConfig to set vendor_config
|
|
||||||
esp_lcd_panel_dev_config_t mutable_panel_config = panelConfig;
|
|
||||||
mutable_panel_config.vendor_config = &vendor_config;
|
|
||||||
|
|
||||||
if (esp_lcd_new_panel_st7123(ioHandle, &mutable_panel_config, &panelHandle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create panel");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "Panel created successfully");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
lvgl_port_display_dsi_cfg_t St7123Display::getLvglPortDisplayDsiConfig(esp_lcd_panel_io_handle_t /*ioHandle*/, esp_lcd_panel_handle_t /*panelHandle*/) {
|
|
||||||
// Disable avoid_tearing to prevent stalls/blank flashes when other tasks (e.g. flash writes) block timing
|
|
||||||
return lvgl_port_display_dsi_cfg_t{
|
|
||||||
.flags = {
|
|
||||||
.avoid_tearing = 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <EspLcdDisplayV2.h>
|
|
||||||
|
|
||||||
#include <esp_lcd_mipi_dsi.h>
|
|
||||||
#include <esp_ldo_regulator.h>
|
|
||||||
|
|
||||||
class St7123Display final : public EspLcdDisplayV2 {
|
|
||||||
|
|
||||||
esp_lcd_dsi_bus_handle_t mipiDsiBus = nullptr;
|
|
||||||
esp_ldo_channel_handle_t ldoChannel = nullptr;
|
|
||||||
|
|
||||||
bool createMipiDsiBus();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) override;
|
|
||||||
|
|
||||||
esp_lcd_panel_dev_config_t createPanelConfig(std::shared_ptr<EspLcdConfiguration> espLcdConfiguration, gpio_num_t resetPin) override;
|
|
||||||
|
|
||||||
bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) override;
|
|
||||||
|
|
||||||
bool useDsiPanel() const override { return true; }
|
|
||||||
|
|
||||||
lvgl_port_display_dsi_cfg_t getLvglPortDisplayDsiConfig(esp_lcd_panel_io_handle_t /*ioHandle*/, esp_lcd_panel_handle_t /*panelHandle*/) override;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
St7123Display(
|
|
||||||
const std::shared_ptr<EspLcdConfiguration>& configuration
|
|
||||||
) : EspLcdDisplayV2(configuration) {}
|
|
||||||
|
|
||||||
~St7123Display() override;
|
|
||||||
|
|
||||||
std::string getName() const override { return "St7123"; }
|
|
||||||
|
|
||||||
std::string getDescription() const override { return "St7123 MIPI-DSI display"; }
|
|
||||||
};
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
#include "St7123Touch.h"
|
|
||||||
|
|
||||||
#include <tactility/drivers/esp32_i2c_master.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
#include <esp_lcd_touch_st7123.h>
|
|
||||||
#include <esp_err.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "ST7123Touch";
|
|
||||||
|
|
||||||
bool St7123Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
|
|
||||||
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_ST7123_CONFIG();
|
|
||||||
io_config.scl_speed_hz = esp32_i2c_master_get_clock_frequency(configuration->controller);
|
|
||||||
i2c_master_bus_handle_t bus = esp32_i2c_master_get_bus_handle(configuration->controller);
|
|
||||||
return esp_lcd_new_panel_io_i2c_v2(bus, &io_config, &outHandle) == ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool St7123Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& config, esp_lcd_touch_handle_t& touchHandle) {
|
|
||||||
return esp_lcd_touch_new_i2c_st7123(ioHandle, &config, &touchHandle) == ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_lcd_touch_config_t St7123Touch::createEspLcdTouchConfig() {
|
|
||||||
return {
|
|
||||||
.x_max = configuration->xMax,
|
|
||||||
.y_max = configuration->yMax,
|
|
||||||
.rst_gpio_num = GPIO_NUM_NC,
|
|
||||||
.int_gpio_num = configuration->pinInterrupt,
|
|
||||||
.levels = {
|
|
||||||
.reset = 0,
|
|
||||||
.interrupt = 0,
|
|
||||||
},
|
|
||||||
.flags = {
|
|
||||||
.swap_xy = configuration->swapXy,
|
|
||||||
.mirror_x = configuration->mirrorX,
|
|
||||||
.mirror_y = configuration->mirrorY,
|
|
||||||
},
|
|
||||||
.process_coordinates = nullptr,
|
|
||||||
.interrupt_callback = nullptr,
|
|
||||||
.user_data = nullptr,
|
|
||||||
.driver_data = nullptr
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <EspLcdTouch.h>
|
|
||||||
#include <Tactility/TactilityCore.h>
|
|
||||||
#include <tactility/device.h>
|
|
||||||
|
|
||||||
class St7123Touch final : public EspLcdTouch {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
class Configuration {
|
|
||||||
public:
|
|
||||||
|
|
||||||
Configuration(
|
|
||||||
::Device* controller,
|
|
||||||
uint16_t xMax,
|
|
||||||
uint16_t yMax,
|
|
||||||
bool swapXy = false,
|
|
||||||
bool mirrorX = false,
|
|
||||||
bool mirrorY = false,
|
|
||||||
gpio_num_t pinInterrupt = GPIO_NUM_NC
|
|
||||||
) : controller(controller),
|
|
||||||
xMax(xMax),
|
|
||||||
yMax(yMax),
|
|
||||||
swapXy(swapXy),
|
|
||||||
mirrorX(mirrorX),
|
|
||||||
mirrorY(mirrorY),
|
|
||||||
pinInterrupt(pinInterrupt)
|
|
||||||
{}
|
|
||||||
|
|
||||||
::Device* controller;
|
|
||||||
uint16_t xMax;
|
|
||||||
uint16_t yMax;
|
|
||||||
bool swapXy;
|
|
||||||
bool mirrorX;
|
|
||||||
bool mirrorY;
|
|
||||||
gpio_num_t pinInterrupt;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
std::unique_ptr<Configuration> configuration;
|
|
||||||
|
|
||||||
bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override;
|
|
||||||
|
|
||||||
bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& config, esp_lcd_touch_handle_t& touchHandle) override;
|
|
||||||
|
|
||||||
esp_lcd_touch_config_t createEspLcdTouchConfig() override;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit St7123Touch(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
|
|
||||||
assert(configuration != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getName() const override { return "ST7123Touch"; }
|
|
||||||
|
|
||||||
std::string getDescription() const override { return "ST7123 I2C touch driver"; }
|
|
||||||
};
|
|
||||||
@@ -1,561 +0,0 @@
|
|||||||
#include "Tab5Keyboard.h"
|
|
||||||
#include <Tactility/app/App.h>
|
|
||||||
#include <Tactility/lvgl/Keyboard.h>
|
|
||||||
#include <Tactility/lvgl/LvglSync.h>
|
|
||||||
#include <tactility/drivers/i2c_controller.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
#include <esp_timer.h>
|
|
||||||
#include <lvgl.h>
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Register addresses
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
static constexpr uint8_t REG_INT_CFG = 0x00;
|
|
||||||
static constexpr uint8_t REG_INT_STAT = 0x01;
|
|
||||||
static constexpr uint8_t REG_EVENT_NUM = 0x02;
|
|
||||||
static constexpr uint8_t REG_BRIGHTNESS = 0x03;
|
|
||||||
static constexpr uint8_t REG_KEYBOARD_MODE = 0x10;
|
|
||||||
static constexpr uint8_t REG_RGB_MODE = 0x11;
|
|
||||||
static constexpr uint8_t REG_KEY_EVENT = 0x20;
|
|
||||||
static constexpr uint8_t REG_RGB_BASE = 0x60;
|
|
||||||
static constexpr uint8_t KEY_EVENT_EMPTY = 0xFF;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Modifier key positions in the 5x14 matrix
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
static constexpr uint8_t MOD_ROW_SYM = 3, MOD_COL_SYM = 0;
|
|
||||||
static constexpr uint8_t MOD_ROW_AA = 3, MOD_COL_AA = 1;
|
|
||||||
static constexpr uint8_t MOD_ROW_CTRL = 4, MOD_COL_CTRL = 0;
|
|
||||||
static constexpr uint8_t MOD_ROW_ALT = 4, MOD_COL_ALT = 1;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// HID lookup tables
|
|
||||||
// Row-major: index = row * 14 + col, 5 rows x 14 cols = 70 entries.
|
|
||||||
// modifier 0x02 = Left Shift (pre-baked by firmware for shifted characters).
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
struct HidMapping {
|
|
||||||
uint8_t keycode;
|
|
||||||
uint8_t modifier;
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr HidMapping KEY_MATRIX_HID_BASE[70] = {
|
|
||||||
// Row 0: Esc 1 2 3 4 5 6 7 8 9 0 - + Del
|
|
||||||
{0x29, 0x00}, {0x1E, 0x00}, {0x1F, 0x00}, {0x20, 0x00}, {0x21, 0x00}, {0x22, 0x00},
|
|
||||||
{0x23, 0x00}, {0x24, 0x00}, {0x25, 0x00}, {0x26, 0x00}, {0x27, 0x00}, {0x2D, 0x00},
|
|
||||||
{0x2E, 0x02}, {0x4C, 0x00},
|
|
||||||
// Row 1: ` ! @ # $ % ^ & * ( ) [ ] backslash
|
|
||||||
{0x35, 0x00}, {0x1E, 0x02}, {0x1F, 0x02}, {0x20, 0x02}, {0x21, 0x02}, {0x22, 0x02},
|
|
||||||
{0x23, 0x02}, {0x24, 0x02}, {0x25, 0x02}, {0x26, 0x02}, {0x27, 0x02}, {0x2F, 0x00},
|
|
||||||
{0x30, 0x00}, {0x31, 0x00},
|
|
||||||
// Row 2: Tab q w e r t y u i o p ; ' Backspace
|
|
||||||
{0x2B, 0x00}, {0x14, 0x00}, {0x1A, 0x00}, {0x08, 0x00}, {0x15, 0x00}, {0x17, 0x00},
|
|
||||||
{0x1C, 0x00}, {0x18, 0x00}, {0x0C, 0x00}, {0x12, 0x00}, {0x13, 0x00}, {0x33, 0x00},
|
|
||||||
{0x34, 0x00}, {0x2A, 0x00},
|
|
||||||
// Row 3: Sym Aa a s d f g h j k l ↑ _ Enter
|
|
||||||
{0x00, 0x00}, {0x00, 0x00}, {0x04, 0x00}, {0x16, 0x00}, {0x07, 0x00}, {0x09, 0x00},
|
|
||||||
{0x0A, 0x00}, {0x0B, 0x00}, {0x0D, 0x00}, {0x0E, 0x00}, {0x0F, 0x00}, {0x52, 0x00},
|
|
||||||
{0x2D, 0x02}, {0x28, 0x00},
|
|
||||||
// Row 4: Ctrl Alt z x c v b n m . ← ↓ → Space
|
|
||||||
{0x00, 0x00}, {0x00, 0x00}, {0x1D, 0x00}, {0x1B, 0x00}, {0x06, 0x00}, {0x19, 0x00},
|
|
||||||
{0x05, 0x00}, {0x11, 0x00}, {0x10, 0x00}, {0x37, 0x00}, {0x50, 0x00}, {0x51, 0x00},
|
|
||||||
{0x4F, 0x00}, {0x2C, 0x00},
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr HidMapping KEY_MATRIX_HID_SYM[70] = {
|
|
||||||
// Row 0: identical to base
|
|
||||||
{0x29, 0x00}, {0x1E, 0x00}, {0x1F, 0x00}, {0x20, 0x00}, {0x21, 0x00}, {0x22, 0x00},
|
|
||||||
{0x23, 0x00}, {0x24, 0x00}, {0x25, 0x00}, {0x26, 0x00}, {0x27, 0x00}, {0x2D, 0x00},
|
|
||||||
{0x2E, 0x02}, {0x4C, 0x00},
|
|
||||||
// Row 1: Sym deltas: ` → ~, ! → ?, * → /, ( → <, ) → >, [ → {, ] → }, backslash → |
|
|
||||||
{0x35, 0x02}, {0x38, 0x02}, {0x1F, 0x02}, {0x20, 0x02}, {0x21, 0x02}, {0x22, 0x02},
|
|
||||||
{0x23, 0x02}, {0x24, 0x02}, {0x38, 0x00}, {0x36, 0x02}, {0x37, 0x02}, {0x2F, 0x02},
|
|
||||||
{0x30, 0x02}, {0x31, 0x02},
|
|
||||||
// Row 2: Sym deltas: ; → :, ' → "
|
|
||||||
{0x2B, 0x00}, {0x14, 0x00}, {0x1A, 0x00}, {0x08, 0x00}, {0x15, 0x00}, {0x17, 0x00},
|
|
||||||
{0x1C, 0x00}, {0x18, 0x00}, {0x0C, 0x00}, {0x12, 0x00}, {0x13, 0x00}, {0x33, 0x02},
|
|
||||||
{0x34, 0x02}, {0x2A, 0x00},
|
|
||||||
// Row 3: Sym delta: _ → =
|
|
||||||
{0x00, 0x00}, {0x00, 0x00}, {0x04, 0x00}, {0x16, 0x00}, {0x07, 0x00}, {0x09, 0x00},
|
|
||||||
{0x0A, 0x00}, {0x0B, 0x00}, {0x0D, 0x00}, {0x0E, 0x00}, {0x0F, 0x00}, {0x52, 0x00},
|
|
||||||
{0x2E, 0x00}, {0x28, 0x00},
|
|
||||||
// Row 4: Sym delta: . → ,
|
|
||||||
{0x00, 0x00}, {0x00, 0x00}, {0x1D, 0x00}, {0x1B, 0x00}, {0x06, 0x00}, {0x19, 0x00},
|
|
||||||
{0x05, 0x00}, {0x11, 0x00}, {0x10, 0x00}, {0x36, 0x00}, {0x50, 0x00}, {0x51, 0x00},
|
|
||||||
{0x4F, 0x00}, {0x2C, 0x00},
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// HID usage code + modifier → LVGL key
|
|
||||||
// Covers all codes present in the Tab5 matrix tables above.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
static uint32_t tab5TranslateKey(uint8_t keycode, uint8_t modifier, bool ctrl) {
|
|
||||||
const bool shift = (modifier & 0x22U) != 0U;
|
|
||||||
|
|
||||||
// Navigation → LVGL key constants
|
|
||||||
switch (keycode) {
|
|
||||||
case 0x29: return LV_KEY_ESC;
|
|
||||||
case 0x28: return LV_KEY_ENTER;
|
|
||||||
case 0x2A: return LV_KEY_BACKSPACE;
|
|
||||||
case 0x4C: return LV_KEY_DEL;
|
|
||||||
case 0x2B: return '\t';
|
|
||||||
// Arrows: Ctrl+arrow = focus navigation, plain arrow = raw cursor movement
|
|
||||||
case 0x52: return ctrl ? (uint32_t)LV_KEY_PREV : (uint32_t)LV_KEY_UP;
|
|
||||||
case 0x51: return ctrl ? (uint32_t)LV_KEY_NEXT : (uint32_t)LV_KEY_DOWN;
|
|
||||||
case 0x50: return ctrl ? (uint32_t)LV_KEY_PREV : (uint32_t)LV_KEY_LEFT;
|
|
||||||
case 0x4F: return ctrl ? (uint32_t)LV_KEY_NEXT : (uint32_t)LV_KEY_RIGHT;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Letters a–z / A–Z
|
|
||||||
if (keycode >= 0x04U && keycode <= 0x1DU) {
|
|
||||||
uint32_t c = static_cast<uint32_t>('a' + (keycode - 0x04U));
|
|
||||||
return shift ? (c - 0x20U) : c;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Numbers 1–0 and their shifted symbols
|
|
||||||
if (keycode >= 0x1EU && keycode <= 0x27U) {
|
|
||||||
static constexpr char nums[] = "1234567890";
|
|
||||||
static constexpr char snums[] = "!@#$%^&*()";
|
|
||||||
return shift ? static_cast<uint32_t>(snums[keycode - 0x1EU])
|
|
||||||
: static_cast<uint32_t>(nums[keycode - 0x1EU]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Space and punctuation - all codes present in the Tab5 matrix
|
|
||||||
switch (keycode) {
|
|
||||||
case 0x2C: return ' ';
|
|
||||||
case 0x2D: return shift ? '_' : '-';
|
|
||||||
case 0x2E: return shift ? '+' : '=';
|
|
||||||
case 0x2F: return shift ? '{' : '[';
|
|
||||||
case 0x30: return shift ? '}' : ']';
|
|
||||||
case 0x31: return shift ? '|' : '\\';
|
|
||||||
case 0x33: return shift ? ':' : ';';
|
|
||||||
case 0x34: return shift ? '"' : '\'';
|
|
||||||
case 0x35: return shift ? '~' : '`';
|
|
||||||
case 0x36: return shift ? '<' : ',';
|
|
||||||
case 0x37: return shift ? '>' : '.';
|
|
||||||
case 0x38: return shift ? '?' : '/';
|
|
||||||
default: return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// I2C helpers - use Tactility I2C controller API
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
bool Tab5Keyboard::readReg(uint8_t reg, uint8_t& value) {
|
|
||||||
return i2c_controller_read_register(i2cController, I2C_ADDRESS, reg, &value, 1, pdMS_TO_TICKS(50)) == ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Tab5Keyboard::writeReg(uint8_t reg, uint8_t value) {
|
|
||||||
return i2c_controller_write_register(i2cController, I2C_ADDRESS, reg, &value, 1, pdMS_TO_TICKS(50)) == ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// LED helpers - LED0 = Sym indicator (green), LED1 = Aa indicator (red)
|
|
||||||
// RGB register layout: [B, G, R] per LED, stride 4 (byte 3 reserved)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
void Tab5Keyboard::updateLeds() {
|
|
||||||
// [LED0: B,G,R, reserved, LED1: B,G,R]
|
|
||||||
uint8_t buf[7] = {
|
|
||||||
0x00, symActive ? uint8_t(0xA0) : uint8_t(0x00), 0x00, 0x00, // LED0: green if Sym
|
|
||||||
0x00, 0x00, aaSticky ? uint8_t(0xA0) : uint8_t(0x00), // LED1: red if Aa latched
|
|
||||||
};
|
|
||||||
// Write 7-byte block starting at REG_RGB_BASE
|
|
||||||
i2c_controller_write_register(i2cController, I2C_ADDRESS, REG_RGB_BASE, buf, 7, pdMS_TO_TICKS(50));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// IRQ pin - GPIO 50, active-low, falling edge
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
void IRAM_ATTR Tab5Keyboard::irqHandler(void* arg) {
|
|
||||||
auto* self = static_cast<Tab5Keyboard*>(arg);
|
|
||||||
self->irqPending = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Tab5Keyboard::configureIrqPin() {
|
|
||||||
gpio_config_t io_conf{};
|
|
||||||
io_conf.pin_bit_mask = (1ULL << INT_PIN);
|
|
||||||
io_conf.mode = GPIO_MODE_INPUT;
|
|
||||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
|
||||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
|
||||||
io_conf.intr_type = GPIO_INTR_NEGEDGE;
|
|
||||||
if (gpio_config(&io_conf) != ESP_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const esp_err_t svc = gpio_install_isr_service(0);
|
|
||||||
if (svc != ESP_OK && svc != ESP_ERR_INVALID_STATE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gpio_isr_handler_add(INT_PIN, irqHandler, this) != ESP_OK) {
|
|
||||||
gpio_set_intr_type(INT_PIN, GPIO_INTR_DISABLE);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
irqConfigured = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tab5Keyboard::removeIrqPin() {
|
|
||||||
if (!irqConfigured) return;
|
|
||||||
gpio_isr_handler_remove(INT_PIN);
|
|
||||||
irqConfigured = false;
|
|
||||||
irqPending = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// drainEvents - reads all pending events from the device queue
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
void Tab5Keyboard::drainEvents() {
|
|
||||||
uint8_t count = 0;
|
|
||||||
if (!readReg(REG_EVENT_NUM, count) || count == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (count > 0) {
|
|
||||||
uint8_t raw = 0;
|
|
||||||
if (!readReg(REG_KEY_EVENT, raw) || raw == KEY_EVENT_EMPTY) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool pressed = (raw & 0x80U) != 0U;
|
|
||||||
const uint8_t row = (raw >> 4U) & 0x07U;
|
|
||||||
const uint8_t col = raw & 0x0FU;
|
|
||||||
|
|
||||||
// Modifier keys: update state, no key output
|
|
||||||
if (row == MOD_ROW_SYM && col == MOD_COL_SYM) {
|
|
||||||
symActive = pressed;
|
|
||||||
updateLeds();
|
|
||||||
count--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (row == MOD_ROW_AA && col == MOD_COL_AA) {
|
|
||||||
if (pressed) {
|
|
||||||
aaHeld = true;
|
|
||||||
aaTapped = true; // assume tap until a real key is pressed while held
|
|
||||||
} else {
|
|
||||||
// Only latch sticky if no non-modifier key was pressed during this hold
|
|
||||||
if (aaTapped) {
|
|
||||||
aaSticky = !aaSticky;
|
|
||||||
}
|
|
||||||
aaHeld = false;
|
|
||||||
aaTapped = false;
|
|
||||||
}
|
|
||||||
updateLeds();
|
|
||||||
count--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (row == MOD_ROW_CTRL && col == MOD_COL_CTRL) {
|
|
||||||
ctrlHeld = pressed;
|
|
||||||
count--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (row == MOD_ROW_ALT && col == MOD_COL_ALT) {
|
|
||||||
count--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (row < 5U && col < 14U) {
|
|
||||||
const bool aaActive = aaHeld || aaSticky;
|
|
||||||
const HidMapping& m = symActive
|
|
||||||
? KEY_MATRIX_HID_SYM[row * 14U + col]
|
|
||||||
: KEY_MATRIX_HID_BASE[row * 14U + col];
|
|
||||||
if (m.keycode != 0U) {
|
|
||||||
const uint8_t modifier = static_cast<uint8_t>(m.modifier | (aaActive ? 0x02U : 0U));
|
|
||||||
const uint32_t lv_key = tab5TranslateKey(m.keycode, modifier, ctrlHeld);
|
|
||||||
if (lv_key != 0U) {
|
|
||||||
if (pressed) {
|
|
||||||
// A real key was pressed — this hold is a chord, not a tap
|
|
||||||
aaTapped = false;
|
|
||||||
if (lv_key == LV_KEY_ESC) {
|
|
||||||
tt::app::stop();
|
|
||||||
} else {
|
|
||||||
xQueueSend(queue, &lv_key, 0);
|
|
||||||
// Arm software repeat tracking by row/col to survive modifier changes
|
|
||||||
const uint32_t now_ms = static_cast<uint32_t>(esp_timer_get_time() / 1000);
|
|
||||||
repeatKey = lv_key;
|
|
||||||
repeatRow = row;
|
|
||||||
repeatCol = col;
|
|
||||||
repeatStartMs = now_ms;
|
|
||||||
repeatLastMs = 0;
|
|
||||||
// Consume sticky Aa after one keypress
|
|
||||||
if (aaSticky) {
|
|
||||||
aaSticky = false;
|
|
||||||
aaHeld = false;
|
|
||||||
updateLeds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (row == repeatRow && col == repeatCol) {
|
|
||||||
// Match release by position, not translated value — survives sticky Aa clear
|
|
||||||
repeatKey = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
count--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear INT status after draining so the line de-asserts
|
|
||||||
writeReg(REG_INT_STAT, 0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// processKeyboard - called from 20ms Timer; IRQ-gated when INT is wired
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
void Tab5Keyboard::processKeyboard() {
|
|
||||||
bool shouldDrain = false;
|
|
||||||
if (irqConfigured) {
|
|
||||||
if (irqPending) {
|
|
||||||
irqPending = false;
|
|
||||||
shouldDrain = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Polling: check INT_STA first — bit 0 = Normal mode event pending
|
|
||||||
uint8_t status = 0;
|
|
||||||
if (readReg(REG_INT_STAT, status) && (status & 0x01U)) {
|
|
||||||
shouldDrain = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shouldDrain) {
|
|
||||||
drainEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Software key-repeat (runs every tick regardless of IRQ)
|
|
||||||
if (repeatKey != 0U) {
|
|
||||||
const uint32_t now_ms = static_cast<uint32_t>(esp_timer_get_time() / 1000);
|
|
||||||
if ((now_ms - repeatStartMs) >= REPEAT_INITIAL_MS) {
|
|
||||||
const uint32_t last = repeatLastMs;
|
|
||||||
if (last == 0 || (now_ms - last) >= REPEAT_RATE_MS) {
|
|
||||||
repeatLastMs = now_ms;
|
|
||||||
xQueueSend(queue, &repeatKey, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkAttachState();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// applyAutoRotation - on attach, switches to landscape if not already (saving
|
|
||||||
// the prior rotation); on detach, restores the saved rotation if we were the
|
|
||||||
// ones who changed it. Only affects the live LVGL rotation, never persisted
|
|
||||||
// display settings.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
bool Tab5Keyboard::applyAutoRotation(bool keyboardAttached) {
|
|
||||||
auto* display = lv_indev_get_display(kbHandle);
|
|
||||||
if (display == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tt::lvgl::lock(pdMS_TO_TICKS(100))) {
|
|
||||||
return false; // retry next poll
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyboardAttached) {
|
|
||||||
if (lv_display_get_rotation(display) != LV_DISPLAY_ROTATION_90) {
|
|
||||||
savedRotation = lv_display_get_rotation(display);
|
|
||||||
rotationOverrideActive = true;
|
|
||||||
lv_display_set_rotation(display, LV_DISPLAY_ROTATION_90);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Only restore if rotation is still what we set it to - if the user manually
|
|
||||||
// changed it since attaching, respect their choice instead.
|
|
||||||
if (rotationOverrideActive && lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_90) {
|
|
||||||
lv_display_set_rotation(display, savedRotation);
|
|
||||||
}
|
|
||||||
rotationOverrideActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt::lvgl::unlock();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// checkAttachState - throttled (~1s) hot-plug detection. Reapplies device
|
|
||||||
// register configuration and auto-rotation on detach/attach transitions.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
void Tab5Keyboard::checkAttachState() {
|
|
||||||
static constexpr uint32_t ATTACH_CHECK_TICKS = 50; // ~1s at 20ms/tick
|
|
||||||
|
|
||||||
if (++attachCheckTickCounter < ATTACH_CHECK_TICKS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
attachCheckTickCounter = 0;
|
|
||||||
|
|
||||||
const bool attached = isAttached();
|
|
||||||
if (attached == wasAttached) {
|
|
||||||
pendingAttachConfirmCount = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Require the new state to be confirmed on a second consecutive check before acting -
|
|
||||||
// a single probe on a floating/half-connected bus (e.g. mid-unplug) can false-positive.
|
|
||||||
if (attached != pendingAttachState || pendingAttachConfirmCount == 0) {
|
|
||||||
pendingAttachState = attached;
|
|
||||||
pendingAttachConfirmCount = 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pendingAttachConfirmCount = 0;
|
|
||||||
|
|
||||||
if (attached) {
|
|
||||||
reinitDevice();
|
|
||||||
}
|
|
||||||
if (!applyAutoRotation(attached)) {
|
|
||||||
return; // keep prior state so transition is retried
|
|
||||||
}
|
|
||||||
wasAttached = attached;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// lateStart - see header comment. Brings up LVGL input handling for a keyboard
|
|
||||||
// that wasn't attached at boot (startLvgl() wasn't called from attachDevices()).
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
bool Tab5Keyboard::lateStart() {
|
|
||||||
if (kbHandle != nullptr) {
|
|
||||||
return true; // already started
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* display = lv_display_get_default();
|
|
||||||
if (display == nullptr) {
|
|
||||||
return false; // LVGL not ready yet
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tt::lvgl::lock(pdMS_TO_TICKS(100))) {
|
|
||||||
return false; // try again on the next attach-state check
|
|
||||||
}
|
|
||||||
|
|
||||||
bool started = startLvgl(display);
|
|
||||||
if (started) {
|
|
||||||
tt::lvgl::hardware_keyboard_set_indev(kbHandle);
|
|
||||||
|
|
||||||
// redraw() assigns every indev that exists at the time to the active screen's
|
|
||||||
// input group. This indev didn't exist yet at the last redraw(), so it has no
|
|
||||||
// group and won't deliver key events until the next app switch. Join the
|
|
||||||
// current default group now so input works immediately on the visible screen.
|
|
||||||
lv_indev_set_group(kbHandle, lv_group_get_default());
|
|
||||||
}
|
|
||||||
|
|
||||||
tt::lvgl::unlock();
|
|
||||||
|
|
||||||
return started;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// LVGL read callback - called from the LVGL task
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
void Tab5Keyboard::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
|
|
||||||
auto* self = static_cast<Tab5Keyboard*>(lv_indev_get_user_data(indev));
|
|
||||||
uint32_t lv_key = 0;
|
|
||||||
if (xQueueReceive(self->queue, &lv_key, 0) == pdTRUE) {
|
|
||||||
data->key = lv_key;
|
|
||||||
data->state = LV_INDEV_STATE_PRESSED;
|
|
||||||
data->continue_reading = (uxQueueMessagesWaiting(self->queue) > 0);
|
|
||||||
} else {
|
|
||||||
data->state = LV_INDEV_STATE_RELEASED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// KeyboardDevice interface
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
Tab5Keyboard::~Tab5Keyboard() {
|
|
||||||
if (inputTimer) {
|
|
||||||
stopLvgl(); // tears down LVGL indev, IRQ, I2C bus
|
|
||||||
}
|
|
||||||
if (queue) {
|
|
||||||
vQueueDelete(queue);
|
|
||||||
queue = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// reinitDevice - (re)applies the device register configuration. Used at
|
|
||||||
// startLvgl() and again on hot-plug reattach, since the device's RGB mode and
|
|
||||||
// interrupt configuration are volatile and reset to power-on defaults when
|
|
||||||
// the keyboard is unplugged and reconnected.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
void Tab5Keyboard::reinitDevice() {
|
|
||||||
writeReg(REG_KEYBOARD_MODE, 0x00); // Normal mode
|
|
||||||
writeReg(REG_EVENT_NUM, 0x00); // flush event queue
|
|
||||||
writeReg(REG_INT_STAT, 0x00); // clear pending INT
|
|
||||||
writeReg(REG_RGB_MODE, 0x01); // Custom RGB mode (manual LED control)
|
|
||||||
writeReg(REG_BRIGHTNESS, 50); // 50% brightness
|
|
||||||
updateLeds(); // restore current LED state
|
|
||||||
|
|
||||||
if (irqConfigured) {
|
|
||||||
writeReg(REG_INT_CFG, 0x01); // re-enable Normal-mode interrupt (bit 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Tab5Keyboard::startLvgl(lv_display_t* display) {
|
|
||||||
if (!queue) {
|
|
||||||
LOG_E("Tab5Keyboard", "Input queue allocation failed — cannot start");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
symActive = false;
|
|
||||||
aaSticky = false;
|
|
||||||
aaHeld = false;
|
|
||||||
aaTapped = false;
|
|
||||||
ctrlHeld = false;
|
|
||||||
repeatKey = 0;
|
|
||||||
repeatRow = 0xFF;
|
|
||||||
repeatCol = 0xFF;
|
|
||||||
repeatLastMs = 0;
|
|
||||||
|
|
||||||
configureIrqPin(); // best-effort; falls back to polling if it fails. Must run before
|
|
||||||
// reinitDevice() so REG_INT_CFG is written if IRQ setup succeeded.
|
|
||||||
|
|
||||||
// Best-effort: if the keyboard isn't attached yet (e.g. started speculatively at
|
|
||||||
// boot so it can be detected later via hot-plug), these I2C writes fail silently
|
|
||||||
// and reinitDevice() runs again once attach is detected.
|
|
||||||
reinitDevice();
|
|
||||||
|
|
||||||
kbHandle = lv_indev_create();
|
|
||||||
lv_indev_set_type(kbHandle, LV_INDEV_TYPE_KEYPAD);
|
|
||||||
lv_indev_set_read_cb(kbHandle, readCallback);
|
|
||||||
lv_indev_set_display(kbHandle, display);
|
|
||||||
lv_indev_set_user_data(kbHandle, this);
|
|
||||||
|
|
||||||
wasAttached = isAttached();
|
|
||||||
rotationOverrideActive = false;
|
|
||||||
|
|
||||||
assert(inputTimer == nullptr);
|
|
||||||
inputTimer = std::make_unique<tt::Timer>(tt::Timer::Type::Periodic, pdMS_TO_TICKS(20), [this] {
|
|
||||||
processKeyboard();
|
|
||||||
});
|
|
||||||
inputTimer->start();
|
|
||||||
|
|
||||||
if (wasAttached) {
|
|
||||||
applyAutoRotation(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Tab5Keyboard::stopLvgl() {
|
|
||||||
if (!inputTimer) {
|
|
||||||
return false; // Not started
|
|
||||||
}
|
|
||||||
inputTimer->stop();
|
|
||||||
inputTimer = nullptr;
|
|
||||||
|
|
||||||
removeIrqPin();
|
|
||||||
if (queue) {
|
|
||||||
xQueueReset(queue); // discard unread keycodes so a restart begins with an empty buffer
|
|
||||||
}
|
|
||||||
writeReg(REG_INT_CFG, 0x00); // disable all interrupts
|
|
||||||
symActive = false;
|
|
||||||
aaSticky = false;
|
|
||||||
aaHeld = false;
|
|
||||||
updateLeds(); // turn LEDs off
|
|
||||||
|
|
||||||
lv_indev_delete(kbHandle);
|
|
||||||
kbHandle = nullptr;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Tab5Keyboard::isAttached() const {
|
|
||||||
return i2c_controller_has_device_at_address(i2cController, I2C_ADDRESS, pdMS_TO_TICKS(100)) == ERROR_NONE;
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <Tactility/hal/keyboard/KeyboardDevice.h>
|
|
||||||
#include <Tactility/Timer.h>
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <freertos/queue.h>
|
|
||||||
|
|
||||||
class Tab5Keyboard final : public tt::hal::keyboard::KeyboardDevice {
|
|
||||||
static constexpr uint8_t I2C_ADDRESS = 0x6D;
|
|
||||||
static constexpr gpio_num_t INT_PIN = GPIO_NUM_50;
|
|
||||||
|
|
||||||
// Software key-repeat timing
|
|
||||||
static constexpr uint32_t REPEAT_INITIAL_MS = 400;
|
|
||||||
static constexpr uint32_t REPEAT_RATE_MS = 80;
|
|
||||||
|
|
||||||
::Device* i2cController = nullptr;
|
|
||||||
|
|
||||||
lv_indev_t* kbHandle = nullptr;
|
|
||||||
QueueHandle_t queue = nullptr;
|
|
||||||
std::unique_ptr<tt::Timer> inputTimer;
|
|
||||||
|
|
||||||
bool symActive = false; // held while Sym is physically down
|
|
||||||
bool aaSticky = false; // latched on single Aa tap, cleared after next non-modifier key
|
|
||||||
bool aaHeld = false; // true while Aa is physically held
|
|
||||||
bool aaTapped = false; // no non-modifier key was pressed while Aa was held
|
|
||||||
bool ctrlHeld = false; // true while Ctrl is physically held
|
|
||||||
|
|
||||||
// IRQ-driven event gating
|
|
||||||
volatile bool irqPending = false;
|
|
||||||
bool irqConfigured = false;
|
|
||||||
|
|
||||||
// Hot-plug attach-state polling (piggybacks on the 20ms inputTimer)
|
|
||||||
bool wasAttached = false;
|
|
||||||
uint32_t attachCheckTickCounter = 0;
|
|
||||||
// I2C probes can false-positive on a floating/half-connected bus (e.g. mid-unplug), so a
|
|
||||||
// state change is only acted on once it's seen on two consecutive ~1s checks in a row.
|
|
||||||
bool pendingAttachState = false;
|
|
||||||
uint8_t pendingAttachConfirmCount = 0;
|
|
||||||
lv_display_rotation_t savedRotation = LV_DISPLAY_ROTATION_0;
|
|
||||||
bool rotationOverrideActive = false;
|
|
||||||
|
|
||||||
// Software key-repeat state (tracked by position to survive modifier changes)
|
|
||||||
uint32_t repeatKey = 0;
|
|
||||||
uint8_t repeatRow = 0xFF;
|
|
||||||
uint8_t repeatCol = 0xFF;
|
|
||||||
uint32_t repeatStartMs = 0;
|
|
||||||
uint32_t repeatLastMs = 0;
|
|
||||||
|
|
||||||
bool readReg(uint8_t reg, uint8_t& value);
|
|
||||||
bool writeReg(uint8_t reg, uint8_t value);
|
|
||||||
void updateLeds();
|
|
||||||
|
|
||||||
bool configureIrqPin();
|
|
||||||
void removeIrqPin();
|
|
||||||
static void IRAM_ATTR irqHandler(void* arg);
|
|
||||||
|
|
||||||
void reinitDevice();
|
|
||||||
bool applyAutoRotation(bool keyboardAttached);
|
|
||||||
void checkAttachState();
|
|
||||||
|
|
||||||
void drainEvents();
|
|
||||||
void processKeyboard();
|
|
||||||
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Tab5Keyboard(::Device* i2cController) : i2cController(i2cController) {
|
|
||||||
queue = xQueueCreate(20, sizeof(uint32_t));
|
|
||||||
// queue == nullptr on OOM; startLvgl() checks and refuses to start
|
|
||||||
}
|
|
||||||
~Tab5Keyboard() override; // defined in .cpp: stops active session, then vQueueDelete(queue)
|
|
||||||
|
|
||||||
std::string getName() const override { return "Tab5Keyboard"; }
|
|
||||||
std::string getDescription() const override { return "M5Stack Tab5 Keyboard addon"; }
|
|
||||||
|
|
||||||
bool startLvgl(lv_display_t* display) override;
|
|
||||||
bool stopLvgl() override;
|
|
||||||
bool isAttached() const override;
|
|
||||||
lv_indev_t* getLvglIndev() override { return kbHandle; }
|
|
||||||
|
|
||||||
// Starts LVGL input handling and registers the hardware keyboard indev for a device
|
|
||||||
// that wasn't attached at boot (so startLvgl() was never called from Lvgl.cpp's
|
|
||||||
// attachDevices()). Called from the device module's attach-detection timer once the
|
|
||||||
// keyboard is first detected post-boot. No-op if LVGL input is already started.
|
|
||||||
bool lateStart();
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#include "detect.h"
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/drivers/i2c_controller.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <esp_lcd_touch_gt911.h>
|
||||||
|
#include <esp_lcd_touch_st7123.h>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
|
||||||
|
constexpr auto* TAG = "Tab5";
|
||||||
|
|
||||||
|
static Tab5Variant detected_variant = Tab5Variant::Unknown;
|
||||||
|
|
||||||
|
Tab5Variant tab5_get_variant() {
|
||||||
|
return detected_variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tab5_set_variant(Tab5Variant variant) {
|
||||||
|
detected_variant = variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab5Variant tab5_probe_variant(Device* i2c0) {
|
||||||
|
// Allow time for the touch IC to fully boot after the reset pulse above: 100ms is enough for
|
||||||
|
// I2C ACK (probe) but cold power-on needs ~300ms before register reads succeed reliably.
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(300));
|
||||||
|
|
||||||
|
constexpr auto PROBE_TIMEOUT = pdMS_TO_TICKS(50);
|
||||||
|
|
||||||
|
for (int attempt = 0; attempt < 3; ++attempt) {
|
||||||
|
if (i2c_controller_has_device_at_address(i2c0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, PROBE_TIMEOUT) == ERROR_NONE ||
|
||||||
|
i2c_controller_has_device_at_address(i2c0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP, PROBE_TIMEOUT) == ERROR_NONE) {
|
||||||
|
LOG_I(TAG, "display_detect: detected GT911 touch — using variant V1 ");
|
||||||
|
return Tab5Variant::V1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i2c_controller_has_device_at_address(i2c0, ESP_LCD_TOUCH_IO_I2C_ST7123_ADDRESS, PROBE_TIMEOUT) == ERROR_NONE) {
|
||||||
|
LOG_I(TAG, "display_detect: detected ST7123 touch — using variant V2");
|
||||||
|
return Tab5Variant::V2;
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_W(TAG, "display_detect: no known touch controller detected, defaulting to V2");
|
||||||
|
return Tab5Variant::V2;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct Device;
|
||||||
|
|
||||||
|
enum class Tab5Variant {
|
||||||
|
Unknown,
|
||||||
|
V1, // Older variant: ILI9881C display + GT911 touch (see devices_v1.cpp)
|
||||||
|
V2, // Newer variant (default): ST7123 display + in-cell touch (see devices_v2.cpp)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Populated once the device_listener callback in display_detect.cpp has detected which
|
||||||
|
// panel/touch variant is present (gated on i2c0 + io_expander0 both reaching
|
||||||
|
// DEVICE_EVENT_STARTED). Safe to call from Configuration.cpp's createDevices()/createTouch():
|
||||||
|
// kernel_init() (which runs the listener to completion) always finishes before hal::init()
|
||||||
|
// invokes createDevices().
|
||||||
|
[[nodiscard]] Tab5Variant tab5_get_variant();
|
||||||
|
|
||||||
|
// Registers/unregisters the device_listener (display_detect.cpp) that detects the display/touch
|
||||||
|
// variant, keyboard and power control devices and dynamically constructs them. Called from
|
||||||
|
// module.cpp's start()/stop().
|
||||||
|
void tab5_detect_start();
|
||||||
|
void tab5_detect_stop();
|
||||||
|
|
||||||
|
// --- detect.cpp internals, exposed only for display_detect.cpp's on_display_detect_event() ---
|
||||||
|
|
||||||
|
// Records the variant found by probe_variant() below - the only writer of the state
|
||||||
|
// getDetectedTab5Variant() reads.
|
||||||
|
void tab5_set_variant(Tab5Variant variant);
|
||||||
|
|
||||||
|
// Pulses only the LCD + touch reset bits on io_expander0 (see the pinout table in
|
||||||
|
// Configuration.cpp). The other io_expander0 pins (speaker enable, RF switch, 5V bus, camera
|
||||||
|
// reset) are unrelated to display bring-up and stay owned by Configuration.cpp's initExpander0().
|
||||||
|
bool pulse_display_reset_pins(Device* io_expander0);
|
||||||
|
|
||||||
|
// Probes i2c0 for a known touch controller address to determine which display/touch variant is
|
||||||
|
// present. Must be called after pulse_display_reset_pins().
|
||||||
|
Tab5Variant tab5_probe_variant(Device* i2c0);
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#include "devices_common.h"
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
constexpr auto* TAG = "Tab5";
|
||||||
|
|
||||||
|
// Mirrors device_construct_add_start(), but with a device_set_parent() call inserted between
|
||||||
|
// construct and add - device_set_parent() asserts on device->internal (only valid after
|
||||||
|
// construct), and device_construct_add_start() doesn't expose a hook to call it before add().
|
||||||
|
bool construct_add_start(Device* device, Device* parent, const char* compatible) {
|
||||||
|
error_t error = device_construct(device);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "display_detect: failed to construct %s: %s", device->name, error_to_string(error));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
device_set_parent(device, parent);
|
||||||
|
|
||||||
|
Driver* driver = driver_find_compatible(compatible);
|
||||||
|
if (driver == nullptr) {
|
||||||
|
LOG_E(TAG, "display_detect: no driver registered for %s", compatible);
|
||||||
|
device_destruct(device);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
device_set_driver(device, driver);
|
||||||
|
|
||||||
|
error = device_add(device);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "display_detect: failed to add %s: %s", device->name, error_to_string(error));
|
||||||
|
device_destruct(device);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = device_start(device);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "display_detect: failed to start %s: %s", device->name, error_to_string(error));
|
||||||
|
device_remove(device);
|
||||||
|
device_destruct(device);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Constructs, parents, binds and starts a dynamically-built Device. Mirrors
|
||||||
|
// device_construct_add_start(), but with a device_set_parent() call inserted between construct and add
|
||||||
|
bool construct_add_start(Device* device, Device* parent, const char* compatible);
|
||||||
|
|
||||||
|
// Unpacks a vendor-typed init-cmd array (ili9881c_lcd_init_cmd_t / st7123_lcd_init_cmd_t - same
|
||||||
|
// field layout) into the flattened [cmd, data_len, delay_ms, data_len bytes...] byte encoding that
|
||||||
|
// ili9881c-module/st7123-module's init-sequence config field expects (the same encoding a
|
||||||
|
// devicetree "array" property would produce - see e.g. ili9881c-module's binding yaml). Needed
|
||||||
|
// because these Configs are built dynamically here rather than from a .dts node.
|
||||||
|
template<typename InitCmd>
|
||||||
|
void flatten_init_sequence(const InitCmd* cmds, size_t count, std::vector<uint8_t>& out) {
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
out.push_back(static_cast<uint8_t>(cmds[i].cmd));
|
||||||
|
out.push_back(static_cast<uint8_t>(cmds[i].data_bytes));
|
||||||
|
out.push_back(static_cast<uint8_t>(cmds[i].delay_ms));
|
||||||
|
const auto* data = static_cast<const uint8_t*>(cmds[i].data);
|
||||||
|
for (size_t j = 0; j < cmds[i].data_bytes; j++) {
|
||||||
|
out.push_back(data[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
#include "devices_v1.h"
|
||||||
|
|
||||||
|
#include "devices_common.h"
|
||||||
|
#include "ili9881_init_data.h"
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/drivers/gpio.h>
|
||||||
|
#include <tactility/drivers/gpio_controller.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <drivers/gt911.h>
|
||||||
|
#include <drivers/ili9881c.h>
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
constexpr auto* TAG = "Tab5";
|
||||||
|
|
||||||
|
// Display Device + Config + flattened init bytes live for the process's whole lifetime (this
|
||||||
|
// board never tears down or re-detects its panel), so plain static storage is fine - no need to
|
||||||
|
// heap-allocate something the kernel would have to track.
|
||||||
|
static std::vector<uint8_t> display_init_bytes;
|
||||||
|
static Ili9881cConfig ili9881c_config {};
|
||||||
|
static Device display_device {};
|
||||||
|
|
||||||
|
static Gt911Config gt911_config {};
|
||||||
|
static Device gt911_device {};
|
||||||
|
|
||||||
|
// GPIO 23 has a pull-up resistor to 3V3 that blocks GT911 touch, and can't be used as the
|
||||||
|
// controller's own interrupt pin for that reason (see detect.cpp's probe_variant() comment) -
|
||||||
|
// driving it low directly sinks that pull-up instead.
|
||||||
|
// See https://github.com/espressif/esp-bsp/blob/ad668c765cbad177495a122181df0a70ff9f8f61/bsp/m5stack_tab5/src/m5stack_tab5.c#L777
|
||||||
|
static void apply_gt911_int_workaround() {
|
||||||
|
Device* gpio0 = nullptr;
|
||||||
|
if (device_get_by_name("gpio0", &gpio0) != ERROR_NONE) {
|
||||||
|
LOG_W(TAG, "display_detect: gpio0 not found, GT911 INT workaround not applied");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto* int_pin = gpio_descriptor_acquire(gpio0, 23, GPIO_OWNER_GPIO);
|
||||||
|
device_put(gpio0);
|
||||||
|
if (int_pin == nullptr) {
|
||||||
|
LOG_W(TAG, "display_detect: failed to acquire GPIO23 for GT911 INT workaround");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gpio_descriptor_set_flags(int_pin, GPIO_FLAG_DIRECTION_OUTPUT | GPIO_FLAG_PULL_UP);
|
||||||
|
gpio_descriptor_set_level(int_pin, false);
|
||||||
|
gpio_descriptor_release(int_pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GT911's touch controller is a kernel driver (goodix,gt911, in gt911-module).
|
||||||
|
static void create_gt911_touch(Device* i2c0) {
|
||||||
|
gt911_device = Device {
|
||||||
|
.address = 0,
|
||||||
|
.name = "touch0",
|
||||||
|
.config = nullptr,
|
||||||
|
.parent = nullptr,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
gt911_config = Gt911Config {
|
||||||
|
.address = 0, // unused: the driver auto-probes both known GT911 addresses regardless
|
||||||
|
.x_max = 720,
|
||||||
|
.y_max = 1280,
|
||||||
|
.swap_xy = false,
|
||||||
|
.mirror_x = false,
|
||||||
|
.mirror_y = false,
|
||||||
|
// 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_interrupt = GPIO_PIN_SPEC_NONE,
|
||||||
|
.reset_active_high = false,
|
||||||
|
.interrupt_active_high = false,
|
||||||
|
};
|
||||||
|
gt911_device.config = >911_config;
|
||||||
|
|
||||||
|
apply_gt911_int_workaround();
|
||||||
|
|
||||||
|
construct_add_start(>911_device, i2c0, "goodix,gt911");
|
||||||
|
}
|
||||||
|
|
||||||
|
void tab5_create_devices_v1(Device* i2c0) {
|
||||||
|
display_device = Device {
|
||||||
|
.address = 0,
|
||||||
|
.name = "display0",
|
||||||
|
.config = nullptr,
|
||||||
|
.parent = nullptr,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
Device* backlight = nullptr;
|
||||||
|
if (device_get_by_name("display_backlight", &backlight) != ERROR_NONE) {
|
||||||
|
LOG_W(TAG, "display_detect: display_backlight not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
flatten_init_sequence(ili9881_init_data, std::size(ili9881_init_data), display_init_bytes);
|
||||||
|
ili9881c_config = Ili9881cConfig {
|
||||||
|
.horizontal_resolution = 720,
|
||||||
|
.vertical_resolution = 1280,
|
||||||
|
.bits_per_pixel = 16,
|
||||||
|
.bgr_order = false,
|
||||||
|
.invert_color = false,
|
||||||
|
.mirror_x = false,
|
||||||
|
.mirror_y = false,
|
||||||
|
// LCD reset is pulsed via io_expander0 (detect.cpp's pulse_display_reset_pins), not a
|
||||||
|
// direct SoC GPIO.
|
||||||
|
.pin_reset = GPIO_PIN_SPEC_NONE,
|
||||||
|
.reset_active_high = false,
|
||||||
|
.ldo_channel = 3,
|
||||||
|
.ldo_voltage_mv = 2500,
|
||||||
|
.dsi_bus_id = 0,
|
||||||
|
.num_data_lanes = 2,
|
||||||
|
.lane_bit_rate_mbps = 1000,
|
||||||
|
.dpi_clock_freq_mhz = 60,
|
||||||
|
.hsync_pulse_width = 40,
|
||||||
|
.hsync_back_porch = 140,
|
||||||
|
.hsync_front_porch = 40,
|
||||||
|
.vsync_pulse_width = 4,
|
||||||
|
.vsync_back_porch = 20,
|
||||||
|
.vsync_front_porch = 20,
|
||||||
|
.num_fbs = 1,
|
||||||
|
.use_dma2d = false,
|
||||||
|
.disable_lp = false,
|
||||||
|
.allow_tearing = true, // matches old lvgl_port_display_dsi_cfg_t.avoid_tearing = 0
|
||||||
|
.init_sequence = display_init_bytes.data(),
|
||||||
|
.init_sequence_length = (uint32_t)display_init_bytes.size(),
|
||||||
|
.backlight = backlight,
|
||||||
|
};
|
||||||
|
display_device.config = &ili9881c_config;
|
||||||
|
|
||||||
|
if (backlight != nullptr) {
|
||||||
|
device_put(backlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
Device* root = nullptr;
|
||||||
|
if (device_get_by_name("/", &root) != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "display_detect: root device not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool started = construct_add_start(&display_device, root, "ilitek,ili9881c");
|
||||||
|
device_put(root);
|
||||||
|
if (!started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
create_gt911_touch(i2c0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct Device;
|
||||||
|
|
||||||
|
void tab5_create_devices_v1(Device* i2c0);
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#include "devices_v2.h"
|
||||||
|
|
||||||
|
#include "devices_common.h"
|
||||||
|
#include "st7123_init_data.h"
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/drivers/gpio.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <drivers/st7123.h>
|
||||||
|
#include <drivers/st7123_touch.h>
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define TAG "Tab5"
|
||||||
|
|
||||||
|
static std::vector<uint8_t> display_init_bytes;
|
||||||
|
static St7123Config st7123_config {};
|
||||||
|
static Device display_device {};
|
||||||
|
|
||||||
|
static St7123TouchConfig st7123_touch_config {};
|
||||||
|
static Device st7123_touch_device {};
|
||||||
|
|
||||||
|
static void create_st7123_touch(Device* i2c0) {
|
||||||
|
st7123_touch_device = Device {
|
||||||
|
.address = 0,
|
||||||
|
.name = "touch0",
|
||||||
|
.config = nullptr,
|
||||||
|
.parent = nullptr,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
GpioPinSpec pin_interrupt = GPIO_PIN_SPEC_NONE;
|
||||||
|
Device* gpio0 = nullptr;
|
||||||
|
if (device_get_by_name("gpio0", &gpio0) == ERROR_NONE) {
|
||||||
|
pin_interrupt = GpioPinSpec { gpio0, 23, GPIO_FLAG_NONE };
|
||||||
|
device_put(gpio0);
|
||||||
|
} else {
|
||||||
|
LOG_W(TAG, "display_detect: gpio0 not found, touch interrupt pin will not be wired");
|
||||||
|
}
|
||||||
|
|
||||||
|
st7123_touch_config = St7123TouchConfig {
|
||||||
|
.address = 0x55, // fixed - see ESP_LCD_TOUCH_IO_I2C_ST7123_ADDRESS
|
||||||
|
.x_max = 720,
|
||||||
|
.y_max = 1280,
|
||||||
|
.swap_xy = false,
|
||||||
|
.mirror_x = false,
|
||||||
|
.mirror_y = false,
|
||||||
|
// 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_interrupt = pin_interrupt,
|
||||||
|
.reset_active_high = false,
|
||||||
|
.interrupt_active_high = false,
|
||||||
|
};
|
||||||
|
st7123_touch_device.config = &st7123_touch_config;
|
||||||
|
|
||||||
|
construct_add_start(&st7123_touch_device, i2c0, "sitronix,st7123-touch");
|
||||||
|
}
|
||||||
|
|
||||||
|
void tab5_create_devices_v2(Device* i2c0) {
|
||||||
|
display_device = Device {
|
||||||
|
.address = 0,
|
||||||
|
.name = "display0",
|
||||||
|
.config = nullptr,
|
||||||
|
.parent = nullptr,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
Device* backlight = nullptr;
|
||||||
|
if (device_get_by_name("display_backlight", &backlight) != ERROR_NONE) {
|
||||||
|
LOG_W(TAG, "display_detect: display_backlight not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
flatten_init_sequence(st7123_init_data, std::size(st7123_init_data), display_init_bytes);
|
||||||
|
st7123_config = St7123Config {
|
||||||
|
.horizontal_resolution = 720,
|
||||||
|
.vertical_resolution = 1280,
|
||||||
|
.bits_per_pixel = 16,
|
||||||
|
.bgr_order = false,
|
||||||
|
.invert_color = false,
|
||||||
|
.mirror_x = false,
|
||||||
|
.mirror_y = false,
|
||||||
|
.pin_reset = GPIO_PIN_SPEC_NONE,
|
||||||
|
.reset_active_high = false,
|
||||||
|
.ldo_channel = 3,
|
||||||
|
.ldo_voltage_mv = 2500,
|
||||||
|
.dsi_bus_id = 0,
|
||||||
|
.num_data_lanes = 2,
|
||||||
|
.lane_bit_rate_mbps = 965, // ST7123 lane bitrate per M5Stack BSP
|
||||||
|
.dpi_clock_freq_mhz = 70,
|
||||||
|
.hsync_pulse_width = 2,
|
||||||
|
.hsync_back_porch = 40,
|
||||||
|
.hsync_front_porch = 40,
|
||||||
|
.vsync_pulse_width = 2,
|
||||||
|
.vsync_back_porch = 8,
|
||||||
|
.vsync_front_porch = 220,
|
||||||
|
.num_fbs = 1,
|
||||||
|
.use_dma2d = false,
|
||||||
|
.disable_lp = false,
|
||||||
|
.allow_tearing = true,
|
||||||
|
.init_sequence = display_init_bytes.data(),
|
||||||
|
.init_sequence_length = (uint32_t)display_init_bytes.size(),
|
||||||
|
.backlight = backlight,
|
||||||
|
};
|
||||||
|
display_device.config = &st7123_config;
|
||||||
|
|
||||||
|
if (backlight != nullptr) {
|
||||||
|
device_put(backlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
Device* root = nullptr;
|
||||||
|
if (device_get_by_name("/", &root) != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "display_detect: root device not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool started = construct_add_start(&display_device, root, "sitronix,st7123");
|
||||||
|
device_put(root);
|
||||||
|
if (!started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
create_st7123_touch(i2c0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct Device;
|
||||||
|
|
||||||
|
void tab5_create_devices_v2(Device* i2c0);
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
#include "detect.h"
|
||||||
|
|
||||||
|
#include "../../../../TactilityKernel/include/tactility/log.h"
|
||||||
|
#include "devices_common.h"
|
||||||
|
#include "devices_v1.h"
|
||||||
|
#include "devices_v2.h"
|
||||||
|
#include "tab5_keyboard.h"
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/device_listener.h>
|
||||||
|
#include <tactility/drivers/gpio_controller.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
constexpr auto* TAG = "Tab5";
|
||||||
|
|
||||||
|
static Device tab5_power_control_device {};
|
||||||
|
|
||||||
|
// Charge-control/quick-charge/power-off for the IP2326 charger IC, driven through io_expander1 (see tab5_power_control.h).
|
||||||
|
static void tab5_create_power_control(Device* io_expander1) {
|
||||||
|
tab5_power_control_device = Device {
|
||||||
|
.address = 0,
|
||||||
|
.name = "power_control0",
|
||||||
|
.config = nullptr,
|
||||||
|
.parent = nullptr,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parented to io_expander1 itself: the driver's start()/set_*() use device_get_parent() as
|
||||||
|
// its GPIO controller.
|
||||||
|
construct_add_start(&tab5_power_control_device, io_expander1, "m5stack,tab5-power-control");
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto GPIO_EXP0_PIN_LCD_RESET = 4;
|
||||||
|
constexpr auto GPIO_EXP0_PIN_TOUCH_RESET = 5;
|
||||||
|
|
||||||
|
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* touch_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_TOUCH_RESET, GPIO_OWNER_GPIO);
|
||||||
|
if (lcd_reset_pin == nullptr || touch_reset_pin == nullptr) {
|
||||||
|
LOG_E(TAG, "display_detect: failed to acquire LCD/touch reset pins on io_expander0");
|
||||||
|
if (lcd_reset_pin != nullptr) {
|
||||||
|
gpio_descriptor_release(lcd_reset_pin);
|
||||||
|
}
|
||||||
|
if (touch_reset_pin != nullptr) {
|
||||||
|
gpio_descriptor_release(touch_reset_pin);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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(touch_reset_pin, true);
|
||||||
|
|
||||||
|
gpio_descriptor_release(lcd_reset_pin);
|
||||||
|
gpio_descriptor_release(touch_reset_pin);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fires for every device's start/stop in the system.
|
||||||
|
static void on_display_detect_event(Device* device, DeviceEvent event, void* context) {
|
||||||
|
(void)context;
|
||||||
|
|
||||||
|
static Device* i2c0 = nullptr;
|
||||||
|
static Device* io_expander0 = nullptr;
|
||||||
|
static bool did_create_display = false;
|
||||||
|
|
||||||
|
static Device* i2c2 = nullptr;
|
||||||
|
static bool did_create_keyboard = false;
|
||||||
|
|
||||||
|
static Device* io_expander1 = nullptr;
|
||||||
|
static bool did_create_power_control = false;
|
||||||
|
|
||||||
|
if (event == DEVICE_EVENT_STARTED) {
|
||||||
|
if (strcmp(device->name, "i2c0") == 0) {
|
||||||
|
i2c0 = device;
|
||||||
|
}
|
||||||
|
if (strcmp(device->name, "io_expander0") == 0) {
|
||||||
|
io_expander0 = device;
|
||||||
|
}
|
||||||
|
if (strcmp(device->name, "i2c2") == 0) {
|
||||||
|
i2c2 = device;
|
||||||
|
}
|
||||||
|
if (strcmp(device->name, "io_expander1") == 0) {
|
||||||
|
io_expander1 = device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (io_expander0 != nullptr && tab5_get_variant() == Tab5Variant::Unknown) {
|
||||||
|
Tab5Variant variant = tab5_probe_variant(i2c0);
|
||||||
|
tab5_set_variant(variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need i2c0 and io_expander0 to create the display and touch devices
|
||||||
|
if (!did_create_display && i2c0 != nullptr && io_expander0 != nullptr) {
|
||||||
|
did_create_display = true;
|
||||||
|
if (pulse_display_reset_pins(io_expander0)) {
|
||||||
|
switch (tab5_get_variant()) {
|
||||||
|
case Tab5Variant::Unknown:
|
||||||
|
LOG_E(TAG, "Variant not detected yet");
|
||||||
|
break;
|
||||||
|
case Tab5Variant::V1:
|
||||||
|
tab5_create_devices_v1(i2c0);
|
||||||
|
break;
|
||||||
|
case Tab5Variant::V2:
|
||||||
|
tab5_create_devices_v2(i2c0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_E(TAG, "display_detect: skipping display creation, failed to pulse reset pins");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The keyboard lives on the unrelated i2c2 bus, so this is gated independently.
|
||||||
|
if (!did_create_keyboard && i2c2 != nullptr) {
|
||||||
|
did_create_keyboard = true;
|
||||||
|
tab5_create_keyboard(i2c2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The charger control lines live on io_expander1, unrelated to the display/keyboard buses.
|
||||||
|
if (!did_create_power_control && io_expander1 != nullptr) {
|
||||||
|
did_create_power_control = true;
|
||||||
|
tab5_create_power_control(io_expander1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tab5_detect_start() {
|
||||||
|
device_listener_add(on_display_detect_event, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tab5_detect_stop() {
|
||||||
|
device_listener_remove(on_display_detect_event);
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#include "tab5_headphone_detect.h"
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/drivers/gpio_controller.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/timers.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#define TAG "Tab5"
|
||||||
|
|
||||||
|
// PI4IOE5V6408-0 (0x43) bit 1
|
||||||
|
constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1;
|
||||||
|
// PI4IOE5V6408-0 (0x43) bit 7
|
||||||
|
constexpr auto GPIO_EXP0_PIN_HEADPHONE_DETECT = 7;
|
||||||
|
|
||||||
|
constexpr auto HP_DETECT_POLL_MS = 1000;
|
||||||
|
|
||||||
|
static TimerHandle_t hp_detect_timer = nullptr;
|
||||||
|
static std::atomic<Device*> io_expander0_cached { nullptr };
|
||||||
|
// Flags are written by the timer daemon task
|
||||||
|
static std::atomic hp_detect_last { false };
|
||||||
|
static std::atomic hp_detect_initialized { false };
|
||||||
|
|
||||||
|
static void headphone_detect_callback(TimerHandle_t /*timer*/) {
|
||||||
|
Device* cached = io_expander0_cached.load(std::memory_order_acquire);
|
||||||
|
if (!cached) {
|
||||||
|
cached = device_find_by_name("io_expander0");
|
||||||
|
io_expander0_cached.store(cached, std::memory_order_release);
|
||||||
|
}
|
||||||
|
auto* io_expander0 = cached;
|
||||||
|
if (!io_expander0) {
|
||||||
|
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);
|
||||||
|
if (!hp_pin) {
|
||||||
|
LOG_W(TAG, "hp_detect: HP_DET pin busy");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hp = false;
|
||||||
|
error_t err = gpio_descriptor_get_level(hp_pin, &hp);
|
||||||
|
gpio_descriptor_release(hp_pin);
|
||||||
|
|
||||||
|
if (err != ERROR_NONE) {
|
||||||
|
LOG_W(TAG, "hp_detect: HP_DET read error: %s", error_to_string(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_D(TAG, "hp_detect: HP_DET=%d", (int)hp);
|
||||||
|
|
||||||
|
if (!hp_detect_initialized || hp != hp_detect_last) {
|
||||||
|
auto* spk_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO);
|
||||||
|
if (!spk_pin) {
|
||||||
|
LOG_W(TAG, "hp_detect: SPK_EN pin busy, will retry");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
error_t spk_err = gpio_descriptor_set_level(spk_pin, !hp);
|
||||||
|
gpio_descriptor_release(spk_pin);
|
||||||
|
if (spk_err != ERROR_NONE) {
|
||||||
|
LOG_W(TAG, "hp_detect: SPK_EN set error: %s, will retry", error_to_string(spk_err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hp_detect_last = hp;
|
||||||
|
hp_detect_initialized = true;
|
||||||
|
LOG_I(TAG, "Headphones %s, speaker %s", hp ? "detected" : "removed", hp ? "disabled" : "enabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tab5_headphone_detect_start() {
|
||||||
|
if (hp_detect_timer != nullptr) {
|
||||||
|
LOG_W(TAG, "hp_detect timer already running");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hp_detect_initialized = false;
|
||||||
|
hp_detect_last = false;
|
||||||
|
|
||||||
|
hp_detect_timer = xTimerCreate("hp_detect", pdMS_TO_TICKS(HP_DETECT_POLL_MS), pdTRUE, nullptr, headphone_detect_callback);
|
||||||
|
if (!hp_detect_timer) {
|
||||||
|
LOG_E(TAG, "Failed to create hp_detect timer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (xTimerStart(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
||||||
|
LOG_E(TAG, "Failed to start hp_detect timer");
|
||||||
|
xTimerDelete(hp_detect_timer, pdMS_TO_TICKS(100));
|
||||||
|
hp_detect_timer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tab5_headphone_detect_stop() {
|
||||||
|
if (hp_detect_timer == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xTimerStop(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
||||||
|
LOG_W(TAG, "Failed to stop hp_detect timer");
|
||||||
|
}
|
||||||
|
if (xTimerDelete(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
||||||
|
LOG_E(TAG, "Failed to delete hp_detect timer");
|
||||||
|
}
|
||||||
|
// Always clear the handle — stale non-null handle is worse than a resource leak, as it would
|
||||||
|
// cause tab5_headphone_detect_start() to silently skip re-creating the timer.
|
||||||
|
hp_detect_timer = nullptr;
|
||||||
|
io_expander0_cached.store(nullptr, std::memory_order_release);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Starts/stops the periodic headphone-jack detection poll (HP_DET pin on io_expander0), which
|
||||||
|
// toggles the speaker amplifier enable pin accordingly. Called from module.cpp's start()/stop().
|
||||||
|
void tab5_headphone_detect_start();
|
||||||
|
void tab5_headphone_detect_stop();
|
||||||
@@ -0,0 +1,695 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#include "tab5_keyboard.h"
|
||||||
|
|
||||||
|
#include "devices_common.h"
|
||||||
|
|
||||||
|
#include <tactility/check.h>
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
#include <tactility/drivers/i2c_controller.h>
|
||||||
|
#include <tactility/drivers/keyboard.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
#include <tactility/lvgl_module.h>
|
||||||
|
#include <tactility/module.h>
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <esp_timer.h>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/queue.h>
|
||||||
|
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
constexpr auto* TAG = "Tab5Keyboard";
|
||||||
|
#define GET_CONFIG(device) (static_cast<const Tab5KeyboardConfig*>((device)->config))
|
||||||
|
|
||||||
|
static constexpr uint8_t I2C_ADDRESS = 0x6D;
|
||||||
|
|
||||||
|
// Software key-repeat timing
|
||||||
|
static constexpr uint32_t REPEAT_INITIAL_MS = 400;
|
||||||
|
static constexpr uint32_t REPEAT_RATE_MS = 80;
|
||||||
|
|
||||||
|
// I2C event-poll interval - mirrors the old deprecated-HAL's 20ms Timer period. Drives both
|
||||||
|
// REG_INT_STAT polling (when no IRQ pin) and software key-repeat ticking.
|
||||||
|
static constexpr uint32_t POLL_INTERVAL_MS = 20;
|
||||||
|
|
||||||
|
// Hot-plug attach-state check interval. I2C probes can false-positive on a floating/half-connected
|
||||||
|
// bus (e.g. mid-unplug), so a state change is only acted on once it's seen on two consecutive
|
||||||
|
// checks in a row (see check_attach_state()).
|
||||||
|
static constexpr uint32_t ATTACH_CHECK_INTERVAL_MS = 1000;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Register addresses
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static constexpr uint8_t REG_INT_CFG = 0x00;
|
||||||
|
static constexpr uint8_t REG_INT_STAT = 0x01;
|
||||||
|
static constexpr uint8_t REG_EVENT_NUM = 0x02;
|
||||||
|
static constexpr uint8_t REG_BRIGHTNESS = 0x03;
|
||||||
|
static constexpr uint8_t REG_KEYBOARD_MODE = 0x10;
|
||||||
|
static constexpr uint8_t REG_RGB_MODE = 0x11;
|
||||||
|
static constexpr uint8_t REG_KEY_EVENT = 0x20;
|
||||||
|
static constexpr uint8_t REG_RGB_BASE = 0x60;
|
||||||
|
static constexpr uint8_t KEY_EVENT_EMPTY = 0xFF;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Modifier key positions in the 5x14 matrix
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static constexpr uint8_t MOD_ROW_SYM = 3, MOD_COL_SYM = 0;
|
||||||
|
static constexpr uint8_t MOD_ROW_AA = 3, MOD_COL_AA = 1;
|
||||||
|
static constexpr uint8_t MOD_ROW_CTRL = 4, MOD_COL_CTRL = 0;
|
||||||
|
static constexpr uint8_t MOD_ROW_ALT = 4, MOD_COL_ALT = 1;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// HID lookup tables
|
||||||
|
// Row-major: index = row * 14 + col, 5 rows x 14 cols = 70 entries.
|
||||||
|
// modifier 0x02 = Left Shift (pre-baked by firmware for shifted characters).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct HidMapping {
|
||||||
|
uint8_t keycode;
|
||||||
|
uint8_t modifier;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr HidMapping KEY_MATRIX_HID_BASE[70] = {
|
||||||
|
// Row 0: Esc 1 2 3 4 5 6 7 8 9 0 - + Del
|
||||||
|
{0x29, 0x00}, {0x1E, 0x00}, {0x1F, 0x00}, {0x20, 0x00}, {0x21, 0x00}, {0x22, 0x00},
|
||||||
|
{0x23, 0x00}, {0x24, 0x00}, {0x25, 0x00}, {0x26, 0x00}, {0x27, 0x00}, {0x2D, 0x00},
|
||||||
|
{0x2E, 0x02}, {0x4C, 0x00},
|
||||||
|
// Row 1: ` ! @ # $ % ^ & * ( ) [ ] backslash
|
||||||
|
{0x35, 0x00}, {0x1E, 0x02}, {0x1F, 0x02}, {0x20, 0x02}, {0x21, 0x02}, {0x22, 0x02},
|
||||||
|
{0x23, 0x02}, {0x24, 0x02}, {0x25, 0x02}, {0x26, 0x02}, {0x27, 0x02}, {0x2F, 0x00},
|
||||||
|
{0x30, 0x00}, {0x31, 0x00},
|
||||||
|
// Row 2: Tab q w e r t y u i o p ; ' Backspace
|
||||||
|
{0x2B, 0x00}, {0x14, 0x00}, {0x1A, 0x00}, {0x08, 0x00}, {0x15, 0x00}, {0x17, 0x00},
|
||||||
|
{0x1C, 0x00}, {0x18, 0x00}, {0x0C, 0x00}, {0x12, 0x00}, {0x13, 0x00}, {0x33, 0x00},
|
||||||
|
{0x34, 0x00}, {0x2A, 0x00},
|
||||||
|
// Row 3: Sym Aa a s d f g h j k l ↑ _ Enter
|
||||||
|
{0x00, 0x00}, {0x00, 0x00}, {0x04, 0x00}, {0x16, 0x00}, {0x07, 0x00}, {0x09, 0x00},
|
||||||
|
{0x0A, 0x00}, {0x0B, 0x00}, {0x0D, 0x00}, {0x0E, 0x00}, {0x0F, 0x00}, {0x52, 0x00},
|
||||||
|
{0x2D, 0x02}, {0x28, 0x00},
|
||||||
|
// Row 4: Ctrl Alt z x c v b n m . ← ↓ → Space
|
||||||
|
{0x00, 0x00}, {0x00, 0x00}, {0x1D, 0x00}, {0x1B, 0x00}, {0x06, 0x00}, {0x19, 0x00},
|
||||||
|
{0x05, 0x00}, {0x11, 0x00}, {0x10, 0x00}, {0x37, 0x00}, {0x50, 0x00}, {0x51, 0x00},
|
||||||
|
{0x4F, 0x00}, {0x2C, 0x00},
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr HidMapping KEY_MATRIX_HID_SYM[70] = {
|
||||||
|
// Row 0: identical to base
|
||||||
|
{0x29, 0x00}, {0x1E, 0x00}, {0x1F, 0x00}, {0x20, 0x00}, {0x21, 0x00}, {0x22, 0x00},
|
||||||
|
{0x23, 0x00}, {0x24, 0x00}, {0x25, 0x00}, {0x26, 0x00}, {0x27, 0x00}, {0x2D, 0x00},
|
||||||
|
{0x2E, 0x02}, {0x4C, 0x00},
|
||||||
|
// Row 1: Sym deltas: ` → ~, ! → ?, * → /, ( → <, ) → >, [ → {, ] → }, backslash → |
|
||||||
|
{0x35, 0x02}, {0x38, 0x02}, {0x1F, 0x02}, {0x20, 0x02}, {0x21, 0x02}, {0x22, 0x02},
|
||||||
|
{0x23, 0x02}, {0x24, 0x02}, {0x38, 0x00}, {0x36, 0x02}, {0x37, 0x02}, {0x2F, 0x02},
|
||||||
|
{0x30, 0x02}, {0x31, 0x02},
|
||||||
|
// Row 2: Sym deltas: ; → :, ' → "
|
||||||
|
{0x2B, 0x00}, {0x14, 0x00}, {0x1A, 0x00}, {0x08, 0x00}, {0x15, 0x00}, {0x17, 0x00},
|
||||||
|
{0x1C, 0x00}, {0x18, 0x00}, {0x0C, 0x00}, {0x12, 0x00}, {0x13, 0x00}, {0x33, 0x02},
|
||||||
|
{0x34, 0x02}, {0x2A, 0x00},
|
||||||
|
// Row 3: Sym delta: _ → =
|
||||||
|
{0x00, 0x00}, {0x00, 0x00}, {0x04, 0x00}, {0x16, 0x00}, {0x07, 0x00}, {0x09, 0x00},
|
||||||
|
{0x0A, 0x00}, {0x0B, 0x00}, {0x0D, 0x00}, {0x0E, 0x00}, {0x0F, 0x00}, {0x52, 0x00},
|
||||||
|
{0x2E, 0x00}, {0x28, 0x00},
|
||||||
|
// Row 4: Sym delta: . → ,
|
||||||
|
{0x00, 0x00}, {0x00, 0x00}, {0x1D, 0x00}, {0x1B, 0x00}, {0x06, 0x00}, {0x19, 0x00},
|
||||||
|
{0x05, 0x00}, {0x11, 0x00}, {0x10, 0x00}, {0x36, 0x00}, {0x50, 0x00}, {0x51, 0x00},
|
||||||
|
{0x4F, 0x00}, {0x2C, 0x00},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// HID usage code + modifier → LVGL key
|
||||||
|
// Covers all codes present in the Tab5 matrix tables above. LV_KEY_* are plain uint32_t
|
||||||
|
// constants - matching KeyboardKeyData::key's driver-defined contract and the same convention
|
||||||
|
// m5stack-module's cardputer_keyboard.cpp kernel driver already uses.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static uint32_t tab5_translate_key(uint8_t keycode, uint8_t modifier, bool ctrl) {
|
||||||
|
const bool shift = (modifier & 0x22U) != 0U;
|
||||||
|
|
||||||
|
// Navigation → LVGL key constants
|
||||||
|
switch (keycode) {
|
||||||
|
case 0x29: return LV_KEY_ESC;
|
||||||
|
case 0x28: return LV_KEY_ENTER;
|
||||||
|
case 0x2A: return LV_KEY_BACKSPACE;
|
||||||
|
case 0x4C: return LV_KEY_DEL;
|
||||||
|
case 0x2B: return '\t';
|
||||||
|
// Arrows: Ctrl+arrow = focus navigation, plain arrow = raw cursor movement
|
||||||
|
case 0x52: return ctrl ? (uint32_t)LV_KEY_PREV : (uint32_t)LV_KEY_UP;
|
||||||
|
case 0x51: return ctrl ? (uint32_t)LV_KEY_NEXT : (uint32_t)LV_KEY_DOWN;
|
||||||
|
case 0x50: return ctrl ? (uint32_t)LV_KEY_PREV : (uint32_t)LV_KEY_LEFT;
|
||||||
|
case 0x4F: return ctrl ? (uint32_t)LV_KEY_NEXT : (uint32_t)LV_KEY_RIGHT;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Letters a–z / A–Z
|
||||||
|
if (keycode >= 0x04U && keycode <= 0x1DU) {
|
||||||
|
uint32_t c = static_cast<uint32_t>('a' + (keycode - 0x04U));
|
||||||
|
return shift ? (c - 0x20U) : c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numbers 1–0 and their shifted symbols
|
||||||
|
if (keycode >= 0x1EU && keycode <= 0x27U) {
|
||||||
|
static constexpr char nums[] = "1234567890";
|
||||||
|
static constexpr char snums[] = "!@#$%^&*()";
|
||||||
|
return shift ? static_cast<uint32_t>(snums[keycode - 0x1EU])
|
||||||
|
: static_cast<uint32_t>(nums[keycode - 0x1EU]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Space and punctuation - all codes present in the Tab5 matrix
|
||||||
|
switch (keycode) {
|
||||||
|
case 0x2C: return ' ';
|
||||||
|
case 0x2D: return shift ? '_' : '-';
|
||||||
|
case 0x2E: return shift ? '+' : '=';
|
||||||
|
case 0x2F: return shift ? '{' : '[';
|
||||||
|
case 0x30: return shift ? '}' : ']';
|
||||||
|
case 0x31: return shift ? '|' : '\\';
|
||||||
|
case 0x33: return shift ? ':' : ';';
|
||||||
|
case 0x34: return shift ? '"' : '\'';
|
||||||
|
case 0x35: return shift ? '~' : '`';
|
||||||
|
case 0x36: return shift ? '<' : ',';
|
||||||
|
case 0x37: return shift ? '>' : '.';
|
||||||
|
case 0x38: return shift ? '?' : '/';
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t now_ms() {
|
||||||
|
return static_cast<uint32_t>(esp_timer_get_time() / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Tab5KeyboardInternal {
|
||||||
|
QueueHandle_t queue;
|
||||||
|
|
||||||
|
// Modifier state
|
||||||
|
bool sym_active;
|
||||||
|
bool aa_sticky;
|
||||||
|
bool aa_held;
|
||||||
|
bool aa_tapped;
|
||||||
|
bool ctrl_held;
|
||||||
|
|
||||||
|
// IRQ-driven event gating
|
||||||
|
volatile bool irq_pending;
|
||||||
|
bool irq_configured;
|
||||||
|
gpio_num_t irq_pin;
|
||||||
|
|
||||||
|
// Poll/attach-check throttling (real-time based, since read_key() is called at whatever rate
|
||||||
|
// LVGL's indev timer and its own drain-loop - via continue_reading - happen to run at, unlike
|
||||||
|
// the old deprecated-HAL's fixed 20ms Timer)
|
||||||
|
uint32_t last_poll_ms;
|
||||||
|
uint32_t last_attach_check_ms;
|
||||||
|
bool was_attached;
|
||||||
|
bool pending_attach_state;
|
||||||
|
uint8_t pending_attach_confirm_count;
|
||||||
|
|
||||||
|
// Software key-repeat state (tracked by position to survive modifier changes)
|
||||||
|
uint32_t repeat_key;
|
||||||
|
uint8_t repeat_row;
|
||||||
|
uint8_t repeat_col;
|
||||||
|
uint32_t repeat_start_ms;
|
||||||
|
uint32_t repeat_last_ms;
|
||||||
|
|
||||||
|
Tab5KeyboardAttachListener attach_listener;
|
||||||
|
void* attach_listener_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// I2C helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static bool read_reg(Device* device, uint8_t reg, uint8_t* value) {
|
||||||
|
auto* parent = device_get_parent(device);
|
||||||
|
return i2c_controller_read_register(parent, I2C_ADDRESS, reg, value, 1, pdMS_TO_TICKS(50)) == ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool write_reg(Device* device, uint8_t reg, uint8_t value) {
|
||||||
|
auto* parent = device_get_parent(device);
|
||||||
|
return i2c_controller_write_register(parent, I2C_ADDRESS, reg, &value, 1, pdMS_TO_TICKS(50)) == ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_attached_raw(Device* device) {
|
||||||
|
auto* parent = device_get_parent(device);
|
||||||
|
return i2c_controller_has_device_at_address(parent, I2C_ADDRESS, pdMS_TO_TICKS(100)) == ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// LED helpers - LED0 = Sym indicator (green), LED1 = Aa indicator (red)
|
||||||
|
// RGB register layout: [B, G, R] per LED, stride 4 (byte 3 reserved)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static void update_leds(Device* device, const Tab5KeyboardInternal* internal) {
|
||||||
|
auto* parent = device_get_parent(device);
|
||||||
|
// [LED0: B,G,R, reserved, LED1: B,G,R]
|
||||||
|
uint8_t buf[7] = {
|
||||||
|
0x00, internal->sym_active ? uint8_t(0xA0) : uint8_t(0x00), 0x00, 0x00,
|
||||||
|
0x00, 0x00, internal->aa_sticky ? uint8_t(0xA0) : uint8_t(0x00),
|
||||||
|
};
|
||||||
|
i2c_controller_write_register(parent, I2C_ADDRESS, REG_RGB_BASE, buf, 7, pdMS_TO_TICKS(50));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// IRQ pin - active-low, falling edge
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static void IRAM_ATTR irq_handler(void* arg) {
|
||||||
|
auto* internal = static_cast<Tab5KeyboardInternal*>(arg);
|
||||||
|
internal->irq_pending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool configure_irq_pin(Tab5KeyboardInternal* internal) {
|
||||||
|
gpio_config_t io_conf{};
|
||||||
|
io_conf.pin_bit_mask = (1ULL << internal->irq_pin);
|
||||||
|
io_conf.mode = GPIO_MODE_INPUT;
|
||||||
|
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||||
|
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||||
|
io_conf.intr_type = GPIO_INTR_NEGEDGE;
|
||||||
|
if (gpio_config(&io_conf) != ESP_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const esp_err_t svc = gpio_install_isr_service(0);
|
||||||
|
if (svc != ESP_OK && svc != ESP_ERR_INVALID_STATE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gpio_isr_handler_add(internal->irq_pin, irq_handler, internal) != ESP_OK) {
|
||||||
|
gpio_set_intr_type(internal->irq_pin, GPIO_INTR_DISABLE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal->irq_configured = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove_irq_pin(Tab5KeyboardInternal* internal) {
|
||||||
|
if (!internal->irq_configured) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gpio_isr_handler_remove(internal->irq_pin);
|
||||||
|
internal->irq_configured = false;
|
||||||
|
internal->irq_pending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// drain_events - reads all pending events from the device queue
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static void drain_events(Device* device, Tab5KeyboardInternal* internal) {
|
||||||
|
uint8_t count = 0;
|
||||||
|
if (!read_reg(device, REG_EVENT_NUM, &count) || count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (count > 0) {
|
||||||
|
uint8_t raw = 0;
|
||||||
|
if (!read_reg(device, REG_KEY_EVENT, &raw) || raw == KEY_EVENT_EMPTY) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool pressed = (raw & 0x80U) != 0U;
|
||||||
|
const uint8_t row = (raw >> 4U) & 0x07U;
|
||||||
|
const uint8_t col = raw & 0x0FU;
|
||||||
|
|
||||||
|
// Modifier keys: update state, no key output
|
||||||
|
if (row == MOD_ROW_SYM && col == MOD_COL_SYM) {
|
||||||
|
internal->sym_active = pressed;
|
||||||
|
update_leds(device, internal);
|
||||||
|
count--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (row == MOD_ROW_AA && col == MOD_COL_AA) {
|
||||||
|
if (pressed) {
|
||||||
|
internal->aa_held = true;
|
||||||
|
internal->aa_tapped = true; // assume tap until a real key is pressed while held
|
||||||
|
} else {
|
||||||
|
// Only latch sticky if no non-modifier key was pressed during this hold
|
||||||
|
if (internal->aa_tapped) {
|
||||||
|
internal->aa_sticky = !internal->aa_sticky;
|
||||||
|
}
|
||||||
|
internal->aa_held = false;
|
||||||
|
internal->aa_tapped = false;
|
||||||
|
}
|
||||||
|
update_leds(device, internal);
|
||||||
|
count--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (row == MOD_ROW_CTRL && col == MOD_COL_CTRL) {
|
||||||
|
internal->ctrl_held = pressed;
|
||||||
|
count--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (row == MOD_ROW_ALT && col == MOD_COL_ALT) {
|
||||||
|
count--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row < 5U && col < 14U) {
|
||||||
|
const bool aa_active = internal->aa_held || internal->aa_sticky;
|
||||||
|
const HidMapping& m = internal->sym_active
|
||||||
|
? KEY_MATRIX_HID_SYM[row * 14U + col]
|
||||||
|
: KEY_MATRIX_HID_BASE[row * 14U + col];
|
||||||
|
if (m.keycode != 0U) {
|
||||||
|
const uint8_t modifier = static_cast<uint8_t>(m.modifier | (aa_active ? 0x02U : 0U));
|
||||||
|
const uint32_t lv_key = tab5_translate_key(m.keycode, modifier, internal->ctrl_held);
|
||||||
|
if (lv_key != 0U) {
|
||||||
|
if (pressed) {
|
||||||
|
// A real key was pressed — this hold is a chord, not a tap
|
||||||
|
internal->aa_tapped = false;
|
||||||
|
// Note: ESC used to stop the foreground app directly (tt::app::stop()) in
|
||||||
|
// the deprecated-HAL version - that's an app-layer concern the driver has
|
||||||
|
// no business reaching into, so ESC is now just queued as a normal key
|
||||||
|
// like everything else (LVGL/app code already handles ESC via focus/group
|
||||||
|
// navigation the same way a dedicated ESC key on any other keyboard would).
|
||||||
|
xQueueSend(internal->queue, &lv_key, 0);
|
||||||
|
// Arm software repeat tracking by row/col to survive modifier changes
|
||||||
|
const uint32_t now = now_ms();
|
||||||
|
internal->repeat_key = lv_key;
|
||||||
|
internal->repeat_row = row;
|
||||||
|
internal->repeat_col = col;
|
||||||
|
internal->repeat_start_ms = now;
|
||||||
|
internal->repeat_last_ms = 0;
|
||||||
|
// Consume sticky Aa after one keypress
|
||||||
|
if (internal->aa_sticky) {
|
||||||
|
internal->aa_sticky = false;
|
||||||
|
internal->aa_held = false;
|
||||||
|
update_leds(device, internal);
|
||||||
|
}
|
||||||
|
} else if (row == internal->repeat_row && col == internal->repeat_col) {
|
||||||
|
// Match release by position, not translated value — survives sticky Aa clear
|
||||||
|
internal->repeat_key = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear INT status after draining so the line de-asserts
|
||||||
|
write_reg(device, REG_INT_STAT, 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// reinit_device - (re)applies the device register configuration. Used at start() and again on
|
||||||
|
// hot-plug reattach, since the device's RGB mode and interrupt configuration are volatile and
|
||||||
|
// reset to power-on defaults when the keyboard is unplugged and reconnected.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static void reinit_device(Device* device, Tab5KeyboardInternal* internal) {
|
||||||
|
write_reg(device, REG_KEYBOARD_MODE, 0x00); // Normal mode
|
||||||
|
write_reg(device, REG_EVENT_NUM, 0x00); // flush event queue
|
||||||
|
write_reg(device, REG_INT_STAT, 0x00); // clear pending INT
|
||||||
|
write_reg(device, REG_RGB_MODE, 0x01); // Custom RGB mode (manual LED control)
|
||||||
|
write_reg(device, REG_BRIGHTNESS, 50); // 50% brightness
|
||||||
|
update_leds(device, internal); // restore current LED state
|
||||||
|
|
||||||
|
if (internal->irq_configured) {
|
||||||
|
write_reg(device, REG_INT_CFG, 0x01); // re-enable Normal-mode interrupt (bit 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// check_attach_state - throttled (~1s) hot-plug detection. Reapplies device register
|
||||||
|
// configuration on reattach, and notifies the registered attach listener (if any) of confirmed
|
||||||
|
// transitions - see Tab5KeyboardAttachListener's doc comment for the retry contract.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static void check_attach_state(Device* device, Tab5KeyboardInternal* internal) {
|
||||||
|
uint32_t now = now_ms();
|
||||||
|
if (now - internal->last_attach_check_ms < ATTACH_CHECK_INTERVAL_MS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internal->last_attach_check_ms = now;
|
||||||
|
|
||||||
|
const bool attached = is_attached_raw(device);
|
||||||
|
if (attached == internal->was_attached) {
|
||||||
|
internal->pending_attach_confirm_count = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Require the new state to be confirmed on a second consecutive check before acting - a
|
||||||
|
// single probe on a floating/half-connected bus (e.g. mid-unplug) can false-positive.
|
||||||
|
if (attached != internal->pending_attach_state || internal->pending_attach_confirm_count == 0) {
|
||||||
|
internal->pending_attach_state = attached;
|
||||||
|
internal->pending_attach_confirm_count = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internal->pending_attach_confirm_count = 0;
|
||||||
|
|
||||||
|
if (attached) {
|
||||||
|
reinit_device(device, internal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (internal->attach_listener != nullptr) {
|
||||||
|
if (!internal->attach_listener(device, attached, internal->attach_listener_context)) {
|
||||||
|
return; // not handled yet (e.g. LVGL lock busy) - retry on the next confirmed check
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal->was_attached = attached;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// poll_if_due - the closest equivalent to the old deprecated-HAL's 20ms-Timer-driven
|
||||||
|
// processKeyboard(): drains new key events (IRQ-gated or polled), ticks software key-repeat, and
|
||||||
|
// checks hot-plug attach state. Called from read_key(), throttled to real elapsed time rather
|
||||||
|
// than call count, since read_key() can be called back-to-back multiple times per LVGL indev
|
||||||
|
// timer tick while draining an already-queued burst (continue_reading).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static void poll_if_due(Device* device, Tab5KeyboardInternal* internal) {
|
||||||
|
uint32_t now = now_ms();
|
||||||
|
if (!internal->irq_pending && (now - internal->last_poll_ms) < POLL_INTERVAL_MS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internal->last_poll_ms = now;
|
||||||
|
|
||||||
|
bool should_drain = false;
|
||||||
|
if (internal->irq_configured) {
|
||||||
|
if (internal->irq_pending) {
|
||||||
|
internal->irq_pending = false;
|
||||||
|
should_drain = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Polling: check INT_STAT first — bit 0 = Normal mode event pending
|
||||||
|
uint8_t status = 0;
|
||||||
|
if (read_reg(device, REG_INT_STAT, &status) && (status & 0x01U)) {
|
||||||
|
should_drain = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (should_drain) {
|
||||||
|
drain_events(device, internal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Software key-repeat (runs every tick regardless of IRQ)
|
||||||
|
if (internal->repeat_key != 0U) {
|
||||||
|
if ((now - internal->repeat_start_ms) >= REPEAT_INITIAL_MS) {
|
||||||
|
const uint32_t last = internal->repeat_last_ms;
|
||||||
|
if (last == 0 || (now - last) >= REPEAT_RATE_MS) {
|
||||||
|
internal->repeat_last_ms = now;
|
||||||
|
xQueueSend(internal->queue, &internal->repeat_key, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check_attach_state(device, internal);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gpio_num_t pin_or_nc(const GpioPinSpec& pin) {
|
||||||
|
return pin.gpio_controller == nullptr ? GPIO_NUM_NC : static_cast<gpio_num_t>(pin.pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// region Driver lifecycle
|
||||||
|
|
||||||
|
static error_t start(Device* device) {
|
||||||
|
auto* parent = device_get_parent(device);
|
||||||
|
check(device_get_type(parent) == &I2C_CONTROLLER_TYPE);
|
||||||
|
|
||||||
|
const auto* config = GET_CONFIG(device);
|
||||||
|
|
||||||
|
auto* internal = static_cast<Tab5KeyboardInternal*>(malloc(sizeof(Tab5KeyboardInternal)));
|
||||||
|
if (internal == nullptr) {
|
||||||
|
return ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
memset(internal, 0, sizeof(Tab5KeyboardInternal));
|
||||||
|
|
||||||
|
internal->queue = xQueueCreate(20, sizeof(uint32_t));
|
||||||
|
if (internal->queue == nullptr) {
|
||||||
|
free(internal);
|
||||||
|
return ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal->repeat_row = 0xFF;
|
||||||
|
internal->repeat_col = 0xFF;
|
||||||
|
|
||||||
|
internal->irq_pin = pin_or_nc(config->pin_interrupt);
|
||||||
|
if (internal->irq_pin != GPIO_NUM_NC) {
|
||||||
|
configure_irq_pin(internal); // best-effort; falls back to polling if it fails. Must
|
||||||
|
// happen before reinit_device() so REG_INT_CFG is written
|
||||||
|
// if IRQ setup succeeded.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Best-effort: if the keyboard isn't attached yet (e.g. this device is constructed
|
||||||
|
// speculatively at boot so it can be hot-plug-detected later), these I2C writes fail
|
||||||
|
// silently and reinit_device() runs again once attach is detected.
|
||||||
|
reinit_device(device, internal);
|
||||||
|
internal->was_attached = is_attached_raw(device);
|
||||||
|
|
||||||
|
device_set_driver_data(device, internal);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t stop(Device* device) {
|
||||||
|
auto* internal = static_cast<Tab5KeyboardInternal*>(device_get_driver_data(device));
|
||||||
|
|
||||||
|
remove_irq_pin(internal);
|
||||||
|
write_reg(device, REG_INT_CFG, 0x00); // disable all interrupts
|
||||||
|
internal->sym_active = false;
|
||||||
|
internal->aa_sticky = false;
|
||||||
|
internal->aa_held = false;
|
||||||
|
update_leds(device, internal); // turn LEDs off
|
||||||
|
|
||||||
|
vQueueDelete(internal->queue);
|
||||||
|
free(internal);
|
||||||
|
device_set_driver_data(device, nullptr);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region KeyboardApi
|
||||||
|
|
||||||
|
static error_t tab5_keyboard_read_key(Device* device, KeyboardKeyData* data) {
|
||||||
|
auto* internal = static_cast<Tab5KeyboardInternal*>(device_get_driver_data(device));
|
||||||
|
|
||||||
|
poll_if_due(device, internal);
|
||||||
|
|
||||||
|
uint32_t lv_key = 0;
|
||||||
|
if (xQueueReceive(internal->queue, &lv_key, 0) == pdTRUE) {
|
||||||
|
data->key = lv_key;
|
||||||
|
data->pressed = true;
|
||||||
|
data->continue_reading = uxQueueMessagesWaiting(internal->queue) > 0;
|
||||||
|
} else {
|
||||||
|
data->key = 0;
|
||||||
|
data->pressed = false;
|
||||||
|
data->continue_reading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
static const KeyboardApi tab5_keyboard_api = {
|
||||||
|
.read_key = tab5_keyboard_read_key,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Defined in module.cpp - this driver is registered directly by m5stack-tab5's own module,
|
||||||
|
// not a separate Drivers/ module.
|
||||||
|
extern Module m5stack_tab5_module;
|
||||||
|
|
||||||
|
Driver tab5_keyboard_driver = {
|
||||||
|
.name = "tab5-keyboard",
|
||||||
|
.compatible = (const char*[]) { "m5stack,tab5-keyboard", nullptr },
|
||||||
|
.start_device = start,
|
||||||
|
.stop_device = stop,
|
||||||
|
.api = &tab5_keyboard_api,
|
||||||
|
.device_type = &KEYBOARD_TYPE,
|
||||||
|
.owner = &m5stack_tab5_module,
|
||||||
|
.internal = nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
// region Attach listener
|
||||||
|
|
||||||
|
void tab5_keyboard_add_attach_listener(Device* device, Tab5KeyboardAttachListener callback, void* context) {
|
||||||
|
auto* internal = static_cast<Tab5KeyboardInternal*>(device_get_driver_data(device));
|
||||||
|
if (internal->attach_listener != nullptr) {
|
||||||
|
LOG_W(TAG, "Replacing existing attach listener without it being removed first");
|
||||||
|
}
|
||||||
|
internal->attach_listener = callback;
|
||||||
|
internal->attach_listener_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tab5_keyboard_remove_attach_listener(Device* device, Tab5KeyboardAttachListener callback) {
|
||||||
|
auto* internal = static_cast<Tab5KeyboardInternal*>(device_get_driver_data(device));
|
||||||
|
if (internal->attach_listener != callback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internal->attach_listener = nullptr;
|
||||||
|
internal->attach_listener_context = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Dynamic construction
|
||||||
|
|
||||||
|
// Reacts to the Tab5 keyboard accessory's hot-plug attach state (see Tab5KeyboardAttachListener's
|
||||||
|
// doc comment above for the retry contract). This is UI-layer behavior the driver itself can't do
|
||||||
|
// (it has no LVGL dependency): switch to landscape while the keyboard is attached, restoring
|
||||||
|
// whatever rotation was active before once it's removed - but only if the user hasn't manually
|
||||||
|
// changed it since attaching, in which case their choice is respected. Ported as-is from the
|
||||||
|
// deprecated HAL's Tab5Keyboard::applyAutoRotation().
|
||||||
|
static bool on_keyboard_attach_changed(Device* /*device*/, bool attached, void* /*context*/) {
|
||||||
|
static lv_display_rotation_t saved_rotation = LV_DISPLAY_ROTATION_0;
|
||||||
|
static bool rotation_override_active = false;
|
||||||
|
|
||||||
|
auto* display = lv_display_get_default();
|
||||||
|
if (display == nullptr) {
|
||||||
|
return false; // LVGL not ready yet - retry on the next confirmed check
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lvgl_try_lock(pdMS_TO_TICKS(1000))) {
|
||||||
|
return false; // retry next check
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attached) {
|
||||||
|
if (lv_display_get_rotation(display) != LV_DISPLAY_ROTATION_90) {
|
||||||
|
saved_rotation = lv_display_get_rotation(display);
|
||||||
|
rotation_override_active = true;
|
||||||
|
lv_display_set_rotation(display, LV_DISPLAY_ROTATION_90);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Only restore if rotation is still what we set it to - if the user manually changed it
|
||||||
|
// since attaching, respect their choice instead.
|
||||||
|
if (rotation_override_active && lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_90) {
|
||||||
|
lv_display_set_rotation(display, saved_rotation);
|
||||||
|
}
|
||||||
|
rotation_override_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lvgl_unlock();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Tab5KeyboardConfig tab5_keyboard_config {};
|
||||||
|
static Device tab5_keyboard_device {};
|
||||||
|
|
||||||
|
// The keyboard accessory is a kernel driver device (m5stack,tab5-keyboard, defined directly in
|
||||||
|
// this project). Unlike the display/touch, it isn't gated on the display-variant detection at
|
||||||
|
// all (it lives on i2c2, a separate bus) - lvgl-module binds its indev unconditionally at boot
|
||||||
|
// regardless of physical attach state, and the driver's own read_key() polling handles hot-plug
|
||||||
|
// internally.
|
||||||
|
void tab5_create_keyboard(Device* i2c2) {
|
||||||
|
tab5_keyboard_device = Device {
|
||||||
|
.address = 0,
|
||||||
|
.name = "keyboard0",
|
||||||
|
.config = nullptr,
|
||||||
|
.parent = nullptr,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
GpioPinSpec pin_interrupt = GPIO_PIN_SPEC_NONE;
|
||||||
|
Device* gpio0 = nullptr;
|
||||||
|
if (device_get_by_name("gpio0", &gpio0) == ERROR_NONE) {
|
||||||
|
pin_interrupt = GpioPinSpec { gpio0, 50, GPIO_FLAG_NONE };
|
||||||
|
device_put(gpio0);
|
||||||
|
} else {
|
||||||
|
LOG_W(TAG, "display_detect: gpio0 not found, keyboard will fall back to INT_STAT polling");
|
||||||
|
}
|
||||||
|
|
||||||
|
tab5_keyboard_config = Tab5KeyboardConfig {
|
||||||
|
.address = 0x6D,
|
||||||
|
.pin_interrupt = pin_interrupt,
|
||||||
|
};
|
||||||
|
tab5_keyboard_device.config = &tab5_keyboard_config;
|
||||||
|
|
||||||
|
// Parented to i2c2 itself (not root, unlike the display): the keyboard driver's start() uses
|
||||||
|
// device_get_parent() as its I2C bus controller.
|
||||||
|
if (construct_add_start(&tab5_keyboard_device, i2c2, "m5stack,tab5-keyboard")) {
|
||||||
|
tab5_keyboard_add_attach_listener(&tab5_keyboard_device, on_keyboard_attach_changed, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/drivers/gpio.h>
|
||||||
|
|
||||||
|
struct Tab5KeyboardConfig {
|
||||||
|
// Fixed 0x6D on this keyboard accessory - kept as a field for parity/documentation with
|
||||||
|
// other dynamically-constructed devices in this project, not used to probe the bus.
|
||||||
|
uint8_t address;
|
||||||
|
// Native SoC GPIO (e.g. gpio0 pin 50) wired directly to the keyboard's INT line - not routed
|
||||||
|
// through an IO expander, since it needs a real hardware ISR for responsive key events.
|
||||||
|
// GPIO_PIN_SPEC_NONE falls back to polling REG_INT_STAT instead.
|
||||||
|
struct GpioPinSpec pin_interrupt;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Called when the keyboard accessory's hot-plug attach state changes (confirmed over two
|
||||||
|
// consecutive ~1s checks - see the driver source). Orientation changes and other LVGL-aware
|
||||||
|
// reactions to attach state live outside the driver (it has no LVGL dependency) - module.cpp
|
||||||
|
// registers a listener for this instead.
|
||||||
|
// @return true once handled; false to be called again on the next confirmed check with the same
|
||||||
|
// `attached` value (e.g. if the LVGL lock couldn't be acquired) - mirrors this driver's own
|
||||||
|
// internal retry-until-handled pattern for reinitializing the device on reattach.
|
||||||
|
typedef bool (*Tab5KeyboardAttachListener)(struct Device* device, bool attached, void* context);
|
||||||
|
|
||||||
|
// Only one listener is supported (this board only ever has one caller - module.cpp). Registering
|
||||||
|
// a new one before removing the previous replaces it with a warning logged.
|
||||||
|
void tab5_keyboard_add_attach_listener(struct Device* device, Tab5KeyboardAttachListener callback, void* context);
|
||||||
|
void tab5_keyboard_remove_attach_listener(struct Device* device, Tab5KeyboardAttachListener callback);
|
||||||
|
|
||||||
|
extern struct Driver tab5_keyboard_driver;
|
||||||
|
|
||||||
|
// Constructs and starts the keyboard accessory device on i2c2, then registers this project's own
|
||||||
|
// hot-plug rotation handler as its attach listener. Called from display_detect.cpp's
|
||||||
|
// on_display_detect_event() once i2c2 is up.
|
||||||
|
void tab5_create_keyboard(struct Device* i2c2);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#include "tab5_power_control.h"
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
#include <tactility/drivers/gpio_controller.h>
|
||||||
|
#include <tactility/drivers/power_supply.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
#include <tactility/module.h>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#define TAG "Tab5PowerControl"
|
||||||
|
|
||||||
|
// GPIO expander 1 (0x44) pins - see the pinout table in Configuration.cpp. Only these three are
|
||||||
|
// this driver's concern; the rest (WLAN enable, USB-A 5V, nCHG_QC_EN's boot default, charge
|
||||||
|
// state LED) stay owned by Configuration.cpp's initExpander1().
|
||||||
|
static constexpr int GPIO_EXP1_PIN_DEVICE_POWER = 4; // PWROFF_PULSE
|
||||||
|
static constexpr int GPIO_EXP1_PIN_IP2326_NCHG_QC_EN = 5; // active-low: LOW = quick charge enabled
|
||||||
|
static constexpr int GPIO_EXP1_PIN_IP2326_CHG_EN = 7; // HIGH = charging enabled
|
||||||
|
|
||||||
|
struct Tab5PowerControlInternal {
|
||||||
|
bool charging_allowed;
|
||||||
|
bool quick_charge_enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
// region PowerSupplyApi
|
||||||
|
|
||||||
|
// No battery metrics here - see ina226-module's power-supply device for voltage/capacity.
|
||||||
|
static bool ps_supports_property(Device*, PowerSupplyProperty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t ps_get_property(Device*, PowerSupplyProperty, PowerSupplyPropertyValue*) {
|
||||||
|
return ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ps_supports_charge_control(Device*) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ps_is_allowed_to_charge(Device* device) {
|
||||||
|
auto* internal = static_cast<Tab5PowerControlInternal*>(device_get_driver_data(device));
|
||||||
|
return internal->charging_allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t ps_set_allowed_to_charge(Device* device, bool allowed) {
|
||||||
|
auto* internal = static_cast<Tab5PowerControlInternal*>(device_get_driver_data(device));
|
||||||
|
auto* io_expander1 = device_get_parent(device);
|
||||||
|
|
||||||
|
auto* pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_CHG_EN, GPIO_OWNER_GPIO);
|
||||||
|
if (pin == nullptr) {
|
||||||
|
LOG_W(TAG, "Failed to acquire CHG_EN pin");
|
||||||
|
return ERROR_RESOURCE;
|
||||||
|
}
|
||||||
|
error_t error = gpio_descriptor_set_flags(pin, GPIO_FLAG_DIRECTION_OUTPUT);
|
||||||
|
if (error == ERROR_NONE) {
|
||||||
|
error = gpio_descriptor_set_level(pin, allowed);
|
||||||
|
}
|
||||||
|
gpio_descriptor_release(pin);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
LOG_W(TAG, "Failed to set CHG_EN pin");
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal->charging_allowed = allowed;
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ps_supports_quick_charge(Device*) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ps_is_quick_charge_enabled(Device* device) {
|
||||||
|
auto* internal = static_cast<Tab5PowerControlInternal*>(device_get_driver_data(device));
|
||||||
|
return internal->quick_charge_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t ps_set_quick_charge_enabled(Device* device, bool enabled) {
|
||||||
|
auto* internal = static_cast<Tab5PowerControlInternal*>(device_get_driver_data(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);
|
||||||
|
if (pin == nullptr) {
|
||||||
|
LOG_W(TAG, "Failed to acquire nCHG_QC_EN pin");
|
||||||
|
return ERROR_RESOURCE;
|
||||||
|
}
|
||||||
|
error_t error = gpio_descriptor_set_flags(pin, GPIO_FLAG_DIRECTION_OUTPUT);
|
||||||
|
if (error == ERROR_NONE) {
|
||||||
|
error = gpio_descriptor_set_level(pin, !enabled); // active-low
|
||||||
|
}
|
||||||
|
gpio_descriptor_release(pin);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
LOG_W(TAG, "Failed to set nCHG_QC_EN pin");
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal->quick_charge_enabled = enabled;
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ps_supports_power_off(Device*) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t ps_power_off(Device* device) {
|
||||||
|
auto* io_expander1 = device_get_parent(device);
|
||||||
|
auto* pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_DEVICE_POWER, GPIO_OWNER_GPIO);
|
||||||
|
if (pin == nullptr) {
|
||||||
|
LOG_E(TAG, "Failed to acquire DEVICE_POWER pin");
|
||||||
|
return ERROR_RESOURCE;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
gpio_descriptor_set_level(pin, true);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
gpio_descriptor_set_level(pin, false);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
}
|
||||||
|
gpio_descriptor_release(pin);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr PowerSupplyApi TAB5_POWER_CONTROL_API = {
|
||||||
|
.supports_property = ps_supports_property,
|
||||||
|
.get_property = ps_get_property,
|
||||||
|
.supports_charge_control = ps_supports_charge_control,
|
||||||
|
.is_allowed_to_charge = ps_is_allowed_to_charge,
|
||||||
|
.set_allowed_to_charge = ps_set_allowed_to_charge,
|
||||||
|
.supports_quick_charge = ps_supports_quick_charge,
|
||||||
|
.is_quick_charge_enabled = ps_is_quick_charge_enabled,
|
||||||
|
.set_quick_charge_enabled = ps_set_quick_charge_enabled,
|
||||||
|
.supports_power_off = ps_supports_power_off,
|
||||||
|
.power_off = ps_power_off,
|
||||||
|
};
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Driver lifecycle
|
||||||
|
|
||||||
|
static error_t start(Device* device) {
|
||||||
|
auto* internal = static_cast<Tab5PowerControlInternal*>(malloc(sizeof(Tab5PowerControlInternal)));
|
||||||
|
if (internal == nullptr) {
|
||||||
|
return ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
internal->charging_allowed = false;
|
||||||
|
internal->quick_charge_enabled = false;
|
||||||
|
device_set_driver_data(device, internal);
|
||||||
|
|
||||||
|
// DEVICE_POWER (PWROFF_PULSE) is only ever pulsed on demand (see ps_power_off) - configure it
|
||||||
|
// 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).
|
||||||
|
auto* io_expander1 = device_get_parent(device);
|
||||||
|
auto* device_power_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_DEVICE_POWER, GPIO_OWNER_GPIO);
|
||||||
|
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_release(device_power_pin);
|
||||||
|
} else {
|
||||||
|
LOG_W(TAG, "Failed to acquire DEVICE_POWER pin at start");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick charge off by default, matching the deprecated HAL's boot-time default (set via
|
||||||
|
// initExpander1(), now owned here for the same reason as above).
|
||||||
|
if (ps_set_quick_charge_enabled(device, false) != ERROR_NONE) {
|
||||||
|
LOG_W(TAG, "Failed to set default quick-charge state");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable charging by default, matching the deprecated HAL's Tab5Power constructor.
|
||||||
|
if (ps_set_allowed_to_charge(device, true) != ERROR_NONE) {
|
||||||
|
LOG_W(TAG, "Failed to enable charging by default");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t stop(Device* device) {
|
||||||
|
auto* internal = static_cast<Tab5PowerControlInternal*>(device_get_driver_data(device));
|
||||||
|
free(internal);
|
||||||
|
device_set_driver_data(device, nullptr);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// Defined in module.cpp - this driver is registered directly by m5stack-tab5's own module,
|
||||||
|
// not a separate Drivers/ module (same pattern as Tab5KeyboardDriver).
|
||||||
|
extern Module m5stack_tab5_module;
|
||||||
|
|
||||||
|
Driver tab5_power_control_driver = {
|
||||||
|
.name = "tab5-power-control",
|
||||||
|
.compatible = (const char*[]) { "m5stack,tab5-power-control", nullptr },
|
||||||
|
.start_device = start,
|
||||||
|
.stop_device = stop,
|
||||||
|
.api = &TAB5_POWER_CONTROL_API,
|
||||||
|
.device_type = &POWER_SUPPLY_TYPE,
|
||||||
|
.owner = &m5stack_tab5_module,
|
||||||
|
.internal = nullptr
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
|
||||||
|
// Board-specific charge-control/quick-charge/power-off glue for the IP2326 charger IC, driven
|
||||||
|
// through io_expander1 (PI4IOE5V6408 at 0x44) - separate from ina226's power-supply device
|
||||||
|
// (battery voltage/capacity, on the same board's INA226). Both are POWER_SUPPLY_TYPE kernel
|
||||||
|
// devices; consumers (Power.cpp, Statusbar.cpp, PowerOff.cpp) already enumerate every
|
||||||
|
// POWER_SUPPLY_TYPE device generically, so having two devices split by concern is the intended
|
||||||
|
// pattern rather than a gap - see module.cpp's create_tab5_power_control().
|
||||||
|
extern struct Driver tab5_power_control_driver;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#include "tab_5_camera.h"
|
||||||
|
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <esp_clock_output.h>
|
||||||
|
|
||||||
|
#define TAG "Tab5"
|
||||||
|
|
||||||
|
static esp_clock_output_mapping_handle_t camera_osc_handle = nullptr;
|
||||||
|
|
||||||
|
// 24 MHz clock on GPIO 36 for the SC2356 MIPI CSI sensor, required before esp_video_init. Uses
|
||||||
|
// SPLL (480 MHz) via esp_clock_output with divider 20 = 24 MHz exactly. This avoids any LEDC
|
||||||
|
// clock source conflict with the display backlight.
|
||||||
|
void tab5_camera_init() {
|
||||||
|
if (camera_osc_handle != nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_clock_output_start(CLKOUT_SIG_SPLL, GPIO_NUM_36, &camera_osc_handle) != ESP_OK) {
|
||||||
|
LOG_E(TAG, "Camera OSC clock output start failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (esp_clock_output_set_divider(camera_osc_handle, 20) != ESP_OK) {
|
||||||
|
LOG_E(TAG, "Camera OSC clock divider set failed");
|
||||||
|
esp_clock_output_stop(camera_osc_handle);
|
||||||
|
camera_osc_handle = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG_I(TAG, "Camera OSC 24MHz started on GPIO 36 (SPLL/20)");
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Starts the 24 MHz clock output on GPIO 36 the SC2356 MIPI CSI sensor needs before
|
||||||
|
// esp_video_init(). Called from module.cpp's start().
|
||||||
|
void tab5_camera_init();
|
||||||
@@ -1,178 +1,100 @@
|
|||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
#include <tactility/check.h>
|
||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/device_listener.h>
|
||||||
|
#include <tactility/driver.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>
|
||||||
|
|
||||||
#include <Tactility/hal/keyboard/KeyboardDevice.h>
|
#include <cstring>
|
||||||
|
|
||||||
#include "devices/Tab5Keyboard.h"
|
#include "devices/detect.h"
|
||||||
|
#include "devices/tab5_headphone_detect.h"
|
||||||
#include <freertos/FreeRTOS.h>
|
#include "devices/tab5_keyboard.h"
|
||||||
#include <freertos/timers.h>
|
#include "devices/tab5_power_control.h"
|
||||||
|
#include "devices/tab_5_camera.h"
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
#define TAG "Tab5"
|
#define TAG "Tab5"
|
||||||
|
|
||||||
|
// PI4IOE5V6408-0 (0x43) bit 1
|
||||||
constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1;
|
constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1;
|
||||||
|
// PI4IOE5V6408-0 (0x43) bit 7
|
||||||
constexpr auto GPIO_EXP0_PIN_HEADPHONE_DETECT = 7;
|
constexpr auto GPIO_EXP0_PIN_HEADPHONE_DETECT = 7;
|
||||||
constexpr auto HP_DETECT_POLL_MS = 1000;
|
|
||||||
constexpr auto KB_DETECT_POLL_MS = 1000;
|
|
||||||
|
|
||||||
// hp_detect_timer and kb_detect_timer are only touched from start()/stop(), which are called
|
static void tab5_init_expander0(Device* io_expander0) {
|
||||||
// serially by the module manager — no atomic needed for the handles themselves.
|
// Speaker and heapdhone pins are managed at runtime, so we can't have them as a hog in the dts file
|
||||||
static TimerHandle_t hp_detect_timer = nullptr;
|
// We have to init them manually.
|
||||||
static TimerHandle_t kb_detect_timer = nullptr;
|
auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO);
|
||||||
static std::atomic<Device*> io_expander0_cached { nullptr };
|
check(speaker_enable_pin);
|
||||||
// Flags are written by the timer daemon task and read by start()/stop() — use atomics.
|
auto* headphone_detect_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_HEADPHONE_DETECT, GPIO_OWNER_GPIO);
|
||||||
static std::atomic<bool> hp_detect_last { false };
|
check(headphone_detect_pin);
|
||||||
static std::atomic<bool> hp_detect_initialized { false };
|
|
||||||
static std::atomic<bool> kb_late_started { false };
|
|
||||||
|
|
||||||
static void headphoneDetectCallback(TimerHandle_t /*timer*/) {
|
gpio_descriptor_set_flags(speaker_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT);
|
||||||
Device* cached = io_expander0_cached.load(std::memory_order_acquire);
|
gpio_descriptor_set_flags(headphone_detect_pin, GPIO_FLAG_DIRECTION_INPUT);
|
||||||
if (!cached) {
|
|
||||||
cached = device_find_by_name("io_expander0");
|
gpio_descriptor_set_level(speaker_enable_pin, false);
|
||||||
io_expander0_cached.store(cached, std::memory_order_release);
|
|
||||||
}
|
gpio_descriptor_release(speaker_enable_pin);
|
||||||
auto* io_expander0 = cached;
|
gpio_descriptor_release(headphone_detect_pin);
|
||||||
if (!io_expander0) {
|
|
||||||
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);
|
static void tab5_enable_speaker_amp(Device* io_expander0) {
|
||||||
if (!hp_pin) {
|
auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO);
|
||||||
LOG_W(TAG, "hp_detect: HP_DET pin busy");
|
check(speaker_enable_pin, "Failed to acquire speaker enable pin");
|
||||||
|
error_t error = gpio_descriptor_set_level(speaker_enable_pin, true);
|
||||||
|
gpio_descriptor_release(speaker_enable_pin);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "Failed to enable amplifier: %s", error_to_string(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fires for every device's start/stop in the system; initializes io_expander0's pins and enables
|
||||||
|
// the speaker amplifier once io_expander0 itself has started.
|
||||||
|
static void on_io_expander0_started(Device* device, DeviceEvent event, void* context) {
|
||||||
|
(void)context;
|
||||||
|
|
||||||
|
static bool did_init = false;
|
||||||
|
if (did_init || event != DEVICE_EVENT_STARTED || strcmp(device->name, "io_expander0") != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
did_init = true;
|
||||||
|
|
||||||
bool hp = false;
|
tab5_init_expander0(device);
|
||||||
error_t err = gpio_descriptor_get_level(hp_pin, &hp);
|
tab5_enable_speaker_amp(device);
|
||||||
gpio_descriptor_release(hp_pin);
|
|
||||||
|
|
||||||
if (err != ERROR_NONE) {
|
|
||||||
LOG_W(TAG, "hp_detect: HP_DET read error: %s", error_to_string(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_D(TAG, "hp_detect: HP_DET=%d", (int)hp);
|
|
||||||
|
|
||||||
if (!hp_detect_initialized || hp != hp_detect_last) {
|
|
||||||
auto* spk_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO);
|
|
||||||
if (!spk_pin) {
|
|
||||||
LOG_W(TAG, "hp_detect: SPK_EN pin busy, will retry");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
error_t spk_err = gpio_descriptor_set_level(spk_pin, !hp);
|
|
||||||
gpio_descriptor_release(spk_pin);
|
|
||||||
if (spk_err != ERROR_NONE) {
|
|
||||||
LOG_W(TAG, "hp_detect: SPK_EN set error: %s, will retry", error_to_string(spk_err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
hp_detect_last = hp;
|
|
||||||
hp_detect_initialized = true;
|
|
||||||
LOG_I(TAG, "Headphones %s, speaker %s", hp ? "detected" : "removed", hp ? "disabled" : "enabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detects a Tab5 Keyboard add-on that was plugged in after boot (so it wasn't started by
|
|
||||||
// Lvgl.cpp's attachDevices()). Once lateStart() succeeds, this stops polling for good — there's
|
|
||||||
// no support for re-detecting after the indev is torn down again.
|
|
||||||
static void keyboardDetectCallback(TimerHandle_t timer) {
|
|
||||||
if (kb_late_started.load(std::memory_order_acquire)) {
|
|
||||||
xTimerStop(timer, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using namespace tt::hal;
|
|
||||||
auto keyboard = findFirstDevice<keyboard::KeyboardDevice>(tt::hal::Device::Type::Keyboard);
|
|
||||||
if (!keyboard) {
|
|
||||||
return; // Not registered yet, will retry on next tick
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyboard->getLvglIndev() != nullptr) {
|
|
||||||
// Already started (boot-time attach) — nothing left to do.
|
|
||||||
kb_late_started.store(true, std::memory_order_release);
|
|
||||||
xTimerStop(timer, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!keyboard->isAttached()) {
|
|
||||||
return; // Not plugged in yet, will retry on next tick
|
|
||||||
}
|
|
||||||
|
|
||||||
auto tab5_keyboard = std::static_pointer_cast<Tab5Keyboard>(keyboard);
|
|
||||||
if (tab5_keyboard->lateStart()) {
|
|
||||||
LOG_I(TAG, "kb_detect: keyboard attached post-boot, LVGL input started");
|
|
||||||
kb_late_started.store(true, std::memory_order_release);
|
|
||||||
xTimerStop(timer, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
static error_t start() {
|
||||||
|
|
||||||
if (hp_detect_timer != nullptr) {
|
/* Crash when construct fails, because if a single driver fails to construct,
|
||||||
LOG_W(TAG, "hp_detect timer already running");
|
* there is no guarantee that the previously constructed drivers can be destroyed */
|
||||||
return ERROR_NONE;
|
check(driver_construct_add(&tab5_keyboard_driver) == ERROR_NONE);
|
||||||
}
|
check(driver_construct_add(&tab5_power_control_driver) == ERROR_NONE);
|
||||||
|
|
||||||
hp_detect_initialized = false;
|
tab5_detect_start();
|
||||||
hp_detect_last = false;
|
|
||||||
|
|
||||||
hp_detect_timer = xTimerCreate("hp_detect", pdMS_TO_TICKS(HP_DETECT_POLL_MS), pdTRUE, nullptr, headphoneDetectCallback);
|
tab5_camera_init();
|
||||||
if (!hp_detect_timer) {
|
|
||||||
LOG_E(TAG, "Failed to create hp_detect timer");
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
if (xTimerStart(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
|
||||||
LOG_E(TAG, "Failed to start hp_detect timer");
|
|
||||||
xTimerDelete(hp_detect_timer, pdMS_TO_TICKS(100));
|
|
||||||
hp_detect_timer = nullptr;
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
kb_late_started = false;
|
device_listener_add(on_io_expander0_started, nullptr);
|
||||||
|
|
||||||
kb_detect_timer = xTimerCreate("kb_detect", pdMS_TO_TICKS(KB_DETECT_POLL_MS), pdTRUE, nullptr, keyboardDetectCallback);
|
tab5_headphone_detect_start();
|
||||||
if (!kb_detect_timer) {
|
|
||||||
LOG_E(TAG, "Failed to create kb_detect timer");
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
if (xTimerStart(kb_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
|
||||||
LOG_E(TAG, "Failed to start kb_detect timer");
|
|
||||||
xTimerDelete(kb_detect_timer, pdMS_TO_TICKS(100));
|
|
||||||
kb_detect_timer = nullptr;
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static error_t stop() {
|
static error_t stop() {
|
||||||
if (hp_detect_timer != nullptr) {
|
tab5_headphone_detect_stop();
|
||||||
if (xTimerStop(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
|
||||||
LOG_W(TAG, "Failed to stop hp_detect timer");
|
|
||||||
}
|
|
||||||
if (xTimerDelete(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
|
||||||
LOG_E(TAG, "Failed to delete hp_detect timer");
|
|
||||||
}
|
|
||||||
// Always clear the handle — stale non-null handle is worse than a resource leak,
|
|
||||||
// as it would cause start() to silently skip re-creating the timer.
|
|
||||||
hp_detect_timer = nullptr;
|
|
||||||
io_expander0_cached.store(nullptr, std::memory_order_release);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kb_detect_timer != nullptr) {
|
device_listener_remove(on_io_expander0_started);
|
||||||
if (xTimerStop(kb_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
|
||||||
LOG_W(TAG, "Failed to stop kb_detect timer");
|
tab5_detect_stop();
|
||||||
}
|
|
||||||
if (xTimerDelete(kb_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
/* Crash when destruct fails, because if a single driver fails to destruct,
|
||||||
LOG_E(TAG, "Failed to delete kb_detect timer");
|
* there is no guarantee that the previously destroyed drivers can be recovered */
|
||||||
}
|
check(driver_remove_destruct(&tab5_keyboard_driver) == ERROR_NONE);
|
||||||
kb_detect_timer = nullptr;
|
check(driver_remove_destruct(&tab5_power_control_driver) == ERROR_NONE);
|
||||||
}
|
|
||||||
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ hardware.tinyUsb=true
|
|||||||
|
|
||||||
storage.userDataLocation=SD
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
|
dependencies.useDeprecatedHal=false
|
||||||
|
|
||||||
display.size=5"
|
display.size=5"
|
||||||
display.shape=rectangle
|
display.shape=rectangle
|
||||||
display.dpi=294
|
display.dpi=294
|
||||||
@@ -39,6 +41,9 @@ sdkconfig.CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_1=8
|
|||||||
sdkconfig.CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE=15
|
sdkconfig.CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE=15
|
||||||
# Fixes recent changes to esp_hosted
|
# Fixes recent changes to esp_hosted
|
||||||
sdkconfig.CONFIG_ESP_HOSTED_USE_MEMPOOL=n
|
sdkconfig.CONFIG_ESP_HOSTED_USE_MEMPOOL=n
|
||||||
|
# ESP-NOW bridge (EspNowBackendHosted.cpp) registers 6 custom-RPC handlers (4 resp + 2 event); default cap is 3
|
||||||
|
sdkconfig.CONFIG_ESP_HOSTED_ENABLE_PEER_DATA_TRANSFER=y
|
||||||
|
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: use P4's PPA (pixel processing accelerator for rotation)
|
||||||
|
|||||||
@@ -8,4 +8,7 @@ dependencies:
|
|||||||
- Drivers/ina226-module
|
- Drivers/ina226-module
|
||||||
- Drivers/rx8130ce-module
|
- Drivers/rx8130ce-module
|
||||||
- Drivers/sc2356-module
|
- Drivers/sc2356-module
|
||||||
|
- Drivers/ili9881c-module
|
||||||
|
- Drivers/st7123-module
|
||||||
|
- Drivers/gt911-module
|
||||||
dts: m5stack,tab5.dts
|
dts: m5stack,tab5.dts
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
#include <tactility/bindings/esp32_grove.h>
|
#include <tactility/bindings/esp32_grove.h>
|
||||||
#include <tactility/bindings/esp32_i2c_master.h>
|
#include <tactility/bindings/esp32_i2c_master.h>
|
||||||
#include <tactility/bindings/esp32_i2s.h>
|
#include <tactility/bindings/esp32_i2s.h>
|
||||||
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
#include <tactility/bindings/esp32_sdmmc.h>
|
#include <tactility/bindings/esp32_sdmmc.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
#include <tactility/bindings/esp32_usbhost.h>
|
#include <tactility/bindings/esp32_usbhost.h>
|
||||||
#include <tactility/bindings/esp32_wifi.h>
|
#include <tactility/bindings/esp32_wifi.h>
|
||||||
|
#include <tactility/bindings/gpio_hog.h>
|
||||||
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
#include <bindings/bmi270.h>
|
#include <bindings/bmi270.h>
|
||||||
#include <bindings/es7210.h>
|
#include <bindings/es7210.h>
|
||||||
#include <bindings/es8388.h>
|
#include <bindings/es8388.h>
|
||||||
@@ -37,6 +40,19 @@
|
|||||||
gpio-count = <57>;
|
gpio-count = <57>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
display_backlight_pwm {
|
||||||
|
compatible = "espressif,esp32-pwm-ledc";
|
||||||
|
pin = <&gpio0 22 GPIO_FLAG_NONE>;
|
||||||
|
period-ns = <200000>; // 5000 Hz
|
||||||
|
ledc-timer = <1>;
|
||||||
|
ledc-channel = <0>;
|
||||||
|
};
|
||||||
|
|
||||||
|
display_backlight {
|
||||||
|
compatible = "pwm-backlight";
|
||||||
|
pwm = <&display_backlight_pwm>;
|
||||||
|
};
|
||||||
|
|
||||||
// Declared before i2c0 so it starts before the es8388/es7210 codecs that reference it
|
// Declared before i2c0 so it starts before the es8388/es7210 codecs that reference it
|
||||||
i2s0 {
|
i2s0 {
|
||||||
compatible = "espressif,esp32-i2s";
|
compatible = "espressif,esp32-i2s";
|
||||||
@@ -55,12 +71,12 @@
|
|||||||
pin-sda = <&gpio0 31 GPIO_FLAG_PULL_UP>;
|
pin-sda = <&gpio0 31 GPIO_FLAG_PULL_UP>;
|
||||||
pin-scl = <&gpio0 32 GPIO_FLAG_PULL_UP>;
|
pin-scl = <&gpio0 32 GPIO_FLAG_PULL_UP>;
|
||||||
|
|
||||||
io_expander0 {
|
io_expander0: io_expander0 {
|
||||||
compatible = "diodes,pi4ioe5v6408";
|
compatible = "diodes,pi4ioe5v6408";
|
||||||
reg = <0x43>;
|
reg = <0x43>;
|
||||||
};
|
};
|
||||||
|
|
||||||
io_expander1 {
|
io_expander1: io_expander1 {
|
||||||
compatible = "diodes,pi4ioe5v6408";
|
compatible = "diodes,pi4ioe5v6408";
|
||||||
reg = <0x44>;
|
reg = <0x44>;
|
||||||
};
|
};
|
||||||
@@ -98,6 +114,10 @@
|
|||||||
compatible = "ti,ina226";
|
compatible = "ti,ina226";
|
||||||
reg = <0x41>;
|
reg = <0x41>;
|
||||||
shunt-milliohms = <5>;
|
shunt-milliohms = <5>;
|
||||||
|
// 2S NP-F550 pack
|
||||||
|
power-supply;
|
||||||
|
power-supply-reference-voltage-mv = <8400>;
|
||||||
|
power-supply-min-voltage-mv = <6400>;
|
||||||
};
|
};
|
||||||
|
|
||||||
sc2356 {
|
sc2356 {
|
||||||
@@ -106,6 +126,42 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exp0_rf_internal_external {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&io_expander0 0 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_OUTPUT_LOW>;
|
||||||
|
};
|
||||||
|
|
||||||
|
exp0_external_5v_bus_enable {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&io_expander0 2 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
||||||
|
};
|
||||||
|
|
||||||
|
exp0_camera_reset {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&io_expander0 6 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
||||||
|
};
|
||||||
|
|
||||||
|
exp1_c6_wlan_enable {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&io_expander1 0 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
||||||
|
};
|
||||||
|
|
||||||
|
exp1_usb_a_5v_enable {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&io_expander1 3 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
||||||
|
};
|
||||||
|
|
||||||
|
exp1_ip2326_chg_stat_led {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&io_expander1 6 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_OUTPUT_LOW>;
|
||||||
|
};
|
||||||
|
|
||||||
port_a: grove0 {
|
port_a: grove0 {
|
||||||
compatible = "espressif,esp32-grove";
|
compatible = "espressif,esp32-grove";
|
||||||
defaultMode = <GROVE_MODE_I2C>;
|
defaultMode = <GROVE_MODE_I2C>;
|
||||||
@@ -145,17 +201,14 @@
|
|||||||
|
|
||||||
usbhosthid0 {
|
usbhosthid0 {
|
||||||
compatible = "espressif,esp32-usbhost-hid";
|
compatible = "espressif,esp32-usbhost-hid";
|
||||||
//status = "disabled";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
usbhostmidi0 {
|
usbhostmidi0 {
|
||||||
compatible = "espressif,esp32-usbhost-midi";
|
compatible = "espressif,esp32-usbhost-midi";
|
||||||
//status = "disabled";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
usbhostmsc0 {
|
usbhostmsc0 {
|
||||||
compatible = "espressif,esp32-usbhost-msc";
|
compatible = "espressif,esp32-usbhost-msc";
|
||||||
//status = "disabled";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
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 ButtonControl ST7789 PwmBacklight driver
|
REQUIRES TactilityKernel driver
|
||||||
)
|
)
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#include "devices/Display.h"
|
|
||||||
|
|
||||||
#include <Tactility/hal/Configuration.h>
|
|
||||||
#include <PwmBacklight.h>
|
|
||||||
#include <ButtonControl.h>
|
|
||||||
|
|
||||||
using namespace tt::hal;
|
|
||||||
|
|
||||||
static DeviceVector createDevices() {
|
|
||||||
return {
|
|
||||||
createDisplay(),
|
|
||||||
ButtonControl::createOneButtonControl(0)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool initBoot() {
|
|
||||||
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern const Configuration hardwareConfiguration = {
|
|
||||||
.initBoot = initBoot,
|
|
||||||
.createDevices = createDevices
|
|
||||||
};
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#include "Display.h"
|
|
||||||
|
|
||||||
#include <PwmBacklight.h>
|
|
||||||
#include <St7789Display.h>
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
|
||||||
St7789Display::Configuration panel_configuration = {
|
|
||||||
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
|
|
||||||
.verticalResolution = LCD_VERTICAL_RESOLUTION,
|
|
||||||
.gapX = 52,
|
|
||||||
.gapY = 40,
|
|
||||||
.swapXY = false,
|
|
||||||
.mirrorX = true,
|
|
||||||
.mirrorY = true,
|
|
||||||
.invertColor = true,
|
|
||||||
.bufferSize = LCD_BUFFER_SIZE,
|
|
||||||
.touch = nullptr,
|
|
||||||
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
|
|
||||||
.resetPin = LCD_PIN_RESET,
|
|
||||||
.lvglSwapBytes = false
|
|
||||||
};
|
|
||||||
|
|
||||||
auto spi_configuration = std::make_shared<St7789Display::SpiConfiguration>(St7789Display::SpiConfiguration {
|
|
||||||
.spiHostDevice = LCD_SPI_HOST,
|
|
||||||
.csPin = LCD_PIN_CS,
|
|
||||||
.dcPin = LCD_PIN_DC,
|
|
||||||
.pixelClockFrequency = 62'500'000,
|
|
||||||
.transactionQueueDepth = 10
|
|
||||||
});
|
|
||||||
|
|
||||||
return std::make_shared<St7789Display>(panel_configuration, spi_configuration);
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/display/DisplayDevice.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <driver/spi_common.h>
|
|
||||||
|
|
||||||
constexpr auto LCD_SPI_HOST = SPI2_HOST;
|
|
||||||
constexpr auto LCD_PIN_CS = GPIO_NUM_10;
|
|
||||||
constexpr auto LCD_PIN_DC = GPIO_NUM_8;
|
|
||||||
constexpr auto LCD_PIN_RESET = GPIO_NUM_9;
|
|
||||||
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_7;
|
|
||||||
constexpr auto LCD_HORIZONTAL_RESOLUTION = 135;
|
|
||||||
constexpr auto LCD_VERTICAL_RESOLUTION = 240;
|
|
||||||
constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 3;
|
|
||||||
constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT;
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
|
||||||
@@ -14,6 +14,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=1.14"
|
display.size=1.14"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
- Platforms/platform-esp32
|
- Platforms/platform-esp32
|
||||||
|
- Drivers/st7789-module
|
||||||
|
- Drivers/button-control-module
|
||||||
dts: waveshare,esp32-s3-geek.dts
|
dts: waveshare,esp32-s3-geek.dts
|
||||||
|
|||||||
@@ -8,7 +8,11 @@
|
|||||||
#include <tactility/bindings/esp32_sdmmc.h>
|
#include <tactility/bindings/esp32_sdmmc.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/display_placeholder.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
|
|
||||||
|
#include <bindings/st7789.h>
|
||||||
|
#include <bindings/button_control.h>
|
||||||
|
|
||||||
/ {
|
/ {
|
||||||
compatible = "root";
|
compatible = "root";
|
||||||
@@ -37,6 +41,22 @@
|
|||||||
pin-scl = <&gpio0 17 GPIO_FLAG_NONE>;
|
pin-scl = <&gpio0 17 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
display_backlight_pwm {
|
||||||
|
compatible = "espressif,esp32-pwm-ledc";
|
||||||
|
pin = <&gpio0 7 GPIO_FLAG_NONE>;
|
||||||
|
period-ns = <1953125>;
|
||||||
|
ledc-timer = <0>;
|
||||||
|
ledc-channel = <0>;
|
||||||
|
};
|
||||||
|
|
||||||
|
display_backlight {
|
||||||
|
compatible = "pwm-backlight";
|
||||||
|
// Off by default so display power-on won't show the screen from before the last power loss.
|
||||||
|
// The display backlight is turned on during the boot process.
|
||||||
|
status = "disabled";
|
||||||
|
pwm = <&display_backlight_pwm>;
|
||||||
|
};
|
||||||
|
|
||||||
spi0 {
|
spi0 {
|
||||||
compatible = "espressif,esp32-spi";
|
compatible = "espressif,esp32-spi";
|
||||||
host = <SPI2_HOST>;
|
host = <SPI2_HOST>;
|
||||||
@@ -44,11 +64,27 @@
|
|||||||
pin-mosi = <&gpio0 11 GPIO_FLAG_NONE>;
|
pin-mosi = <&gpio0 11 GPIO_FLAG_NONE>;
|
||||||
pin-sclk = <&gpio0 12 GPIO_FLAG_NONE>;
|
pin-sclk = <&gpio0 12 GPIO_FLAG_NONE>;
|
||||||
|
|
||||||
display {
|
display@0 {
|
||||||
compatible = "display-placeholder";
|
compatible = "sitronix,st7789";
|
||||||
|
horizontal-resolution = <135>;
|
||||||
|
vertical-resolution = <240>;
|
||||||
|
gap-x = <52>;
|
||||||
|
gap-y = <40>;
|
||||||
|
mirror-x;
|
||||||
|
mirror-y;
|
||||||
|
invert-color;
|
||||||
|
pixel-clock-hz = <62500000>;
|
||||||
|
pin-dc = <&gpio0 8 GPIO_FLAG_NONE>;
|
||||||
|
pin-reset = <&gpio0 9 GPIO_FLAG_NONE>;
|
||||||
|
backlight = <&display_backlight>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
buttons {
|
||||||
|
compatible = "tactility,button-control";
|
||||||
|
pin-primary = <&gpio0 0 GPIO_FLAG_NONE>;
|
||||||
|
};
|
||||||
|
|
||||||
sdmmc0 {
|
sdmmc0 {
|
||||||
compatible = "espressif,esp32-sdmmc";
|
compatible = "espressif,esp32-sdmmc";
|
||||||
pin-clk = <&gpio0 36 GPIO_FLAG_NONE>;
|
pin-clk = <&gpio0 36 GPIO_FLAG_NONE>;
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
Module waveshare_s3_lcd_13_module = {
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module waveshare_s3_lcd_13_module = {
|
|
||||||
.name = "waveshare-s3-lcd-13",
|
.name = "waveshare-s3-lcd-13",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
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 esp_lvgl_port GC9A01 CST816S PwmBacklight driver
|
REQUIRES TactilityKernel
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
#include "devices/Display.h"
|
|
||||||
|
|
||||||
#include <Tactility/hal/Configuration.h>
|
|
||||||
#include <PwmBacklight.h>
|
|
||||||
|
|
||||||
using namespace tt::hal;
|
|
||||||
|
|
||||||
static DeviceVector createDevices() {
|
|
||||||
return {
|
|
||||||
createDisplay(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool initBoot() {
|
|
||||||
return driver::pwmbacklight::init(GPIO_NUM_2, 256);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern const Configuration hardwareConfiguration = {
|
|
||||||
.initBoot = initBoot,
|
|
||||||
.createDevices = createDevices
|
|
||||||
};
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#include "Display.h"
|
|
||||||
|
|
||||||
#include <Cst816Touch.h>
|
|
||||||
#include <PwmBacklight.h>
|
|
||||||
#include <Gc9a01Display.h>
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
|
|
||||||
auto configuration = std::make_unique<Cst816sTouch::Configuration>(
|
|
||||||
I2C_NUM_0,
|
|
||||||
240,
|
|
||||||
240,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
GPIO_NUM_13,
|
|
||||||
GPIO_NUM_5
|
|
||||||
);
|
|
||||||
|
|
||||||
return std::make_shared<Cst816sTouch>(std::move(configuration));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
|
||||||
auto touch = createTouch();
|
|
||||||
|
|
||||||
auto configuration = std::make_unique<Gc9a01Display::Configuration>(
|
|
||||||
SPI2_HOST,
|
|
||||||
GPIO_NUM_9,
|
|
||||||
GPIO_NUM_8,
|
|
||||||
240,
|
|
||||||
240,
|
|
||||||
touch,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
0,
|
|
||||||
LCD_RGB_ELEMENT_ORDER_BGR
|
|
||||||
);
|
|
||||||
|
|
||||||
configuration->resetPin = GPIO_NUM_14;
|
|
||||||
configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty;
|
|
||||||
|
|
||||||
auto display = std::make_shared<Gc9a01Display>(std::move(configuration));
|
|
||||||
return std::reinterpret_pointer_cast<tt::hal::display::DisplayDevice>(display);
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Tactility/hal/display/DisplayDevice.h"
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#include <tactility/module.h>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
// Empty for now
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
// Empty for now
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module waveshare_s3_touch_lcd_128_module = {
|
|
||||||
.name = "waveshare-s3-touch-lcd-128",
|
|
||||||
.start = start,
|
|
||||||
.stop = stop,
|
|
||||||
.symbols = nullptr,
|
|
||||||
.internal = nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,9 @@ hardware.tinyUsb=true
|
|||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
storage.userDataLocation=SD
|
dependencies.useDeprecatedHal=false
|
||||||
|
|
||||||
|
storage.userDataLocation=Internal
|
||||||
|
|
||||||
display.size=1.28"
|
display.size=1.28"
|
||||||
display.shape=circle
|
display.shape=circle
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
- Platforms/platform-esp32
|
- Platforms/platform-esp32
|
||||||
- Drivers/qmi8658-module
|
- Drivers/qmi8658-module
|
||||||
|
- Drivers/cst816t-module
|
||||||
|
- Drivers/gc9a01-module
|
||||||
dts: waveshare,s3-touch-lcd-128.dts
|
dts: waveshare,s3-touch-lcd-128.dts
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#include <tactility/error.h>
|
||||||
|
#include <tactility/module.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
Module waveshare_s3_touch_lcd_128_module = {
|
||||||
|
.name = "waveshare-s3-touch-lcd-128",
|
||||||
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
|
.symbols = nullptr,
|
||||||
|
.internal = nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,7 +8,10 @@
|
|||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <bindings/qmi8658.h>
|
#include <bindings/qmi8658.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/display_placeholder.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
|
#include <bindings/cst816t.h>
|
||||||
|
#include <bindings/gc9a01.h>
|
||||||
|
|
||||||
// Reference: https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-1.28
|
// Reference: https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-1.28
|
||||||
/ {
|
/ {
|
||||||
@@ -41,6 +44,33 @@
|
|||||||
compatible = "qst,qmi8658";
|
compatible = "qst,qmi8658";
|
||||||
reg = <0x6B>;
|
reg = <0x6B>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
touch0 {
|
||||||
|
compatible = "hynitron,cst816t";
|
||||||
|
reg = <0x15>;
|
||||||
|
x-max = <240>;
|
||||||
|
y-max = <240>;
|
||||||
|
mirror-x;
|
||||||
|
mirror-y;
|
||||||
|
pin-reset = <&gpio0 13 GPIO_FLAG_NONE>;
|
||||||
|
pin-interrupt = <&gpio0 5 GPIO_FLAG_NONE>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
display_backlight_pwm {
|
||||||
|
compatible = "espressif,esp32-pwm-ledc";
|
||||||
|
pin = <&gpio0 2 GPIO_FLAG_NONE>;
|
||||||
|
period-ns = <3906250>;
|
||||||
|
ledc-timer = <0>;
|
||||||
|
ledc-channel = <0>;
|
||||||
|
};
|
||||||
|
|
||||||
|
display_backlight {
|
||||||
|
compatible = "pwm-backlight";
|
||||||
|
// Off by default so display power-on won't show the screen from before the last power loss.
|
||||||
|
// The display backlight is turned on during the boot process.
|
||||||
|
status = "disabled";
|
||||||
|
pwm = <&display_backlight_pwm>;
|
||||||
};
|
};
|
||||||
|
|
||||||
spi0 {
|
spi0 {
|
||||||
@@ -51,22 +81,16 @@
|
|||||||
pin-miso = <&gpio0 12 GPIO_FLAG_NONE>;
|
pin-miso = <&gpio0 12 GPIO_FLAG_NONE>;
|
||||||
pin-sclk = <&gpio0 10 GPIO_FLAG_NONE>;
|
pin-sclk = <&gpio0 10 GPIO_FLAG_NONE>;
|
||||||
|
|
||||||
display {
|
display@0 {
|
||||||
compatible = "display-placeholder";
|
compatible = "galaxycore,gc9a01";
|
||||||
};
|
horizontal-resolution = <240>;
|
||||||
};
|
vertical-resolution = <240>;
|
||||||
|
mirror-y;
|
||||||
spi1 {
|
invert-color;
|
||||||
compatible = "espressif,esp32-spi";
|
bgr-order;
|
||||||
host = <SPI3_HOST>;
|
pin-dc = <&gpio0 8 GPIO_FLAG_NONE>;
|
||||||
cs-gpios = <&gpio0 18 GPIO_FLAG_NONE>;
|
pin-reset = <&gpio0 14 GPIO_FLAG_NONE>;
|
||||||
pin-mosi = <&gpio0 16 GPIO_FLAG_NONE>;
|
backlight = <&display_backlight>;
|
||||||
pin-miso = <&gpio0 15 GPIO_FLAG_NONE>;
|
|
||||||
pin-sclk = <&gpio0 17 GPIO_FLAG_NONE>;
|
|
||||||
|
|
||||||
sdcard@0 {
|
|
||||||
compatible = "espressif,esp32-sdspi";
|
|
||||||
frequency-khz = <20000>;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
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 esp_lvgl_port EspLcdCompat AXS5106 JD9853
|
REQUIRES TactilityKernel
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
#include "devices/Display.h"
|
|
||||||
|
|
||||||
#include <Tactility/hal/Configuration.h>
|
|
||||||
|
|
||||||
bool initBoot();
|
|
||||||
|
|
||||||
using namespace tt::hal;
|
|
||||||
|
|
||||||
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
|
|
||||||
return {
|
|
||||||
createDisplay(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
extern const Configuration hardwareConfiguration = {
|
|
||||||
.initBoot = initBoot,
|
|
||||||
.createDevices = createDevices
|
|
||||||
};
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#include <Tactility/LogMessages.h>
|
|
||||||
#include <Tactility/TactilityCore.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
#include <driver/ledc.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "Waveshare";
|
|
||||||
|
|
||||||
constexpr auto LCD_BL_LEDC_TIMER = LEDC_TIMER_0;
|
|
||||||
constexpr auto LCD_BL_LEDC_MODE = LEDC_LOW_SPEED_MODE;
|
|
||||||
constexpr auto LCD_BL_LEDC_CHANNEL = LEDC_CHANNEL_0;
|
|
||||||
constexpr auto LCD_BL_LEDC_DUTY_RES = LEDC_TIMER_10_BIT;
|
|
||||||
constexpr auto LCD_BL_LEDC_DUTY = 1024;
|
|
||||||
constexpr auto LCD_BL_LEDC_FREQUENCY = 10000;
|
|
||||||
|
|
||||||
void setBacklightDuty(uint8_t level) {
|
|
||||||
uint32_t duty = (level * (LCD_BL_LEDC_DUTY - 1)) / 255;
|
|
||||||
ESP_ERROR_CHECK(ledc_set_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL, duty));
|
|
||||||
ESP_ERROR_CHECK(ledc_update_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL));
|
|
||||||
}
|
|
||||||
|
|
||||||
void initBacklight() {
|
|
||||||
ledc_timer_config_t timer_config = {
|
|
||||||
.speed_mode = LCD_BL_LEDC_MODE,
|
|
||||||
.duty_resolution = LCD_BL_LEDC_DUTY_RES,
|
|
||||||
.timer_num = LCD_BL_LEDC_TIMER,
|
|
||||||
.freq_hz = LCD_BL_LEDC_FREQUENCY,
|
|
||||||
.clk_cfg = LEDC_AUTO_CLK};
|
|
||||||
ESP_ERROR_CHECK(ledc_timer_config(&timer_config));
|
|
||||||
|
|
||||||
ledc_channel_config_t channel_config = {
|
|
||||||
.gpio_num = GPIO_NUM_46,
|
|
||||||
.speed_mode = LCD_BL_LEDC_MODE,
|
|
||||||
.channel = LCD_BL_LEDC_CHANNEL,
|
|
||||||
.intr_type = LEDC_INTR_DISABLE,
|
|
||||||
.timer_sel = LCD_BL_LEDC_TIMER,
|
|
||||||
.duty = 0, // Set duty to 0%
|
|
||||||
.hpoint = 0};
|
|
||||||
ESP_ERROR_CHECK(ledc_channel_config(&channel_config));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool initBoot() {
|
|
||||||
LOG_I(TAG, LOG_MESSAGE_POWER_ON_START);
|
|
||||||
initBacklight();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user