Tab5 features, StackChan, fixes, drivers.... (#526)

This commit is contained in:
Shadowtrance 2026-05-29 07:31:25 +10:00 committed by GitHub
parent 5c78d55b04
commit a59fbf4ed5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
140 changed files with 2500 additions and 835 deletions

View File

@ -65,8 +65,10 @@ jobs:
{ id: m5stack-core2, arch: esp32 },
{ id: m5stack-cores3, arch: esp32s3 },
{ id: m5stack-papers3, arch: esp32s3 },
{ id: m5stack-stackchan, arch: esp32s3 },
{ id: m5stack-stickc-plus, arch: esp32 },
{ id: m5stack-stickc-plus2, arch: esp32 },
{ id: m5stack-sticks3, arch: esp32s3 },
{ id: m5stack-tab5, arch: esp32p4 },
{ id: unphone, arch: esp32s3 },
{ id: waveshare-esp32-s3-geek, arch: esp32s3 },

View File

@ -256,6 +256,15 @@ def generate_devicetree_c(filename: str, items: list[object], bindings: list[Bin
file.write("};\n")
# Gather module symbols
module_symbol_names = []
# Device's own module goes first (started before its dependency modules)
if config.dts:
device_dir = os.path.dirname(os.path.normpath(config.dts))
device_name = os.path.basename(device_dir)
if device_name:
device_module_name = device_name.replace('-', '_')
if not device_module_name.endswith("_module"):
device_module_name += "_module"
module_symbol_names.append(device_module_name)
for dependency in config.dependencies:
dependency_name = os.path.basename(os.path.normpath(dependency))
module_symbol_name = f"{dependency_name.replace('-', '_')}"

View File

@ -51,7 +51,9 @@ struct DtsDevice dts_devices[] = {
DTS_DEVICE_TERMINATOR
};
extern struct Module data_module;
struct Module* dts_modules[] = {
&data_module,
NULL
};

View File

@ -3,6 +3,13 @@ idf_component_register(
"Libraries/TactilityC/include"
"Libraries/TactilityKernel/include"
"Libraries/lvgl/include"
"Modules/lvgl-module/include"
"Drivers/bm8563-module/include"
"Drivers/bmi270-module/include"
"Drivers/mpu6886-module/include"
"Drivers/pi4ioe5v6408-module/include"
"Drivers/qmi8658-module/include"
"Drivers/rx8130ce-module/include"
REQUIRES esp_timer
)

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
@ -9,6 +10,10 @@
compatible = "root";
model = "BigTreeTech Panda Touch";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -12,6 +12,7 @@ spiRam=true
spiRamMode=OCT
spiRamSpeed=120M
esptoolFlashFreq=120M
bluetooth=true
[display]
size=5"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
@ -9,6 +10,10 @@
compatible = "root";
model = "CYD 4848S040C";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -11,6 +11,7 @@ flashSize=16MB
spiRam=true
spiRamMode=OCT
spiRamSpeed=80M
bluetooth=true
[display]
size=4"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_uart.h>
@ -10,6 +11,10 @@
compatible = "root";
model = "CYD 8048S043C";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -13,6 +13,7 @@ spiRam=true
spiRamMode=OCT
spiRamSpeed=80M
esptoolFlashFreq=80M
bluetooth=true
[display]
size=4.3"

View File

@ -13,6 +13,7 @@ spiRamMode=OCT
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=2.8"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
@ -10,6 +11,10 @@
compatible = "root";
model = "Elecrow CrowPanel Advance 2.8";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -13,6 +13,7 @@ spiRamMode=OCT
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=3.5"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
@ -10,6 +11,10 @@
compatible = "root";
model = "Elecrow CrowPanel Advance 3.5";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -13,6 +13,7 @@ spiRamMode=OCT
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=5"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
@ -10,6 +11,10 @@
compatible = "root";
model = "Elecrow CrowPanel Advance 5.0";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -13,6 +13,7 @@ spiRamMode=OCT
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=5.0"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
@ -10,6 +11,10 @@
compatible = "root";
model = "Elecrow CrowPanel Basic 5.0";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -12,6 +12,7 @@ spiRam=true
spiRamMode=OCT
spiRamSpeed=200M
esptoolFlashFreq=80M
bluetooth=true
[display]
size=7"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.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>
@ -17,6 +18,10 @@
compatible = "root";
model = "Guition JC1060P470C-I-W-Y";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <57>;

View File

@ -13,6 +13,7 @@ spiRamMode=OCT
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=3.5"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.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>
@ -11,6 +12,10 @@
compatible = "root";
model = "Guition JC3248W535C";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -12,6 +12,7 @@ spiRam=true
spiRamMode=OCT
spiRamSpeed=80M
esptoolFlashFreq=80M
bluetooth=true
[display]
size=5"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.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>
@ -11,6 +12,10 @@
compatible = "root";
model = "Guition JC8048W550C";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -13,6 +13,7 @@ flashSize=8MB
spiRam=false
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=0.96"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
@ -8,6 +9,10 @@
compatible = "root";
model = "Heltec WiFi LoRa 32 V3";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -33,7 +33,8 @@ std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
.touch = createTouch(),
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
.resetPin = GPIO_NUM_NC,
.lvglSwapBytes = false
.lvglSwapBytes = false,
.buffSpiram = true
};
auto spi_configuration = std::make_shared<St7789Display::SpiConfiguration>(St7789Display::SpiConfiguration {

View File

@ -1,10 +1,10 @@
/dts-v1/;
#include <tactility/bindings/root.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_ble.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_uart.h>
@ -25,7 +25,7 @@
i2c_internal: i2c0 {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_0>;
clock-frequency = <400000>;
clock-frequency = <100000>;
pin-sda = <&gpio0 18 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 8 GPIO_FLAG_NONE>;
};

View File

@ -14,6 +14,7 @@ spiRamMode=OCT
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=1.9"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
@ -9,6 +10,10 @@
compatible = "root";
model = "LilyGO T-Display S3";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -13,6 +13,7 @@ flashSize=16MB
spiRam=false
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=0.96"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_sdmmc.h>
@ -11,6 +12,10 @@
compatible = "root";
model = "LilyGO T-Dongle S3";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -12,7 +12,7 @@ static error_t stop() {
return ERROR_NONE;
}
struct Module lilygo_thmi_s3_module = {
struct Module lilygo_thmi_module = {
.name = "lilygo-thmi",
.start = start,
.stop = stop,

View File

@ -13,6 +13,7 @@ spiRamMode=OCT
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=2.8"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_sdmmc.h>
@ -10,6 +11,10 @@
compatible = "root";
model = "LilyGO T-HMI";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -6,7 +6,7 @@
extern "C" {
extern struct Module device_module;
extern struct Module lilygo_tlora_pager_module;
static int start(Device* device) {
return 0;
@ -23,7 +23,7 @@ Driver tlora_pager_driver = {
.stop_device = stop,
.api = nullptr,
.device_type = nullptr,
.owner = &device_module,
.owner = &lilygo_tlora_pager_module,
.internal = nullptr
};

View File

@ -14,6 +14,7 @@ spiRamMode=AUTO
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=40M
bluetooth=true
[display]
size=2.33"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <bindings/tlora_pager.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>
@ -12,6 +13,10 @@
compatible = "lilygo,tlora-pager";
model = "LilyGO T-Lora Pager";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -11,6 +11,7 @@ flashSize=8MB
spiRam=false
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=1.14"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.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>
@ -13,6 +14,10 @@
compatible = "root";
model = "M5Stack Cardputer Adv";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -11,6 +11,7 @@ flashSize=8MB
spiRam=false
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=1.14"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.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>
@ -12,6 +13,10 @@
compatible = "root";
model = "M5Stack Cardputer";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -6,13 +6,16 @@
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto configuration = std::make_unique<Ft6x36Touch::Configuration>(
I2C_NUM_0,
GPIO_NUM_39,
LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION
LCD_VERTICAL_RESOLUTION,
false,
false,
false,
GPIO_NUM_NC,
GPIO_NUM_39
);
auto touch = std::make_shared<Ft6x36Touch>(std::move(configuration));
return std::reinterpret_pointer_cast<tt::hal::touch::TouchDevice>(touch);
return std::make_shared<Ft6x36Touch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {

View File

@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port ILI934x FT5x06 AXP2101 AW9523 driver vfs fatfs
REQUIRES Tactility esp_lvgl_port ILI934x FT6x36 AXP2101 AW9523 driver vfs fatfs
)

View File

@ -1,7 +1,7 @@
#include "Display.h"
#include <Axp2101.h>
#include <Ft5x06Touch.h>
#include <Ft6x36Touch.h>
#include <Ili934xDisplay.h>
#include <Tactility/Logger.h>
#include <Tactility/hal/i2c/I2c.h>
@ -17,14 +17,16 @@ static void setBacklightDuty(uint8_t backlightDuty) {
}
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto configuration = std::make_unique<Ft5x06Touch::Configuration>(
auto configuration = std::make_unique<Ft6x36Touch::Configuration>(
I2C_NUM_0,
LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION
319,//LCD_HORIZONTAL_RESOLUTION,
239,//LCD_VERTICAL_RESOLUTION,
false,
false,
false
);
auto touch = std::make_shared<Ft5x06Touch>(std::move(configuration));
return std::reinterpret_pointer_cast<tt::hal::touch::TouchDevice>(touch);
return std::make_shared<Ft6x36Touch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {

View File

@ -13,6 +13,7 @@ spiRamMode=QUAD
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=2"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.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>
@ -14,6 +15,10 @@
compatible = "root";
model = "M5Stack CoreS3";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -14,6 +14,7 @@ spiRamMode=OPI
spiRamSpeed=80M
esptoolFlashFreq=80M
tinyUsb=true
bluetooth=true
[display]
size=4.7"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
@ -11,6 +12,10 @@
compatible = "root";
model = "M5Stack PaperS3";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -0,0 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port ILI934x FT6x36 AXP2101 AW9523 driver vfs fatfs ina226-module py32ioexpander-module
)

View File

@ -0,0 +1,254 @@
#include "devices/Display.h"
#include "devices/Power.h"
#include "devices/SdCard.h"
#include <driver/gpio.h>
#include <tactility/device.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h>
#include <drivers/py32ioexpander.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <Tactility/hal/Configuration.h>
#include <Axp2101Power.h>
#include <Axp2101.h>
using namespace tt::hal;
static const auto* TAG = "StackChan";
// ---------------------------------------------------------------------------
// I2C addresses
// ---------------------------------------------------------------------------
static constexpr uint8_t AXP2101_ADDR = 0x34;
static constexpr uint8_t AW9523B_ADDR = 0x58;
static constexpr uint8_t AW88298_ADDR = 0x36;
static constexpr uint8_t ES7210_ADDR = 0x40;
// ---------------------------------------------------------------------------
// AW9523B GPIO expander — same wiring as CoreS3
// ---------------------------------------------------------------------------
// P0 pins: 0=touch reset, 1=bus out enable, 2=AW88298 reset, 4=SD card, 5=USB OTG
// P1 pins: 0=cam reset, 1=LCD reset, 7=boost enable (SY7088)
static constexpr uint8_t AW9523B_CTL_REG = 0x11; // P0 push-pull mode
static constexpr uint8_t AW9523B_P0_REG = 0x02;
static constexpr uint8_t AW9523B_P1_REG = 0x03;
static bool initGpioExpander(::Device* i2c) {
// P0: touch, bus enable, AW88298 reset, SD card switch
constexpr uint8_t p0 = (1U << 0U) | (1U << 1U) | (1U << 2U) | (1U << 4U);
// P1: LCD reset, boost enable
constexpr uint8_t p1 = (1U << 1U) | (1U << 7U);
// Set P0 to push-pull mode
if (i2c_controller_register8_set(i2c, AW9523B_ADDR, AW9523B_CTL_REG, 0x10, pdMS_TO_TICKS(1000)) != ERROR_NONE) {
LOG_E(TAG, "AW9523B: Failed to set CTL");
return false;
}
if (i2c_controller_register8_set(i2c, AW9523B_ADDR, AW9523B_P0_REG, p0, pdMS_TO_TICKS(1000)) != ERROR_NONE) {
LOG_E(TAG, "AW9523B: Failed to set P0");
return false;
}
if (i2c_controller_register8_set(i2c, AW9523B_ADDR, AW9523B_P1_REG, p1, pdMS_TO_TICKS(1000)) != ERROR_NONE) {
LOG_E(TAG, "AW9523B: Failed to set P1");
return false;
}
return true;
}
// ---------------------------------------------------------------------------
// AXP2101 power management — same voltage rails as CoreS3
// ---------------------------------------------------------------------------
static bool initPowerControl(::Device* i2c) {
// Source: https://github.com/m5stack/M5Unified/blob/b8cfec7fed046242da7f7b8024a4e92004a51ff7/src/utility/Power_Class.cpp#L64
static constexpr uint8_t reg_data[] = {
0x90U, 0xBFU, // LDOS ON/OFF control 0 (backlight)
0x92U, 18U - 5U, // ALDO1 = 1.8V (AW88298)
0x93U, 33U - 5U, // ALDO2 = 3.3V (ES7210)
0x94U, 33U - 5U, // ALDO3 = 3.3V (camera)
0x95U, 33U - 5U, // ALDO4 = 3.3V (TF card)
0x27U, 0x00U, // PowerKey Hold=1sec / PowerOff=4sec
0x69U, 0x11U, // CHGLED setting
0x10U, 0x30U, // PMU common config
0x30U, 0x0FU, // ADC enabled
};
if (i2c_controller_write_register_array(i2c, AXP2101_ADDR, reg_data, sizeof(reg_data), pdMS_TO_TICKS(1000)) != ERROR_NONE) {
LOG_E(TAG, "AXP2101: Failed to set registers");
return false;
}
return true;
}
// ---------------------------------------------------------------------------
// AW88298 speaker amplifier
// Called once in initBoot() at 16kHz. AW88298 default state after hardware reset
// is sufficient for SfxEngine. Sequence mirrors M5Unified _speaker_enabled_cb_cores3().
// NOTE: 44100Hz (AudioPlayer/MusicPlayer) needs esp_codec_dev integration — see memory notes.
// ---------------------------------------------------------------------------
static bool initSpeaker(::Device* i2c, uint32_t sample_rate_hz) {
// M5Unified rate table: (sample_rate + 1102) / 2205 steps, first entry >= result
static constexpr uint8_t rate_tbl[] = { 4, 5, 6, 8, 10, 11, 15, 20, 22, 44 };
size_t reg06_idx = 0;
size_t rate = (sample_rate_hz + 1102) / 2205;
while (rate > rate_tbl[reg06_idx] && ++reg06_idx < sizeof(rate_tbl)) {}
if (reg06_idx >= sizeof(rate_tbl)) {
reg06_idx = sizeof(rate_tbl) - 1; // clamp to max supported rate
}
// 0x14C0: M5Unified's upper byte for CoreS3, I2SBCK=0 (BCK 16*2)
const uint16_t reg06 = static_cast<uint16_t>(0x14C0U | reg06_idx);
// Hardware reset AW88298 via AW9523B P0 bit 2: pull LOW (reset), then HIGH (release).
if (i2c_controller_register8_reset_bits(i2c, AW9523B_ADDR, AW9523B_P0_REG, 0b00000100, pdMS_TO_TICKS(100)) != ERROR_NONE) {
LOG_E(TAG, "AW9523B: failed to assert AW88298 reset");
return false;
}
vTaskDelay(pdMS_TO_TICKS(10));
if (i2c_controller_register8_set_bits(i2c, AW9523B_ADDR, AW9523B_P0_REG, 0b00000100, pdMS_TO_TICKS(100)) != ERROR_NONE) {
LOG_E(TAG, "AW9523B: failed to release AW88298 reset");
return false;
}
vTaskDelay(pdMS_TO_TICKS(50));
// Exact sequence from M5Unified _speaker_enabled_cb_cores3() — no I2C software reset.
struct { uint8_t reg; uint16_t val; } regs[] = {
{ 0x61, 0x0673 }, // boost mode disabled
{ 0x04, 0x4040 }, // I2SEN=1, AMPPD=0, PWDN=0
{ 0x05, 0x0008 }, // RMSE=0, HAGCE=0, HDCCE=0, HMUTE=0
{ 0x06, reg06 }, // I2S mode + sample rate
{ 0x0C, 0x3064 }, // volume -24dB
};
for (auto& r : regs) {
if (i2c_controller_register16be_set(i2c, AW88298_ADDR, r.reg, r.val, pdMS_TO_TICKS(100)) != ERROR_NONE) {
LOG_E(TAG, "AW88298: failed reg 0x%02X", r.reg);
return false;
}
}
LOG_I(TAG, "AW88298 initialized (%luHz, reg06=0x%04X)", (unsigned long)sample_rate_hz, reg06);
return true;
}
// ---------------------------------------------------------------------------
// ES7210 microphone ADC
// Source: https://github.com/m5stack/M5Unified
// ---------------------------------------------------------------------------
static bool initMicrophone(::Device* i2c) {
static constexpr uint8_t reg_data[] = {
0x00, 0x41, // RESET_CTL
0x01, 0x1F, // CLK_ON_OFF (initial)
0x06, 0x00, // DIGITAL_PDN
0x07, 0x20, // ADC_OSR
0x08, 0x10, // MODE_CFG
0x09, 0x30, // TCT0_CHPINI
0x0A, 0x30, // TCT1_CHPINI
0x20, 0x0A, // ADC34_HPF2
0x21, 0x2A, // ADC34_HPF1
0x22, 0x0A, // ADC12_HPF2
0x23, 0x2A, // ADC12_HPF1
0x02, 0xC1,
0x04, 0x01,
0x05, 0x00,
0x11, 0x60,
0x40, 0x42, // ANALOG_SYS
0x41, 0x70, // MICBIAS12
0x42, 0x70, // MICBIAS34
0x43, 0x1B, // MIC1_GAIN
0x44, 0x1B, // MIC2_GAIN
0x45, 0x00, // MIC3_GAIN
0x46, 0x00, // MIC4_GAIN
0x47, 0x00, // MIC1_LP
0x48, 0x00, // MIC2_LP
0x49, 0x00, // MIC3_LP
0x4A, 0x00, // MIC4_LP
0x4B, 0x00, // MIC12_PDN
0x4C, 0xFF, // MIC34_PDN
0x01, 0x14, // CLK_ON_OFF (final)
};
if (i2c_controller_write_register_array(i2c, ES7210_ADDR, reg_data, sizeof(reg_data), pdMS_TO_TICKS(1000)) != ERROR_NONE) {
LOG_E(TAG, "ES7210: Failed to set registers");
return false;
}
return true;
}
// ---------------------------------------------------------------------------
// initBoot — called before device tree is started
// ---------------------------------------------------------------------------
static std::shared_ptr<Axp2101> axp2101;
bool initBoot() {
auto* i2c = device_find_by_name("i2c_internal");
if (i2c == nullptr) {
LOG_E(TAG, "i2c_internal not found");
return false;
}
// Boost enable via AXP2101 before GPIO expander init (same as CoreS3)
// AW9523B P1 bit 7 = SY7088 boost enable — set after AXP2101 is configured
if (!initPowerControl(i2c)) {
LOG_E(TAG, "AXP2101 init failed");
return false;
}
if (!initGpioExpander(i2c)) {
LOG_E(TAG, "AW9523B init failed");
return false;
}
// AW88298 default state after reset is sufficient for SfxEngine (16kHz).
// Full 44100Hz support requires esp_codec_dev integration (see memory notes).
if (!initSpeaker(i2c, 16000)) {
LOG_W(TAG, "AW88298 init failed (non-fatal)");
}
if (!initMicrophone(i2c)) {
LOG_W(TAG, "ES7210 init failed (non-fatal)");
}
// Boot LED pattern — confirms PY32IOExpander is working.
// PY32 pin 0 = servo VM_EN (output), pin 13 = WS2812C data line.
auto* py32 = device_find_by_name("py32");
if (py32 != nullptr) {
static constexpr uint8_t LED_COUNT = 12;
static constexpr uint8_t COLORS[][3] = {
{ 255, 0, 0 }, // red
{ 0, 255, 0 }, // green
{ 0, 0, 255 }, // blue
};
py32_led_set_count(py32, LED_COUNT);
for (auto& c : COLORS) {
for (uint8_t i = 0; i < LED_COUNT; i++) {
py32_led_set_color(py32, i, c[0], c[1], c[2]);
}
py32_led_refresh(py32);
vTaskDelay(pdMS_TO_TICKS(150));
}
py32_led_disable(py32);
} else {
LOG_W(TAG, "py32 not found — LED boot pattern skipped");
}
// Keep Axp2101 C++ wrapper alive for Axp2101Power (backlight + battery)
axp2101 = std::make_shared<Axp2101>(I2C_NUM_0);
return true;
}
// ---------------------------------------------------------------------------
// Device list
// ---------------------------------------------------------------------------
static DeviceVector createDevices() {
return {
axp2101,
std::make_shared<Axp2101Power>(axp2101),
createPower(),
createSdCard(),
createDisplay(),
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};

View File

@ -0,0 +1,58 @@
#include "Display.h"
#include <Axp2101.h>
#include <Ft6x36Touch.h>
#include <Ili934xDisplay.h>
#include <Tactility/Logger.h>
#include <Tactility/hal/i2c/I2c.h>
static const auto LOGGER = tt::Logger("StackChanDisplay");
static void setBacklightDuty(uint8_t backlightDuty) {
const uint8_t voltage = 20 + ((8 * backlightDuty) / 255); // [0b00000, 0b11100]
if (!tt::hal::i2c::masterWriteRegister(I2C_NUM_0, AXP2101_ADDRESS, 0x99, &voltage, 1, 1000)) {
LOGGER.error("Failed to set display backlight voltage");
}
}
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto configuration = std::make_unique<Ft6x36Touch::Configuration>(
I2C_NUM_0,
319,//LCD_HORIZONTAL_RESOLUTION,
239,//LCD_VERTICAL_RESOLUTION,
false,
false,
false
);
return std::make_shared<Ft6x36Touch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
Ili934xDisplay::Configuration panel_configuration = {
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
.verticalResolution = LCD_VERTICAL_RESOLUTION,
.gapX = 0,
.gapY = 0,
.swapXY = false,
.mirrorX = false,
.mirrorY = false,
.invertColor = true,
.swapBytes = true,
.bufferSize = LCD_BUFFER_SIZE,
.touch = createTouch(),
.backlightDutyFunction = ::setBacklightDuty,
.resetPin = GPIO_NUM_NC,
.rgbElementOrder = LCD_RGB_ELEMENT_ORDER_BGR
};
auto spi_configuration = std::make_shared<Ili934xDisplay::SpiConfiguration>(Ili934xDisplay::SpiConfiguration {
.spiHostDevice = LCD_SPI_HOST,
.csPin = LCD_PIN_CS,
.dcPin = LCD_PIN_DC,
.pixelClockFrequency = 40'000'000,
.transactionQueueDepth = 10
});
return std::make_shared<Ili934xDisplay>(panel_configuration, spi_configuration, true);
}

View File

@ -0,0 +1,17 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <driver/gpio.h>
#include <driver/spi_common.h>
// Display
constexpr auto LCD_SPI_HOST = SPI2_HOST;
constexpr auto LCD_PIN_CS = GPIO_NUM_3;
constexpr auto LCD_PIN_DC = GPIO_NUM_35;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 320;
constexpr auto LCD_VERTICAL_RESOLUTION = 240;
constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10;
constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT;
constexpr auto LCD_SPI_TRANSFER_SIZE_LIMIT = LCD_BUFFER_SIZE * LV_COLOR_DEPTH / 8;
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -0,0 +1,86 @@
#include "Power.h"
#include <Tactility/hal/power/PowerDevice.h>
#include <drivers/ina226.h>
#include <tactility/device.h>
#include <tactility/log.h>
using namespace tt::hal::power;
static constexpr auto* TAG = "StackChanPower";
// 1S Li-ion cell (550 mAh): 3.2V (empty) 4.2V (full)
static constexpr float MIN_BATTERY_VOLTAGE_MV = 3200.0f;
static constexpr float MAX_BATTERY_VOLTAGE_MV = 4200.0f;
class StackChanPower final : public PowerDevice {
public:
explicit StackChanPower(::Device* ina226Device) : ina226(ina226Device) {}
std::string getName() const override { return "M5Stack StackChan 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:
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, &amps) != ERROR_NONE) return false;
data.valueAsInt32 = static_cast<int32_t>(amps * 1000.0f);
return true;
}
default:
return false;
}
}
private:
::Device* ina226;
};
std::shared_ptr<PowerDevice> createPower() {
auto* ina226 = device_find_by_name("ina226");
if (ina226 == nullptr) {
LOG_E(TAG, "ina226 device not found");
}
return std::make_shared<StackChanPower>(ina226);
}

View File

@ -0,0 +1,6 @@
#pragma once
#include <memory>
#include <Tactility/hal/power/PowerDevice.h>
std::shared_ptr<tt::hal::power::PowerDevice> createPower();

View File

@ -0,0 +1,30 @@
#include "SdCard.h"
#include <tactility/device.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/hal/sdcard/SpiSdCardDevice.h>
constexpr auto STACKCHAN_SDCARD_PIN_CS = GPIO_NUM_4;
constexpr auto STACKCHAN_LCD_PIN_CS = GPIO_NUM_3;
using tt::hal::sdcard::SpiSdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard() {
auto configuration = std::make_unique<SpiSdCardDevice::Config>(
STACKCHAN_SDCARD_PIN_CS,
GPIO_NUM_NC,
GPIO_NUM_NC,
GPIO_NUM_NC,
SdCardDevice::MountBehaviour::AtBoot,
tt::lvgl::getSyncLock(),
std::vector { STACKCHAN_LCD_PIN_CS }
);
auto* spi_controller = device_find_by_name("spi0");
check(spi_controller, "spi0 not found");
return std::make_shared<SpiSdCardDevice>(
std::move(configuration),
spi_controller
);
}

View File

@ -0,0 +1,7 @@
#pragma once
#include "Tactility/hal/sdcard/SdCardDevice.h"
using tt::hal::sdcard::SdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard();

View File

@ -0,0 +1,21 @@
#include <tactility/module.h>
extern "C" {
static error_t start() {
return ERROR_NONE;
}
static error_t stop() {
return ERROR_NONE;
}
struct Module m5stack_stackchan_module = {
.name = "m5stack-stackchan",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}

View File

@ -0,0 +1,24 @@
[general]
vendor=M5Stack
name=StackChan
[apps]
launcherAppId=Launcher
[hardware]
target=ESP32S3
flashSize=16MB
spiRam=true
spiRamMode=QUAD
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=2"
shape=rectangle
dpi=200
[lvgl]
colorDepth=16

View File

@ -0,0 +1,7 @@
dependencies:
- Platforms/platform-esp32
- Drivers/bmi270-module
- Drivers/bm8563-module
- Drivers/ina226-module
- Drivers/py32ioexpander-module
dts: m5stack,stackchan.dts

View File

@ -0,0 +1,123 @@
/dts-v1/;
#include <tactility/bindings/root.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_spi.h>
#include <tactility/bindings/esp32_uart.h>
#include <bindings/bmi270.h>
#include <bindings/bm8563.h>
#include <bindings/ina226.h>
#include <bindings/py32ioexpander.h>
// Reference: https://docs.m5stack.com/en/StackChan
/ {
compatible = "root";
model = "M5Stack StackChan";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;
};
i2c_internal {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_0>;
clock-frequency = <100000>;
pin-sda = <&gpio0 12 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 11 GPIO_FLAG_NONE>;
// PY32L020 body IO expander — controls WS2812C LED ring (12 LEDs)
py32 {
compatible = "m5stack,py32ioexpander";
reg = <0x6F>;
};
bmi270 {
compatible = "bosch,bmi270";
reg = <0x69>;
};
bm8563 {
compatible = "belling,bm8563";
reg = <0x51>;
};
ina226 {
compatible = "ti,ina226";
reg = <0x41>;
shunt-milliohms = <10>;
};
// AXP2101 PMIC @ 0x34 — initialized manually in initBoot()
// AW9523B GPIO expander @ 0x58 — initialized manually in initBoot() (same as CoreS3)
// AW88298 speaker amp @ 0x36 — initialized manually in initBoot()
// ES7210 microphone ADC @ 0x40 — initialized manually in initBoot()
// FT6336U capacitive touch @ 0x38 — used by Display driver (FT6x36 library)
// TODO: Si12T 3-zone head touch @ 0x68 — INT active-low, 10kΩ pull-up to 3.3V; driver not yet implemented
// TODO: GC0308 camera @ 0x21 — requires i2c_master driver, not yet available
// TODO: LTR-553ALS-WA proximity/light @ 0x23 — no driver yet
// TODO: BMM150 magnetometer @ 0x10 — accessible only via BMI270 aux I2C
};
i2c_port_a {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_1>;
clock-frequency = <400000>;
pin-sda = <&gpio0 2 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 1 GPIO_FLAG_NONE>;
};
i2c_port_b {
compatible = "espressif,esp32-i2c";
status = "disabled";
port = <I2C_NUM_1>;
clock-frequency = <400000>;
pin-sda = <&gpio0 9 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 8 GPIO_FLAG_NONE>;
};
i2c_port_c {
compatible = "espressif,esp32-i2c";
status = "disabled";
port = <I2C_NUM_1>;
clock-frequency = <400000>;
pin-sda = <&gpio0 18 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 17 GPIO_FLAG_NONE>;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
pin-mosi = <&gpio0 37 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 35 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 36 GPIO_FLAG_NONE>;
};
// AW88298 speaker + ES7210 microphone
i2s0 {
compatible = "espressif,esp32-i2s";
port = <I2S_NUM_0>;
pin-bclk = <&gpio0 34 GPIO_FLAG_NONE>;
pin-ws = <&gpio0 33 GPIO_FLAG_NONE>;
pin-data-out = <&gpio0 13 GPIO_FLAG_NONE>;
pin-data-in = <&gpio0 14 GPIO_FLAG_NONE>;
pin-mclk = <&gpio0 0 GPIO_FLAG_NONE>;
};
// TODO: Servo UART (SCS9009, 1 Mbaud) — TX=GPIO6, RX=GPIO7
uart_port_a: uart1 {
compatible = "espressif,esp32-uart";
status = "disabled";
port = <UART_NUM_1>;
pin-tx = <&gpio0 1 GPIO_FLAG_NONE>;
pin-rx = <&gpio0 2 GPIO_FLAG_NONE>;
};
};

View File

@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight ButtonControl m5pm1-module
REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight ButtonControl m5pm1-module vfs fatfs
)

View File

@ -1,14 +1,101 @@
#include "devices/Display.h"
#include "devices/SdCard.h"
#include "devices/Power.h"
#include <driver/gpio.h>
#include <tactility/device.h>
#include <tactility/drivers/i2c_controller.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";
static constexpr uint8_t ES8311_I2C_ADDR = 0x18;
static error_t initSound(::Device* i2c_controller) {
// Init data from M5Unified:
// https://github.com/m5stack/M5Unified/blob/master/src/M5Unified.cpp#L454
static constexpr uint8_t ENABLED_BULK_DATA[] = {
0x00, 0x80, // 0x00 RESET/ CSM POWER ON
0x01, 0x3F, // 0x01 CLOCK_MANAGER/ use MCLK pin (external), all clocks on
0x02, 0x00, // 0x02 CLOCK_MANAGER/ pre_div=1 pre_multi=1 (256x MCLK from ESP32 is correct ratio)
0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry
0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default
0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default
0x32, 0xBF, // 0x32 DAC/ DAC volume (0xBF == ±0 dB )
0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default
};
error_t error = i2c_controller_write_register_array(
i2c_controller,
ES8311_I2C_ADDR,
ENABLED_BULK_DATA,
sizeof(ENABLED_BULK_DATA),
pdMS_TO_TICKS(1000)
);
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to enable ES8311: %s", error_to_string(error));
return error;
}
// Enable speaker amp via M5PM1 driver
auto* m5pm1 = device_find_by_name("m5pm1");
if (m5pm1 != nullptr) {
m5pm1_set_speaker_enable(m5pm1, true);
} else {
LOG_W(TAG, "m5pm1 not found — speaker amp not enabled");
}
return ERROR_NONE;
}
static error_t initMicrophone(::Device* i2c_controller) {
// Init data from M5Unified:
// https://github.com/m5stack/M5Unified/blob/master/src/M5Unified.cpp#L842
static constexpr uint8_t ENABLED_BULK_DATA[] = {
0x00, 0x80, // 0x00 RESET/ CSM POWER ON
0x01, 0x3F, // 0x01 CLOCK_MANAGER/ use MCLK pin (external), all clocks on
0x02, 0x00, // 0x02 CLOCK_MANAGER/ pre_div=1 pre_multi=1 (256x MCLK from ESP32 is correct ratio)
0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry
0x0E, 0x02, // 0x0E SYSTEM/ : Enable analog PGA, enable ADC modulator
0x14, 0x10, // ES8311_ADC_REG14 : select Mic1p-Mic1n / PGA GAIN (minimum)
0x17, 0xFF, // ES8311_ADC_REG17 : ADC_VOLUME (MAXGAIN) // (0xBF == ± 0 dB )
0x1C, 0x6A, // ES8311_ADC_REG1C : ADC Equalizer bypass, cancel DC offset in digital domain
};
error_t error = i2c_controller_write_register_array(
i2c_controller,
ES8311_I2C_ADDR,
ENABLED_BULK_DATA,
sizeof(ENABLED_BULK_DATA),
pdMS_TO_TICKS(1000)
);
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to enable ES8311: %s", error_to_string(error));
return error;
}
return ERROR_NONE;
}
bool initBoot() {
auto* i2c_internal = device_find_by_name("i2c_internal");
check(i2c_internal, "i2c_internal not found");
error_t error = initSound(i2c_internal);
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to enable ES8311 speaker");
}
error = initMicrophone(i2c_internal);
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to enable ES8311 microphone");
}
return driver::pwmbacklight::init(GPIO_NUM_38, 512);
}
@ -16,7 +103,8 @@ static DeviceVector createDevices() {
return {
createPower(),
ButtonControl::createTwoButtonControl(11, 12), // top button, side button
createDisplay()
createDisplay(),
createSdCard()
};
}

View File

@ -0,0 +1,30 @@
#include "SdCard.h"
#include <tactility/device.h>
#include <Tactility/hal/sdcard/SpiSdCardDevice.h>
constexpr auto SDCARD_SPI_HOST = SPI3_HOST;
constexpr auto SDCARD_PIN_CS = GPIO_NUM_7;
using tt::hal::sdcard::SpiSdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard() {
auto configuration = std::make_unique<SpiSdCardDevice::Config>(
SDCARD_PIN_CS,
GPIO_NUM_NC,
GPIO_NUM_NC,
GPIO_NUM_NC,
SdCardDevice::MountBehaviour::AtBoot,
nullptr,
std::vector<gpio_num_t>(),
SDCARD_SPI_HOST
);
auto* spi_controller = device_find_by_name("spi1");
check(spi_controller, "spi1 not found");
return std::make_shared<SpiSdCardDevice>(
std::move(configuration),
spi_controller
);
}

View File

@ -0,0 +1,8 @@
#pragma once
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <memory>
using tt::hal::sdcard::SdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard();

View File

@ -4,7 +4,6 @@ name=StickS3
[apps]
launcherAppId=Launcher
autoStartAppId=ApWebServer
[hardware]
target=ESP32S3
@ -14,6 +13,7 @@ spiRamMode=OCT
spiRamSpeed=80M
esptoolFlashFreq=80M
tinyUsb=true
bluetooth=true
[display]
size=1.14"

View File

@ -1,5 +1,6 @@
/dts-v1/;
#include <tactility/bindings/root.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>
@ -12,6 +13,10 @@
compatible = "root";
model = "M5Stack StickS3";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;
@ -50,6 +55,15 @@
pin-sclk = <&gpio0 40 GPIO_FLAG_NONE>;
};
//DIY SD Card - https://wiki.bruce.computer/wiring-diagrams/m5sticks3/sd-card/
spi1 {
compatible = "espressif,esp32-spi";
host = <SPI3_HOST>;
pin-mosi = <&gpio0 6 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 4 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 5 GPIO_FLAG_NONE>;
};
// Speaker and microphone (ES8311)
i2s0 {
compatible = "espressif,esp32-i2s";

View File

@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c esp_lcd_st7123 esp_lcd_touch_st7123 GT911 PwmBacklight driver vfs fatfs
REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c esp_lcd_st7123 esp_lcd_touch_st7123 GT911 PwmBacklight driver vfs fatfs ina226-module
)

View File

@ -1,5 +1,6 @@
#include "devices/Display.h"
#include "devices/SdCard.h"
#include "devices/Power.h"
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/i2c_controller.h>
@ -13,6 +14,7 @@ static constexpr auto* TAG = "Tab5";
static DeviceVector createDevices() {
return {
createPower(),
createDisplay(),
createSdCard(),
};
@ -195,6 +197,57 @@ static error_t initSound(::Device* i2c_controller, ::Device* io_expander0 = null
return ERROR_NONE;
}
static error_t initMicrophone(::Device* i2c_controller) {
// ES7210 quad-channel microphone ADC at 0x40.
// Register sequence from M5Unified (M5Unified.cpp, _microphone_enabled_cb_tab5).
// Configures 4-slot TDM output at 48kHz/16-bit with MIC1+MIC2 active and MICBIAS enabled.
static constexpr uint8_t ES7210_I2C_ADDR = 0x40;
static constexpr uint8_t INIT_DATA[] = {
0x00, 0xFF, // RESET_CTL: full reset
0x00, 0x41, // RESET_CTL: release reset, keep CSM active
0x01, 0x1F, // CLK_ON_OFF: enable all clocks
0x06, 0x00, // DIGITAL_PDN: power up all digital blocks
0x07, 0x20, // ADC_OSR: OSR=256
0x08, 0x10, // MODE_CFG: I2S slave, TDM mode
0x09, 0x30, // TCT0_CHPINI: chopper init period
0x0A, 0x30, // TCT1_CHPINI
0x20, 0x0A, // ADC34_HPF2
0x21, 0x2A, // ADC34_HPF1
0x22, 0x0A, // ADC12_HPF2
0x23, 0x2A, // ADC12_HPF1
0x02, 0xC1, // CLK_CTRL: MCLK from I2S, PLL off
0x04, 0x01, // SDPOUT_CTL1: TDM output enable
0x05, 0x00, // SDPOUT_CTL0
0x11, 0x60, // DBIAS: adjust reference voltage for P4
0x40, 0x42, // ANALOG_SYS: enable analog supply
0x41, 0x70, // MICBIAS12: enable MICBIAS for MIC1+MIC2
0x42, 0x70, // MICBIAS34: enable MICBIAS for MIC3+MIC4
0x43, 0x1B, // MIC1_GAIN: +30 dB
0x44, 0x1B, // MIC2_GAIN: +30 dB
0x45, 0x00, // MIC3_GAIN: AEC ref, no gain
0x46, 0x00, // MIC4_GAIN: AEC ref, no gain
0x47, 0x00, // MIC1_LP
0x48, 0x00, // MIC2_LP
0x49, 0x00, // MIC3_LP
0x4A, 0x00, // MIC4_LP
0x4B, 0x00, // MIC12_PDN: power up MIC1+MIC2
0x4C, 0xFF, // MIC34_PDN: keep MIC3+MIC4 in power-down (AEC ref not needed)
0x01, 0x14, // CLK_ON_OFF: final clock config
};
error_t error = i2c_controller_write_register_array(
i2c_controller,
ES7210_I2C_ADDR,
INIT_DATA,
sizeof(INIT_DATA),
pdMS_TO_TICKS(1000)
);
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to init ES7210: %s", error_to_string(error));
}
return error;
}
static bool initBoot() {
auto* i2c0 = device_find_by_name("i2c0");
check(i2c0, "i2c0 not found");
@ -212,6 +265,11 @@ static bool initBoot() {
LOG_E(TAG, "Failed to enable ES8388");
}
error = initMicrophone(i2c0);
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to init ES7210");
}
return true;
}

View File

@ -0,0 +1,192 @@
#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, &amps) != 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, &amps) != 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);
}

View File

@ -0,0 +1,6 @@
#pragma once
#include <memory>
#include <Tactility/hal/power/PowerDevice.h>
std::shared_ptr<tt::hal::power::PowerDevice> createPower();

View File

@ -1,14 +1,113 @@
#include <tactility/module.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"
constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1;
constexpr auto GPIO_EXP0_PIN_HEADPHONE_DETECT = 7;
constexpr auto HP_DETECT_POLL_MS = 1000;
// hp_detect_timer is only touched from start()/stop(), which are called serially
// by the module manager — no atomic needed for the handle itself.
static TimerHandle_t hp_detect_timer = nullptr;
static std::atomic<Device*> io_expander0_cached { nullptr };
// Flags are written by the timer daemon task and read by start()/stop() — use atomics.
static std::atomic<bool> hp_detect_last { false };
static std::atomic<bool> hp_detect_initialized { false };
static void headphoneDetectCallback(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");
}
}
extern "C" {
static error_t start() {
// Empty for now
if (hp_detect_timer != nullptr) {
LOG_W(TAG, "hp_detect timer already running");
return ERROR_NONE;
}
hp_detect_initialized = false;
hp_detect_last = false;
hp_detect_timer = xTimerCreate("hp_detect", pdMS_TO_TICKS(HP_DETECT_POLL_MS), pdTRUE, nullptr, headphoneDetectCallback);
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;
}
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
if (hp_detect_timer == nullptr) {
return ERROR_NONE;
}
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);
return ERROR_NONE;
}

View File

@ -41,3 +41,9 @@ CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_1=8
CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE=15
# Fixes recent changes to esp_hosted
CONFIG_ESP_HOSTED_USE_MEMPOOL=n
# Performance: larger L2 cache reduces PSRAM stalls for draw/DPI buffers
CONFIG_CACHE_L2_CACHE_256KB=y
# Performance: use P4's PPA (pixel processing accelerator for rotation)
CONFIG_LVGL_PORT_ENABLE_PPA=y
CONFIG_LV_DRAW_BUF_ALIGN=64
CONFIG_LV_DEF_REFR_PERIOD=15

View File

@ -2,5 +2,6 @@ dependencies:
- Platforms/platform-esp32
- Drivers/pi4ioe5v6408-module
- Drivers/bmi270-module
- Drivers/ina226-module
- Drivers/rx8130ce-module
dts: m5stack,tab5.dts

View File

@ -1,12 +1,14 @@
/dts-v1/;
#include <tactility/bindings/root.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_spi.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_uart.h>
#include <bindings/bmi270.h>
#include <bindings/ina226.h>
#include <bindings/pi4ioe5v6408.h>
#include <bindings/rx8130ce.h>
@ -49,6 +51,12 @@
compatible = "epson,rx8130ce";
reg = <0x32>;
};
ina226 {
compatible = "ti,ina226";
reg = <0x41>;
shunt-milliohms = <5>;
};
};
i2c_port_a: i2c1 {
@ -77,4 +85,12 @@
pin-data-in = <&gpio0 28 GPIO_FLAG_NONE>;
pin-mclk = <&gpio0 30 GPIO_FLAG_NONE>;
};
uart_port_a: uart1 {
compatible = "espressif,esp32-uart";
status = "disabled";
port = <UART_NUM_1>;
pin-tx = <&gpio0 53 GPIO_FLAG_NONE>;
pin-rx = <&gpio0 54 GPIO_FLAG_NONE>;
};
};

View File

@ -11,6 +11,7 @@ flashSize=8MB
spiRam=true
spiRamMode=OCT
spiRamSpeed=80M
bluetooth=true
[display]
size=3.5"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
@ -9,6 +10,10 @@
compatible = "root";
model = "unPhone";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -15,6 +15,7 @@ spiRamMode=QUAD
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=1.14"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_sdmmc.h>
@ -11,6 +12,10 @@
compatible = "root";
model = "Waveshare ESP32-S3-Geek";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -15,6 +15,7 @@ spiRamMode=OCT
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=1.3"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
@ -11,6 +12,10 @@
compatible = "root";
model = "Waveshare S3 LCD 1.3";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -15,6 +15,7 @@ spiRamMode=QUAD
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=1.28"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
@ -11,6 +12,10 @@
compatible = "root";
model = "Waveshare S3 Touch LCD 1.28";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -15,6 +15,7 @@ spiRamMode=OCT
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=1.47"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
@ -10,6 +11,10 @@
compatible = "root";
model = "Waveshare S3 Touch LCD 1.47";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -13,6 +13,7 @@ spiRamMode=OCT
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=4.3"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
@ -10,6 +11,10 @@
compatible = "root";
model = "Waveshare ESP32-S3-Touch-LCD-4.3";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

@ -10,13 +10,16 @@ constexpr auto LCD_VERTICAL_RESOLUTION = 480;
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto configuration = std::make_unique<Ft6x36Touch::Configuration>(
I2C_NUM_0,
GPIO_NUM_7,
LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION
LCD_VERTICAL_RESOLUTION,
false,
false,
false,
GPIO_NUM_NC,
GPIO_NUM_7
);
auto touch = std::make_shared<Ft6x36Touch>(std::move(configuration));
return std::static_pointer_cast<tt::hal::touch::TouchDevice>(touch);
return std::make_shared<Ft6x36Touch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {

View File

@ -13,6 +13,7 @@ spiRamMode=QUAD
spiRamSpeed=80M
tinyUsb=true
esptoolFlashFreq=80M
bluetooth=true
[display]
size=3.5"

View File

@ -1,6 +1,7 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
@ -9,6 +10,10 @@
compatible = "root";
model = "Wireless-Tag WT32-SC01 Plus";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;

View File

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

View File

@ -1,134 +1,36 @@
#include "Ft6x36Touch.h"
#include <Ft6x36Touch.h>
#include <Tactility/Logger.h>
#include <esp_lcd_touch_ft6x36.h>
#include <esp_err.h>
#include <esp_lvgl_port.h>
static const auto LOGGER = tt::Logger("FT6x36");
void Ft6x36Touch::touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data) {
auto* touch = (Ft6x36Touch*)lv_indev_get_driver_data(indev);
touch->mutex.lock();
data->point = touch->lastPoint;
data->state = touch->lastState;
touch->mutex.unlock();
bool Ft6x36Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_FT6x36_CONFIG();
return esp_lcd_new_panel_io_i2c(configuration->port, &io_config, &outHandle) == ESP_OK;
}
Ft6x36Touch::Ft6x36Touch(std::unique_ptr<Configuration> inConfiguration) :
configuration(std::move(inConfiguration)) {
nativeTouch = std::make_shared<Ft6TouchDriver>(*this);
bool Ft6x36Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) {
return esp_lcd_touch_new_i2c_ft6x36(ioHandle, &configuration, &panelHandle) == ESP_OK;
}
Ft6x36Touch::~Ft6x36Touch() {
if (driverThread != nullptr && driverThread->getState() != tt::Thread::State::Stopped) {
interruptDriverThread = true;
driverThread->join();
}
}
void Ft6x36Touch::driverThreadMain() {
TPoint point = { .x = 0, .y = 0 };
TEvent event = TEvent::None;
while (!shouldInterruptDriverThread()) {
driver.processTouch();
driver.poll(&point, &event);
if (mutex.lock(100)) {
switch (event) {
case TEvent::TouchStart:
case TEvent::TouchMove:
case TEvent::DragStart:
case TEvent::DragMove:
case TEvent::DragEnd:
lastState = LV_INDEV_STATE_PR;
lastPoint.x = point.x;
lastPoint.y = point.y;
break;
case TEvent::TouchEnd:
lastState = LV_INDEV_STATE_REL;
lastPoint.x = point.x;
lastPoint.y = point.y;
break;
case TEvent::Tap:
case TEvent::None:
break;
}
mutex.unlock();
}
}
}
bool Ft6x36Touch::shouldInterruptDriverThread() const {
bool interrupt = false;
if (mutex.lock(50 / portTICK_PERIOD_MS)) {
interrupt = interruptDriverThread;
mutex.unlock();
}
return interrupt;
}
bool Ft6x36Touch::start() {
LOGGER.info("Start");
if (!driver.begin(FT6X36_DEFAULT_THRESHOLD, configuration->width, configuration->height)) {
LOGGER.error("driver.begin() failed");
return false;
}
mutex.lock();
interruptDriverThread = false;
driverThread = std::make_shared<tt::Thread>("ft6x36", 4096, [this] {
driverThreadMain();
return 0;
});
driverThread->start();
mutex.unlock();
return true;
}
bool Ft6x36Touch::stop() {
LOGGER.info("Stop");
mutex.lock();
interruptDriverThread = true;
mutex.unlock();
driverThread->join();
mutex.lock();
driverThread = nullptr;
mutex.unlock();
return false;
}
bool Ft6x36Touch::startLvgl(lv_display_t* display) {
if (deviceHandle != nullptr) {
return false;
}
deviceHandle = lv_indev_create();
lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_POINTER);
lv_indev_set_driver_data(deviceHandle, this);
lv_indev_set_read_cb(deviceHandle, touchReadCallback);
return true;
}
bool Ft6x36Touch::stopLvgl() {
if (deviceHandle == nullptr) {
return false;
}
lv_indev_delete(deviceHandle);
deviceHandle = nullptr;
return true;
esp_lcd_touch_config_t Ft6x36Touch::createEspLcdTouchConfig() {
return {
.x_max = configuration->xMax,
.y_max = configuration->yMax,
.rst_gpio_num = configuration->pinReset,
.int_gpio_num = configuration->pinInterrupt,
.levels = {
.reset = configuration->pinResetLevel,
.interrupt = configuration->pinInterruptLevel,
},
.flags = {
.swap_xy = configuration->swapXy,
.mirror_x = configuration->mirrorX,
.mirror_y = configuration->mirrorY,
},
.process_coordinates = nullptr,
.interrupt_callback = nullptr,
.user_data = nullptr,
.driver_data = nullptr
};
}

View File

@ -2,12 +2,11 @@
#include <Tactility/hal/touch/TouchDevice.h>
#include <Tactility/TactilityCore.h>
#include <Tactility/Thread.h>
#include <driver/i2c.h>
#include "ft6x36/FT6X36.h"
#include <EspLcdTouch.h>
class Ft6x36Touch final : public tt::hal::touch::TouchDevice {
class Ft6x36Touch final : public EspLcdTouch {
public:
@ -16,78 +15,56 @@ public:
Configuration(
i2c_port_t port,
gpio_num_t pinInterrupt,
uint16_t width,
uint16_t height
uint16_t xMax,
uint16_t yMax,
bool swapXy = false,
bool mirrorX = false,
bool mirrorY = false,
gpio_num_t pinReset = GPIO_NUM_NC,
gpio_num_t pinInterrupt = GPIO_NUM_NC,
unsigned int pinResetLevel = 0,
unsigned int pinInterruptLevel = 0
) : port(port),
xMax(xMax),
yMax(yMax),
swapXy(swapXy),
mirrorX(mirrorX),
mirrorY(mirrorY),
pinReset(pinReset),
pinInterrupt(pinInterrupt),
width(width),
height(height)
pinResetLevel(pinResetLevel),
pinInterruptLevel(pinInterruptLevel)
{}
i2c_port_t port;
uint16_t xMax;
uint16_t yMax;
bool swapXy;
bool mirrorX;
bool mirrorY;
gpio_num_t pinReset;
gpio_num_t pinInterrupt;
uint16_t width;
uint16_t height;
};
unsigned int pinResetLevel;
unsigned int pinInterruptLevel;
};
private:
std::unique_ptr<Configuration> configuration;
lv_indev_t* deviceHandle = nullptr;
FT6X36 driver = FT6X36(configuration->port, configuration->pinInterrupt);
std::shared_ptr<tt::Thread> driverThread;
bool interruptDriverThread = false;
tt::Mutex mutex;
std::shared_ptr<tt::hal::touch::TouchDriver> nativeTouch;
lv_point_t lastPoint = { .x = 0, .y = 0 };
lv_indev_state_t lastState = LV_INDEV_STATE_RELEASED;
bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override;
bool shouldInterruptDriverThread() const;
bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) override;
void driverThreadMain();
static void touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data);
esp_lcd_touch_config_t createEspLcdTouchConfig() override;
public:
explicit Ft6x36Touch(std::unique_ptr<Configuration> inConfiguration);
~Ft6x36Touch() override;
explicit Ft6x36Touch(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
assert(configuration != nullptr);
}
std::string getName() const override { return "FT6x36"; }
std::string getDescription() const override { return "FT6x36 I2C touch driver"; }
bool start() override;
bool stop() override;
bool supportsLvgl() const override { return true; }
bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override;
lv_indev_t* getLvglIndev() override { return deviceHandle; }
class Ft6TouchDriver : public tt::hal::touch::TouchDriver {
public:
const Ft6x36Touch& parent;
Ft6TouchDriver(const Ft6x36Touch& parent) : parent(parent) {}
bool getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* _Nullable strength, uint8_t* pointCount, uint8_t maxPointCount) {
auto lock = parent.mutex.asScopedLock();
lock.lock();
if (parent.lastState == LV_INDEV_STATE_PRESSED) {
*x = parent.lastPoint.x;
*y = parent.lastPoint.y;
*pointCount = 1;
return true;
} else {
*pointCount = 0;
return false;
}
}
};
bool supportsTouchDriver() override { return true; }
std::shared_ptr<tt::hal::touch::TouchDriver> _Nullable getTouchDriver() override { return nativeTouch; }
};

View File

@ -1,378 +0,0 @@
#include "FT6X36.h"
#include "freertos/FreeRTOS.h"
#define CONFIG_FT6X36_DEBUG false
FT6X36 *FT6X36::_instance = nullptr;
static const char *TAG = "i2c-touch";
//Handle indicating I2C is ready to read the touch
SemaphoreHandle_t TouchSemaphore = xSemaphoreCreateBinary();
FT6X36::FT6X36(i2c_port_t port, gpio_num_t interruptPin)
{
_instance = this;
_port = port;
_intPin = interruptPin;
}
// Destructor should detach interrupt to the pin
FT6X36::~FT6X36()
{
if (_intPin >= 0)
gpio_isr_handler_remove((gpio_num_t)_intPin);
}
bool FT6X36::begin(uint8_t threshold, uint16_t width, uint16_t height)
{
_touch_width = width;
_touch_height = height;
if (width == 0 || height ==0) {
ESP_LOGE(TAG,"begin(uint8_t threshold, uint16_t width, uint16_t height) did not receive the width / height so touch cannot be rotation aware");
}
uint8_t data_panel_id;
readRegister8(FT6X36_REG_PANEL_ID, &data_panel_id);
if (data_panel_id != FT6X36_VENDID) {
ESP_LOGE(TAG,"FT6X36_VENDID does not match. Received:0x%x Expected:0x%x\n",data_panel_id,FT6X36_VENDID);
return false;
}
ESP_LOGI(TAG, "\tDevice ID: 0x%02x", data_panel_id);
uint8_t chip_id;
readRegister8(FT6X36_REG_CHIPID, &chip_id);
if (chip_id != FT6206_CHIPID && chip_id != FT6236_CHIPID && chip_id != FT6336_CHIPID) {
ESP_LOGE(TAG,"FT6206_CHIPID does not match. Received:0x%x\n",chip_id);
return false;
}
ESP_LOGI(TAG, "\tFound touch controller with Chip ID: 0x%02x", chip_id);
if (_intPin >= 0)
{
// INT pin triggers the callback function on the Falling edge of the GPIO
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_NEGEDGE; // GPIO_INTR_NEGEDGE repeats always interrupt
io_conf.pin_bit_mask = 1ULL<<_intPin;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_down_en = (gpio_pulldown_t) 0; // disable pull-down mode
io_conf.pull_up_en = (gpio_pullup_t) 1; // pull-up mode
gpio_config(&io_conf);
esp_err_t isr_service = gpio_install_isr_service(0);
printf("ISR trigger install response: 0x%x %s\n", isr_service, (isr_service==0)?"ESP_OK":"");
gpio_isr_handler_add((gpio_num_t)_intPin, isr, (void*) 1);
}
writeRegister8(FT6X36_REG_DEVICE_MODE, 0x00);
writeRegister8(FT6X36_REG_THRESHHOLD, threshold);
writeRegister8(FT6X36_REG_TOUCHRATE_ACTIVE, 0x0E);
return true;
}
void FT6X36::registerTouchHandler(void (*fn)(TPoint point, TEvent e))
{
_touchHandler = fn;
if (CONFIG_FT6X36_DEBUG) printf("Touch handler function registered\n");
}
uint8_t FT6X36::touched()
{
uint8_t data_buf;
esp_err_t ret = readRegister8(FT6X36_REG_NUM_TOUCHES, &data_buf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error reading from device: %s", esp_err_to_name(ret));
}
if (data_buf > 2)
{
data_buf = 0;
}
return data_buf;
}
void FT6X36::loop()
{
processTouch();
}
void IRAM_ATTR FT6X36::isr(void* arg)
{
/* Un-block the interrupt processing task now */
xSemaphoreGive(TouchSemaphore);
//xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}
void FT6X36::processTouch()
{
/* Task move to Block state to wait for interrupt event */
if (_intPin >= 0)
{
if (xSemaphoreTake(TouchSemaphore, portMAX_DELAY) == false) return;
}
readData();
uint8_t n = 0;
TRawEvent event = (TRawEvent)_touchEvent[n];
TPoint point{_touchX[n], _touchY[n]};
switch (event) {
case TRawEvent::PressDown:
_points[0] = point;
_dragMode = false;
// Note: Is in microseconds. Ref https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_timer.html
_touchStartTime = esp_timer_get_time()/1000;
fireEvent(point, TEvent::TouchStart);
break;
case TRawEvent::Contact:
// Dragging makes no sense IMHO. Since the X & Y are not getting updated while dragging
// Dragging && _points[0].aboutEqual(point) - Not used IDEA 2: && (lastEvent == 2)
if (!_dragMode &&
(abs(lastX-_touchX[n]) <= maxDeviation || abs(lastY-_touchY[n])<=maxDeviation) &&
esp_timer_get_time()/1000 - _touchStartTime > 300) {
_dragMode = true;
fireEvent(point, TEvent::DragStart);
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
printf("EV: DragStart\n");
#endif
} else if (_dragMode) {
fireEvent(point, TEvent::DragMove);
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
printf("EV: DragMove\n");
#endif
}
fireEvent(point, TEvent::TouchMove);
// For me the _touchStartTime shouold be set in both PressDown & Contact events, but after Drag detection
_touchStartTime = esp_timer_get_time()/1000;
break;
case TRawEvent::LiftUp:
_points[9] = point;
_touchEndTime = esp_timer_get_time()/1000;
//printf("TIMEDIFF: %lu End: %lu\n", _touchEndTime - _touchStartTime, _touchEndTime);
fireEvent(point, TEvent::TouchEnd);
if (_dragMode) {
fireEvent(point, TEvent::DragEnd);
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
printf("EV: DragEnd\n");
#endif
_dragMode = false;
}
if ( _touchEndTime - _touchStartTime <= 900) {
// Do not get why this: _points[0].aboutEqual(point) (Original library)
fireEvent(point, TEvent::Tap);
_points[0] = {0, 0};
_touchStartTime = 0;
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
printf("EV: Tap\n");
#endif
_dragMode = false;
}
break;
case TRawEvent::NoEvent:
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
printf("EV: NoEvent\n");
#endif
break;
}
// Store lastEvent
lastEvent = (int) event;
lastX = _touchX[0];
lastY = _touchY[0];
}
void FT6X36::poll(TPoint * point, TEvent * e)
{
readData();
// TPoint point{_touchX[0], _touchY[0]};
TRawEvent event = (TRawEvent)_touchEvent[0];
if (point != NULL)
{
point->x = _touchX[0];
point->y = _touchY[0];
}
if (e != NULL)
{
switch (event)
{
case TRawEvent::PressDown:
*e = TEvent::TouchStart;
break;
case TRawEvent::Contact:
*e = TEvent::TouchMove;
break;
case TRawEvent::LiftUp:
default:
*e = TEvent::TouchEnd;
break;
}
}
}
uint8_t FT6X36::read8(uint8_t regName) {
uint8_t buf;
readRegister8(regName, &buf);
return buf;
}
#define data_size 16 // Discarding last 2: 0x0E & 0x0F as not relevant
bool FT6X36::readData(void)
{
esp_err_t ret;
uint8_t data[data_size];
uint8_t touch_pnt_cnt; // Number of detected touch points
readRegister8(FT6X36_REG_NUM_TOUCHES, &touch_pnt_cnt);
// Read data
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (FT6X36_ADDR<<1), ACK_CHECK_EN);
i2c_master_write_byte(cmd, 0, ACK_CHECK_EN);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
return ret;
}
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (FT6X36_ADDR<<1)|1, ACK_CHECK_EN);
i2c_master_read(cmd, data, data_size, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (CONFIG_FT6X36_DEBUG) {
//printf("REGISTERS:\n");
for (int16_t i = 0; i < data_size; i++)
{
printf("%x:%x ", i, data[i]);
}
printf("\n");
}
const uint8_t addrShift = 6;
// READ X, Y and Touch events (X 2)
for (uint8_t i = 0; i < 2; i++)
{
_touchX[i] = data[FT6X36_REG_P1_XH + i * addrShift] & 0x0F;
_touchX[i] <<= 8;
_touchX[i] |= data[FT6X36_REG_P1_XL + i * addrShift];
_touchY[i] = data[FT6X36_REG_P1_YH + i * addrShift] & 0x0F;
_touchY[i] <<= 8;
_touchY[i] |= data[FT6X36_REG_P1_YL + i * addrShift];
_touchEvent[i] = data[FT6X36_REG_P1_XH + i * addrShift] >> 6;
}
// Make _touchX[idx] and _touchY[idx] rotation aware
switch (_rotation)
{
case 1:
swap(_touchX[0], _touchY[0]);
swap(_touchX[1], _touchY[1]);
_touchY[0] = _touch_width - _touchY[0] -1;
_touchY[1] = _touch_width - _touchY[1] -1;
break;
case 2:
_touchX[0] = _touch_width - _touchX[0] - 1;
_touchX[1] = _touch_width - _touchX[1] - 1;
_touchY[0] = _touch_height - _touchY[0] - 1;
_touchY[1] = _touch_height - _touchY[1] - 1;
break;
case 3:
swap(_touchX[0], _touchY[0]);
swap(_touchX[1], _touchY[1]);
_touchX[0] = _touch_height - _touchX[0] - 1;
_touchX[1] = _touch_height - _touchX[1] - 1;
break;
}
if (CONFIG_FT6X36_DEBUG) {
printf("X0:%d Y0:%d EVENT:%d\n", _touchX[0], _touchY[0], _touchEvent[0]);
//printf("X1:%d Y1:%d EVENT:%d\n", _touchX[1], _touchY[1], _touchEvent[1]);
}
return true;
}
void FT6X36::writeRegister8(uint8_t reg, uint8_t value)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, FT6X36_ADDR << 1 | I2C_MASTER_WRITE, ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg , ACK_CHECK_EN);
i2c_master_write_byte(cmd, value , ACK_CHECK_EN);
i2c_master_stop(cmd);
i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
}
uint8_t FT6X36::readRegister8(uint8_t reg, uint8_t *data_buf)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, FT6X36_ADDR << 1 | I2C_MASTER_WRITE, ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg, I2C_MASTER_ACK);
// Research: Why it's started a 2nd time here
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (FT6X36_ADDR << 1) | I2C_MASTER_READ, true);
i2c_master_read_byte(cmd, data_buf, I2C_MASTER_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
//FT6X36_REG_GESTURE_ID. Check if it can be read!
#if defined(FT6X36_DEBUG) && FT6X36_DEBUG==1
printf("REG 0x%x: 0x%x\n",reg,ret);
#endif
return ret;
}
void FT6X36::fireEvent(TPoint point, TEvent e)
{
if (_touchHandler)
_touchHandler(point, e);
}
void FT6X36::debugInfo()
{
printf(" TH_DIFF: %d CTRL: %d\n", read8(FT6X36_REG_FILTER_COEF), read8(FT6X36_REG_CTRL));
printf(" TIMEENTERMONITOR: %d PERIODACTIVE: %d\n", read8(FT6X36_REG_TIME_ENTER_MONITOR), read8(FT6X36_REG_TOUCHRATE_ACTIVE));
printf(" PERIODMONITOR: %d RADIAN_VALUE: %d\n", read8(FT6X36_REG_TOUCHRATE_MONITOR), read8(FT6X36_REG_RADIAN_VALUE));
printf(" OFFSET_LEFT_RIGHT: %d OFFSET_UP_DOWN: %d\n", read8(FT6X36_REG_OFFSET_LEFT_RIGHT), read8(FT6X36_REG_OFFSET_UP_DOWN));
printf("DISTANCE_LEFT_RIGHT: %d DISTANCE_UP_DOWN: %d\n", read8(FT6X36_REG_DISTANCE_LEFT_RIGHT), read8(FT6X36_REG_DISTANCE_UP_DOWN));
printf(" DISTANCE_ZOOM: %d CIPHER: %d\n", read8(FT6X36_REG_DISTANCE_ZOOM), read8(FT6X36_REG_CHIPID));
printf(" G_MODE: %d PWR_MODE: %d\n", read8(FT6X36_REG_INTERRUPT_MODE), read8(FT6X36_REG_POWER_MODE));
printf(" FIRMID: %d FOCALTECH_ID: %d STATE: %d\n", read8(FT6X36_REG_FIRMWARE_VERSION), read8(FT6X36_REG_PANEL_ID), read8(FT6X36_REG_STATE));
}
void FT6X36::setRotation(uint8_t rotation) {
_rotation = rotation;
}
void FT6X36::setTouchWidth(uint16_t width) {
printf("touch w:%d\n",width);
_touch_width = width;
}
void FT6X36::setTouchHeight(uint16_t height) {
printf("touch h:%d\n",height);
_touch_height = height;
}

View File

@ -1,184 +0,0 @@
#include <cstdio>
#include <cstdlib>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "driver/i2c.h"
#include "sdkconfig.h"
#include <esp_timer.h>
#ifndef ft6x36_h
#define ft6x36_h
// I2C Constants
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
#define ACK_VAL 0x0 /*!< I2C ack value */
#define NACK_VAL 0x1 /*!< I2C nack value */
//SemaphoreHandle_t print_mux = NULL;
#define FT6X36_ADDR 0x38
#define FT6X36_REG_DEVICE_MODE 0x00
#define FT6X36_REG_GESTURE_ID 0x01
#define FT6X36_REG_NUM_TOUCHES 0x02
#define FT6X36_REG_P1_XH 0x03
#define FT6X36_REG_P1_XL 0x04
#define FT6X36_REG_P1_YH 0x05
#define FT6X36_REG_P1_YL 0x06
#define FT6X36_REG_P1_WEIGHT 0x07
#define FT6X36_REG_P1_MISC 0x08
#define FT6X36_REG_P2_XH 0x09
#define FT6X36_REG_P2_XL 0x0A
#define FT6X36_REG_P2_YH 0x0B
#define FT6X36_REG_P2_YL 0x0C
#define FT6X36_REG_P2_WEIGHT 0x0D
#define FT6X36_REG_P2_MISC 0x0E
#define FT6X36_REG_THRESHHOLD 0x80
#define FT6X36_REG_FILTER_COEF 0x85
#define FT6X36_REG_CTRL 0x86
#define FT6X36_REG_TIME_ENTER_MONITOR 0x87
#define FT6X36_REG_TOUCHRATE_ACTIVE 0x88
#define FT6X36_REG_TOUCHRATE_MONITOR 0x89 // value in ms
#define FT6X36_REG_RADIAN_VALUE 0x91
#define FT6X36_REG_OFFSET_LEFT_RIGHT 0x92
#define FT6X36_REG_OFFSET_UP_DOWN 0x93
#define FT6X36_REG_DISTANCE_LEFT_RIGHT 0x94
#define FT6X36_REG_DISTANCE_UP_DOWN 0x95
#define FT6X36_REG_DISTANCE_ZOOM 0x96
#define FT6X36_REG_LIB_VERSION_H 0xA1
#define FT6X36_REG_LIB_VERSION_L 0xA2
#define FT6X36_REG_CHIPID 0xA3
#define FT6X36_REG_INTERRUPT_MODE 0xA4
#define FT6X36_REG_POWER_MODE 0xA5
#define FT6X36_REG_FIRMWARE_VERSION 0xA6
#define FT6X36_REG_PANEL_ID 0xA8
#define FT6X36_REG_STATE 0xBC
#define FT6X36_PMODE_ACTIVE 0x00
#define FT6X36_PMODE_MONITOR 0x01
#define FT6X36_PMODE_STANDBY 0x02
#define FT6X36_PMODE_HIBERNATE 0x03
/* Possible values returned by FT6X36_GEST_ID_REG */
#define FT6X36_GEST_ID_NO_GESTURE 0x00
#define FT6X36_GEST_ID_MOVE_UP 0x10
#define FT6X36_GEST_ID_MOVE_RIGHT 0x14
#define FT6X36_GEST_ID_MOVE_DOWN 0x18
#define FT6X36_GEST_ID_MOVE_LEFT 0x1C
#define FT6X36_GEST_ID_ZOOM_IN 0x48
#define FT6X36_GEST_ID_ZOOM_OUT 0x49
#define FT6X36_VENDID 0x11
#define FT6206_CHIPID 0x06
#define FT6236_CHIPID 0x36
#define FT6336_CHIPID 0x64
#define FT6X36_DEFAULT_THRESHOLD 22
// From: https://github.com/lvgl/lv_port_esp32/blob/master/components/lvgl_esp32_drivers/lvgl_touch/ft6x36.h
#define FT6X36_MSB_MASK 0x0F
#define FT6X36_LSB_MASK 0xFF
enum class TRawEvent
{
PressDown,
LiftUp,
Contact,
NoEvent
};
enum class TEvent
{
None,
TouchStart,
TouchMove,
TouchEnd,
Tap,
DragStart,
DragMove,
DragEnd
};
struct TPoint
{
uint16_t x;
uint16_t y;
/**
* This is being used in the original library but I'm not using it in this implementation
*/
bool aboutEqual(const TPoint point)
{
return abs(x - point.x) <= 5 && abs(y - point.y) <= 5;
}
};
class FT6X36
{
static void IRAM_ATTR isr(void* arg);
public:
// TwoWire * wire will be replaced by ESP-IDF https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2c.html
FT6X36(i2c_port_t = I2C_NUM_0, gpio_num_t interruptPin = GPIO_NUM_NC);
~FT6X36();
bool begin(uint8_t threshold = FT6X36_DEFAULT_THRESHOLD, uint16_t width = 0, uint16_t height = 0);
void registerTouchHandler(void(*fn)(TPoint point, TEvent e));
uint8_t touched();
void loop();
void processTouch();
void debugInfo();
void poll(TPoint * point, TEvent * event);
// Helper functions to make the touch display aware
void setRotation(uint8_t rotation);
void setTouchWidth(uint16_t width);
void setTouchHeight(uint16_t height);
// Pending implementation. How much x->touch y↓touch is placed (In case is smaller than display)
void setXoffset(uint16_t x_offset);
void setYoffset(uint16_t y_offset);
// Smart template from EPD to swap x,y:
template <typename T> static inline void
swap(T& a, T& b)
{
T t = a;
a = b;
b = t;
}
void(*_touchHandler)(TPoint point, TEvent e) = nullptr;
bool readData(void);
private:
void writeRegister8(uint8_t reg, uint8_t val);
uint8_t readRegister8(uint8_t reg, uint8_t *data_buf);
void fireEvent(TPoint point, TEvent e);
uint8_t read8(uint8_t regName);
static FT6X36 * _instance;
i2c_port_t _port;
int8_t _intPin;
// Make touch rotation aware:
uint8_t _rotation = 0;
uint16_t _touch_width = 0;
uint16_t _touch_height = 0;
uint8_t _touches;
uint16_t _touchX[2], _touchY[2], _touchEvent[2];
TPoint _points[10];
uint8_t _pointIdx = 0;
unsigned long _touchStartTime = 0;
unsigned long _touchEndTime = 0;
uint8_t lastEvent = 3; // No event
uint16_t lastX = 0;
uint16_t lastY = 0;
bool _dragMode = false;
const uint8_t maxDeviation = 5;
};
#endif

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 strange_v
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,8 +0,0 @@
This project is an adaption of the code at https://github.com/martinberlin/FT6X36-IDF which is an adaptation of https://github.com/strange-v/FT6X36
The original license is an MIT license and is included in this directory.
Changes:
- Remove Kconfig-based configuratio
- Removed I2C init code
- Allow for passing a different I2C port

View File

@ -14,6 +14,7 @@ std::shared_ptr<EspLcdConfiguration> St7789Display::createEspLcdConfiguration(co
.mirrorY = configuration.mirrorY,
.invertColor = configuration.invertColor,
.bufferSize = configuration.bufferSize,
.buffSpiram = configuration.buffSpiram,
.touch = configuration.touch,
.backlightDutyFunction = configuration.backlightDutyFunction,
.resetPin = configuration.resetPin,

View File

@ -25,6 +25,7 @@ public:
std::function<void(uint8_t)> _Nullable backlightDutyFunction;
gpio_num_t resetPin;
bool lvglSwapBytes;
bool buffSpiram = false;
lcd_rgb_element_order_t rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB;
};

View File

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

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