Compare commits

...

10 Commits

Author SHA1 Message Date
Adolfo Reyna 5c535490ea Fix compatibility issues for ESP-IDF v5.3.2 and add es3c28p target support
Build Simulator / Linux (push) Has been cancelled
Build Simulator / macOS (push) Has been cancelled
Build Firmware / BuildSdk (map[arch:esp32 id:generic-esp32]) (push) Has been cancelled
Build Firmware / BuildSdk (map[arch:esp32c6 id:generic-esp32c6]) (push) Has been cancelled
Build Firmware / BuildSdk (map[arch:esp32p4 id:generic-esp32p4]) (push) Has been cancelled
Build Firmware / BuildSdk (map[arch:esp32s3 id:generic-esp32s3]) (push) Has been cancelled
Tests / TactilityTests (push) Has been cancelled
Tests / DevicetreeTests (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:cyd-2432s024c]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:cyd-2432s024r]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:cyd-2432s028r]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:cyd-2432s028rv3]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:cyd-2432s032c]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:cyd-3248s035c]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:cyd-e32r28t]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:cyd-e32r32p]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:elecrow-crowpanel-basic-28]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:elecrow-crowpanel-basic-35]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:guition-jc2432w328c]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:lilygo-tdisplay]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:m5stack-core2]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:m5stack-stickc-plus2]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32 id:m5stack-stickc-plus]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32p4 id:guition-jc1060p470ciwy]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32p4 id:m5stack-tab5]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:btt-panda-touch]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:cyd-4848s040c]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:cyd-8048s043c]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:elecrow-crowpanel-advance-28]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:elecrow-crowpanel-advance-35]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:elecrow-crowpanel-advance-50]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:elecrow-crowpanel-basic-50]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:guition-jc3248w535c]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:guition-jc8048w550c]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:heltec-wifi-lora-32-v3]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:lilygo-tdeck]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:lilygo-tdisplay-s3]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:lilygo-tdongle-s3]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:lilygo-thmi]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:lilygo-tlora-pager]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:m5stack-cardputer-adv]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:m5stack-cardputer]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:m5stack-cores3]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:m5stack-papers3]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:m5stack-stackchan]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:m5stack-sticks3]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:unphone]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:waveshare-esp32-s3-geek]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:waveshare-s3-lcd-13]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:waveshare-s3-touch-lcd-128]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:waveshare-s3-touch-lcd-147]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:waveshare-s3-touch-lcd-43]) (push) Has been cancelled
Build Firmware / BuildFirmware (map[arch:esp32s3 id:wireless-tag-wt32-sc01-plus]) (push) Has been cancelled
Build Firmware / BundleArtifacts (push) Has been cancelled
Build Firmware / PublishFirmwareSnapshot (push) Has been cancelled
Build Firmware / PublishFirmwareStable (push) Has been cancelled
Build Firmware / PublishSdk (push) Has been cancelled
2026-06-23 11:59:47 -04:00
Ken Van Hoeylandt e50659a3fb Fix for Gt911Touch. (#538) 2026-06-22 08:18:55 +02:00
Ken Van Hoeylandt aeccc15ab6 Fix for Gt911Touch when using new I2C driver. (#536)
Committing this straight to main branch to avoid wasting pipelines.
2026-06-21 22:20:07 +02:00
Shadowtrance 594b8bd27e Smart tab5keyboard (#533) 2026-06-19 22:14:07 +02:00
Ken Van Hoeylandt e8b9a1f2a9 Fixes and improvements (#534) 2026-06-19 20:46:56 +02:00
Ken Van Hoeylandt a35c88c8fd Grove driver, I2C migrations, bugfixes and docs (#532) 2026-06-19 00:52:31 +02:00
Ken Van Hoeylandt 8dabda2b5b Implement new I2C driver (#531) 2026-06-12 18:46:22 +02:00
Shadowtrance 8dd9bee8d0 M5Stack Tab5 TinyUSB MSC support implemented (#530) 2026-06-10 22:39:36 +02:00
Ken Van Hoeylandt 543390a977 Fix for CoreS3 touch (#529)
The FT6 driver does a hardware ID check, which fails on the CoreS3 but
not on Stackchan for some reason. FT5 works on Stackchan, so we'll use
that for all devices, just like M5GFX does.
2026-06-10 00:24:18 +02:00
Shadowtrance 62266dff58 Tab5 Keyboard (#528) 2026-06-09 23:19:56 +02:00
142 changed files with 2766 additions and 682 deletions
+4 -1
View File
@@ -22,4 +22,7 @@ dependencies.lock
sdkconfig.board.*.dev
.tactility/
.tactility/
.caveman.json
.ai/mcp
@@ -26,7 +26,6 @@ macro(tactility_project project_name)
set(COMPONENTS
TactilityFreeRtos
bm8563-module
bm8563-module
bmi270-module
mpu6886-module
pi4ioe5v6408-module
+3 -1
View File
@@ -37,4 +37,6 @@ CONFIG_FATFS_SECTOR_512=y
CONFIG_WL_SECTOR_SIZE_512=y
CONFIG_WL_SECTOR_SIZE=512
CONFIG_WL_SECTOR_MODE_SAFE=y
CONFIG_WL_SECTOR_MODE=1
CONFIG_WL_SECTOR_MODE=1
# Allow new i2c_master API (used by Tab5Keyboard for LP_I2C_NUM_0) to coexist with legacy i2c driver
CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK=y
Symlink
+1
View File
@@ -0,0 +1 @@
Documentation/README.md
+2
View File
@@ -54,6 +54,8 @@ if (DEFINED ENV{ESP_IDF_VERSION})
set(EXCLUDE_COMPONENTS "Simulator")
idf_build_set_property(LINK_OPTIONS "-Wl,--allow-multiple-definition" APPEND)
# Panic handler wrapping is only available on Xtensa architecture
if (CONFIG_IDF_TARGET_ARCH_XTENSA)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_panic_handler" APPEND)
@@ -3,11 +3,15 @@
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 41 and interrupt pin is 40
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0,
i2c,
800,
480
);
@@ -3,10 +3,14 @@
#include <Gt911Touch.h>
#include <Ili934xDisplay.h>
#include <PwmBacklight.h>
#include <tactility/check.h>
#include <tactility/device.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0,
i2c,
LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION
);
@@ -3,10 +3,14 @@
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <St7796Display.h>
#include <tactility/check.h>
#include <tactility/device.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0,
i2c,
LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION
);
@@ -3,6 +3,8 @@
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <Tactility/Logger.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <driver/gpio.h>
#include <esp_err.h>
@@ -221,8 +223,10 @@ lvgl_port_display_rgb_cfg_t St7701Display::getLvglPortDisplayRgbConfig(esp_lcd_p
std::shared_ptr<tt::hal::touch::TouchDevice> St7701Display::getTouchDevice() {
if (touchDevice == nullptr) {
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0,
i2c,
480,
480
);
@@ -3,12 +3,16 @@
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 38 and interrupt pin is 18
// or INT = NC, schematic and other info floating around is kinda conflicting...
auto* i2c = device_find_by_name("i2c_internal");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0,
i2c,
800,
480
);
@@ -3,10 +3,14 @@
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <Ili9488Display.h>
#include <tactility/check.h>
#include <tactility/device.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0,
i2c,
320,
480
);
@@ -2,12 +2,16 @@
#include <Gt911Touch.h>
#include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 38 and interrupt pin is 18
// or INT = NC, schematic and other info floating around is kinda conflicting...
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0,
i2c,
800,
480
);
@@ -3,12 +3,16 @@
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 38 and interrupt pin is 18
// or INT = NC, schematic and other info floating around is kinda conflicting...
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0,
i2c,
800,
480
);
+7
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 PwmBacklight driver vfs fatfs
)
+22
View File
@@ -0,0 +1,22 @@
#include "PwmBacklight.h"
#include "devices/Display.h"
#include <driver/gpio.h>
#include <Tactility/hal/Configuration.h>
using namespace tt::hal;
static bool initBoot() {
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT);
}
static DeviceVector createDevices() {
return {
createDisplay()
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -0,0 +1,49 @@
#include "Display.h"
#include <Ft6x36Touch.h>
#include <Ili934xDisplay.h>
#include <PwmBacklight.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto configuration = std::make_unique<Ft6x36Touch::Configuration>(
I2C_NUM_0,
240, // xMax (native portrait width)
320, // yMax (native portrait height)
true, // swapXy
true, // mirrorX
false, // mirrorY
GPIO_NUM_18, // Touch Reset (RST)
GPIO_NUM_17 // Touch Interrupt (INT)
);
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 = true,
.mirrorX = false,
.mirrorY = false,
.invertColor = false,
.swapBytes = true,
.bufferSize = LCD_BUFFER_SIZE,
.touch = createTouch(),
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
.resetPin = GPIO_NUM_NC,
.rgbElementOrder = LCD_RGB_ELEMENT_ORDER_BGR
};
auto spi_configuration = std::make_shared<Ili934xDisplay::SpiConfiguration>(Ili934xDisplay::SpiConfiguration {
.spiHostDevice = LCD_SPI_HOST,
.csPin = LCD_PIN_CS,
.dcPin = LCD_PIN_DC,
.pixelClockFrequency = 40'000'000,
.transactionQueueDepth = 10
});
return std::make_shared<Ili934xDisplay>(panel_configuration, spi_configuration, true);
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <driver/gpio.h>
#include <driver/spi_common.h>
#include <memory>
// Display
constexpr auto LCD_SPI_HOST = SPI2_HOST;
constexpr auto LCD_PIN_CS = GPIO_NUM_10;
constexpr auto LCD_PIN_DC = GPIO_NUM_46;
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;
// Backlight pin
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_45;
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
+23
View File
@@ -0,0 +1,23 @@
#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 es3c28p_module = {
.name = "es3c28p",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}
+24
View File
@@ -0,0 +1,24 @@
[general]
vendor=LCDWIKI
name=ES3C28P
[apps]
launcherAppId=Launcher
[hardware]
target=ESP32S3
flashSize=16MB
spiRam=true
spiRamMode=OCT
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
bluetooth=true
[display]
size=2.8"
shape=rectangle
dpi=143
[lvgl]
colorDepth=16
+3
View File
@@ -0,0 +1,3 @@
dependencies:
- Platforms/platform-esp32
dts: es3c28p.dts
+48
View File
@@ -0,0 +1,48 @@
/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>
#include <tactility/bindings/esp32_spi.h>
/ {
compatible = "root";
model = "LCDWIKI ES3C28P";
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;
};
i2c0 {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_0>;
clock-frequency = <400000>;
pin-sda = <&gpio0 16 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 15 GPIO_FLAG_NONE>;
};
display_spi: spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
pin-mosi = <&gpio0 11 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 12 GPIO_FLAG_NONE>;
};
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>;
bus-width = <4>;
};
};
@@ -5,21 +5,22 @@
#include <PwmBacklight.h>
#include <Tactility/Logger.h>
#include <Tactility/Mutex.h>
#include <tactility/check.h>
#include <tactility/device.h>
constexpr auto LCD_PIN_RESET = GPIO_NUM_0; // Match P4 EV board reset line
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_23;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 1024;
constexpr auto LCD_VERTICAL_RESOLUTION = 600;
constexpr auto TOUCH_I2C_PORT = I2C_NUM_0;
constexpr auto TOUCH_I2C_SDA = GPIO_NUM_7;
constexpr auto TOUCH_I2C_SCL = GPIO_NUM_8;
constexpr auto TOUCH_PIN_RESET = GPIO_NUM_NC;
constexpr auto TOUCH_PIN_INTERRUPT = GPIO_NUM_NC;
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto* i2c = device_find_by_name("i2c_internal");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
TOUCH_I2C_PORT,
i2c,
LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION,
false, // swapXY
@@ -3,12 +3,16 @@
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 38 and interrupt pin is 18
// or INT = NC, schematic and other info floating around is kinda conflicting...
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0,
i2c,
800,
480
);
@@ -7,16 +7,20 @@
#include <Tactility/hal/Configuration.h>
#include <Tactility/lvgl/LvglSync.h>
#include <tactility/check.h>
#include <tactility/device.h>
bool initBoot();
using namespace tt::hal;
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
auto* i2c_internal = device_find_by_name("i2c0");
check(i2c_internal);
return {
createPower(),
createDisplay(),
std::make_shared<TdeckKeyboard>(),
std::make_shared<TdeckKeyboard>(i2c_internal),
std::make_shared<KeyboardBacklightDevice>(),
std::make_shared<TrackballDevice>(),
createSdCard()
+1 -1
View File
@@ -58,7 +58,7 @@ bool initBoot() {
std::vector<tt::hal::gps::GpsConfiguration> gps_configurations;
gps_service->getGpsConfigurations(gps_configurations);
if (gps_configurations.empty()) {
if (gps_service->addGpsConfiguration(tt::hal::gps::GpsConfiguration {.uartName = "uart1", .baudRate = 38400, .model = tt::hal::gps::GpsModel::UBLOX10})) {
if (gps_service->addGpsConfiguration(tt::hal::gps::GpsConfiguration {.uartName = "uart0", .baudRate = 38400, .model = tt::hal::gps::GpsModel::UBLOX10})) {
LOGGER.info("Configured internal GPS");
} else {
LOGGER.error("Failed to configure internal GPS");
@@ -3,10 +3,14 @@
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <St7789Display.h>
#include <tactility/check.h>
#include <tactility/device.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0,
i2c,
240,
320,
true,
@@ -1,6 +1,5 @@
#include "KeyboardBacklight.h"
#include <KeyboardBacklight/KeyboardBacklight.h> // Driver
#include <Tactility/hal/i2c/I2c.h>
#include <Tactility/settings/KeyboardSettings.h>
// TODO: Add Mutex and consider refactoring into a class
@@ -1,24 +1,18 @@
#include "TdeckKeyboard.h"
#include <Tactility/hal/i2c/I2c.h>
#include <driver/i2c.h>
#include <lvgl.h>
#include <Tactility/settings/KeyboardSettings.h>
#include <Tactility/settings/DisplaySettings.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <tactility/hal/Device.h>
#include <Tactility/Logger.h>
#include <KeyboardBacklight/KeyboardBacklight.h>
#include <Tactility/Logger.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/settings/DisplaySettings.h>
#include <Tactility/settings/KeyboardSettings.h>
#include <lvgl.h>
#include <tactility/drivers/i2c_controller.h>
using tt::hal::findFirstDevice;
static const auto LOGGER = tt::Logger("TdeckKeyboard");
constexpr auto TDECK_KEYBOARD_I2C_BUS_HANDLE = I2C_NUM_0;
constexpr auto TDECK_KEYBOARD_SLAVE_ADDRESS = 0x55;
static bool keyboard_i2c_read(uint8_t* output) {
return tt::hal::i2c::masterRead(TDECK_KEYBOARD_I2C_BUS_HANDLE, TDECK_KEYBOARD_SLAVE_ADDRESS, output, 1, 100 / portTICK_PERIOD_MS);
}
constexpr uint8_t TDECK_KEYBOARD_SLAVE_ADDRESS = 0x55;
/**
* The callback simulates press and release events, because the T-Deck
@@ -37,7 +31,8 @@ static void keyboard_read_callback(lv_indev_t* indev, lv_indev_data_t* data) {
data->key = 0;
data->state = LV_INDEV_STATE_RELEASED;
if (keyboard_i2c_read(&read_buffer)) {
auto* keyboard = static_cast<TdeckKeyboard*>(lv_indev_get_user_data(indev));
if (i2c_controller_read(keyboard->getI2cController(), TDECK_KEYBOARD_SLAVE_ADDRESS, &read_buffer, 1, 100 / portTICK_PERIOD_MS) == ERROR_NONE) {
if (read_buffer == 0 && read_buffer != last_buffer) {
if (LOGGER.isLoggingDebug()) {
LOGGER.debug("Released {}", last_buffer);
@@ -91,5 +86,5 @@ bool TdeckKeyboard::stopLvgl() {
}
bool TdeckKeyboard::isAttached() const {
return tt::hal::i2c::masterHasDeviceAtAddress(TDECK_KEYBOARD_I2C_BUS_HANDLE, TDECK_KEYBOARD_SLAVE_ADDRESS, 100);
return i2c_controller_has_device_at_address(i2cController, TDECK_KEYBOARD_SLAVE_ADDRESS, 100) == ERROR_NONE;
}
@@ -1,17 +1,23 @@
#pragma once
#include <Tactility/hal/keyboard/KeyboardDevice.h>
#include <Tactility/TactilityCore.h>
struct Device;
class TdeckKeyboard final : public tt::hal::keyboard::KeyboardDevice {
::Device* i2cController;
lv_indev_t* deviceHandle = nullptr;
public:
explicit TdeckKeyboard(::Device* i2cController) : i2cController(i2cController) {}
std::string getName() const override { return "T-Deck Keyboard"; }
std::string getDescription() const override { return "I2C keyboard"; }
::Device* getI2cController() const { return i2cController; }
bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override;
bool isAttached() const override;
+2 -10
View File
@@ -3,6 +3,7 @@
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_spi.h>
@@ -30,15 +31,6 @@
pin-scl = <&gpio0 8 GPIO_FLAG_NONE>;
};
i2c_external: i2c1 {
compatible = "espressif,esp32-i2c";
status = "disabled";
port = <I2C_NUM_1>;
clock-frequency = <400000>;
pin-sda = <&gpio0 43 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 44 GPIO_FLAG_NONE>;
};
i2s0 {
compatible = "espressif,esp32-i2s";
port = <I2S_NUM_0>;
@@ -55,7 +47,7 @@
pin-sclk = <&gpio0 40 GPIO_FLAG_NONE>;
};
uart1 {
uart0 {
compatible = "espressif,esp32-uart";
port = <UART_NUM_1>;
pin-tx = <&gpio0 43 GPIO_FLAG_NONE>;
@@ -4,6 +4,7 @@
#include "devices/TpagerKeyboard.h"
#include "devices/TpagerPower.h"
#include <driver/gpio.h>
#include <tactility/device.h>
#include <Tactility/hal/Configuration.h>
#include <Bq25896.h>
@@ -14,17 +15,18 @@ bool tpagerInit();
using namespace tt::hal;
static DeviceVector createDevices() {
auto bq27220 = std::make_shared<Bq27220>(I2C_NUM_0);
auto* i2c = device_find_by_name("i2c0");
auto bq27220 = std::make_shared<Bq27220>(i2c);
auto power = std::make_shared<TpagerPower>(bq27220);
auto tca8418 = std::make_shared<Tca8418>(I2C_NUM_0);
auto tca8418 = std::make_shared<Tca8418>(i2c);
auto keyboard = std::make_shared<TpagerKeyboard>(tca8418);
return std::vector<std::shared_ptr<tt::hal::Device>> {
tca8418,
std::make_shared<Bq25896>(I2C_NUM_0),
std::make_shared<Bq25896>(i2c),
bq27220,
std::make_shared<Drv2605>(I2C_NUM_0),
std::make_shared<Drv2605>(i2c),
power,
createTpagerSdCard(),
createDisplay(),
@@ -1,6 +1,5 @@
#include "TpagerKeyboard.h"
#include <Tactility/hal/i2c/I2c.h>
#include <Tactility/Logger.h>
#include <driver/i2c.h>
@@ -156,7 +155,7 @@ bool TpagerKeyboard::stopLvgl() {
}
bool TpagerKeyboard::isAttached() const {
return tt::hal::i2c::masterHasDeviceAtAddress(keypad->getPort(), keypad->getAddress(), 100);
return i2c_controller_has_device_at_address(keypad->getController(), keypad->getAddress(), 100) == ERROR_NONE;
}
bool TpagerKeyboard::initBacklight(gpio_num_t pin, uint32_t frequencyHz, ledc_timer_t timer, ledc_channel_t channel) {
@@ -5,8 +5,6 @@
static const auto LOGGER = tt::Logger("TpagerPower");
constexpr auto TPAGER_GAUGE_I2C_BUS_HANDLE = I2C_NUM_0;
TpagerPower::~TpagerPower() {}
bool TpagerPower::supportsMetric(MetricType type) const {
@@ -4,6 +4,7 @@
#include "devices/CardputerPower.h"
#include <driver/gpio.h>
#include <tactility/device.h>
#include <Tactility/hal/Configuration.h>
#include <PwmBacklight.h>
@@ -16,7 +17,7 @@ static bool initBoot() {
}
static DeviceVector createDevices() {
auto tca8418 = std::make_shared<Tca8418>(I2C_NUM_0);
auto tca8418 = std::make_shared<Tca8418>(device_find_by_name("i2c_internal"));
return {
createSdCard(),
createDisplay(),
@@ -1,10 +1,8 @@
#include "CardputerKeyboard.h"
#include <Tactility/hal/i2c/I2c.h>
#include <tactility/drivers/i2c_controller.h>
constexpr auto* TAG = "CardputerKeyb";
constexpr auto BACKLIGHT = GPIO_NUM_46;
constexpr auto KB_ROWS = 14;
constexpr auto KB_COLS = 4;
@@ -151,5 +149,5 @@ bool CardputerKeyboard::stopLvgl() {
}
bool CardputerKeyboard::isAttached() const {
return tt::hal::i2c::masterHasDeviceAtAddress(keypad->getPort(), keypad->getAddress(), 100);
return i2c_controller_has_device_at_address(keypad->getController(), keypad->getAddress(), 100) == ERROR_NONE;
}
@@ -3,6 +3,7 @@
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_spi.h>
@@ -36,12 +37,14 @@
};
};
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>;
port_a: grove0 {
compatible = "espressif,esp32-grove";
defaultMode = <GROVE_MODE_I2C>;
pinSdaTx = <&gpio0 2 GPIO_FLAG_NONE>;
pinSclRx = <&gpio0 1 GPIO_FLAG_NONE>;
uartPort = <UART_NUM_1>;
i2cPort = <I2C_NUM_1>;
i2cClockFrequency = <400000>;
};
display_spi: spi0 {
@@ -68,12 +71,4 @@
pin-data-out = <&gpio0 42 GPIO_FLAG_NONE>;
pin-data-in = <&gpio0 46 GPIO_FLAG_NONE>;
};
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>;
};
};
@@ -3,6 +3,7 @@
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_spi.h>
@@ -22,12 +23,14 @@
gpio-count = <49>;
};
i2c_port_a {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_0>;
clock-frequency = <400000>;
pin-sda = <&gpio0 2 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 1 GPIO_FLAG_NONE>;
port_a: grove0 {
compatible = "espressif,esp32-grove";
defaultMode = <GROVE_MODE_I2C>;
pinSdaTx = <&gpio0 2 GPIO_FLAG_NONE>;
pinSclRx = <&gpio0 1 GPIO_FLAG_NONE>;
uartPort = <UART_NUM_1>;
i2cPort = <I2C_NUM_0>;
i2cClockFrequency = <400000>;
};
display_spi: spi0 {
@@ -55,12 +58,4 @@
pin-data-out = <&gpio0 42 GPIO_FLAG_NONE>;
pin-data-in = <&gpio0 46 GPIO_FLAG_NONE>;
};
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>;
};
};
@@ -1,10 +1,11 @@
#include <Axp192.h>
#include <tactility/device.h>
static std::shared_ptr<Axp192> axp192 = nullptr;
std::shared_ptr<Axp192> createAxp192() {
assert(axp192 == nullptr);
auto configuration = std::make_unique<Axp192::Configuration>(I2C_NUM_0);
auto configuration = std::make_unique<Axp192::Configuration>(device_find_by_name("i2c_internal"));
axp192 = std::make_shared<Axp192>(std::move(configuration));
return axp192;
}
+9 -14
View File
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_spi.h>
@@ -37,12 +38,14 @@
};
};
i2c_port_a {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_1>;
clock-frequency = <400000>;
pin-sda = <&gpio0 32 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 33 GPIO_FLAG_NONE>;
port_a: grove0 {
compatible = "espressif,esp32-grove";
defaultMode = <GROVE_MODE_I2C>;
pinSdaTx = <&gpio0 32 GPIO_FLAG_NONE>;
pinSclRx = <&gpio0 33 GPIO_FLAG_NONE>;
uartPort = <UART_NUM_1>;
i2cPort = <I2C_NUM_1>;
i2cClockFrequency = <400000>;
};
spi0 {
@@ -63,12 +66,4 @@
pin-data-out = <&gpio0 2 GPIO_FLAG_NONE>;
pin-data-in = <&gpio0 34 GPIO_FLAG_NONE>;
};
uart_port_a: uart1 {
compatible = "espressif,esp32-uart";
status = "disabled";
port = <UART_NUM_1>;
pin-tx = <&gpio0 33 GPIO_FLAG_NONE>;
pin-rx = <&gpio0 32 GPIO_FLAG_NONE>;
};
};
+1 -1
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 FT6x36 AXP2101 AW9523 driver vfs fatfs
REQUIRES Tactility esp_lvgl_port ILI934x FT5x06 AXP2101 AW9523 driver vfs fatfs
)
+3 -2
View File
@@ -146,8 +146,9 @@ bool initPowerControl() {
bool initBoot() {
LOGGER.info("initBoot()");
axp2101 = std::make_shared<Axp2101>(I2C_NUM_0);
aw9523 = std::make_shared<Aw9523>(I2C_NUM_0);
auto controller = device_find_by_name("i2c_internal");
axp2101 = std::make_shared<Axp2101>(controller);
aw9523 = std::make_shared<Aw9523>(controller);
return initPowerControl() && initGpioExpander();
}
@@ -1,32 +1,32 @@
#include "Display.h"
#include <Axp2101.h>
#include <Ft6x36Touch.h>
#include <Ft5x06Touch.h>
#include <Ili934xDisplay.h>
#include <Tactility/Logger.h>
#include <Tactility/hal/i2c/I2c.h>
#include <tactility/check.h>
static const auto LOGGER = tt::Logger("CoreS3Display");
static void setBacklightDuty(uint8_t backlightDuty) {
const uint8_t voltage = 20 + ((8 * backlightDuty) / 255); // [0b00000, 0b11100] - under 20 is too dark
// TODO: Refactor to use Axp2102 driver subproject. Reference: https://github.com/m5stack/M5Unified/blob/b8cfec7fed046242da7f7b8024a4e92004a51ff7/src/utility/AXP2101_Class.cpp#L42
if (!tt::hal::i2c::masterWriteRegister(I2C_NUM_0, AXP2101_ADDRESS, 0x99, &voltage, 1, 1000)) { // Sets DLD01
auto controller = device_find_by_name("i2c_internal");
check(controller);
if (i2c_controller_write_register(controller, AXP2101_ADDRESS, 0x99, &voltage, 1, 1000) != ERROR_NONE) { // Sets DLD01
LOGGER.error("Failed to set display backlight voltage");
}
}
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto configuration = std::make_unique<Ft6x36Touch::Configuration>(
auto configuration = std::make_unique<Ft5x06Touch::Configuration>(
I2C_NUM_0,
319,//LCD_HORIZONTAL_RESOLUTION,
239,//LCD_VERTICAL_RESOLUTION,
false,
false,
false
239//LCD_VERTICAL_RESOLUTION,
);
return std::make_shared<Ft6x36Touch>(std::move(configuration));
return std::make_shared<Ft5x06Touch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
+25 -28
View File
@@ -3,6 +3,7 @@
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_spi.h>
@@ -42,30 +43,34 @@
};
};
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>;
port_a: grove0 {
compatible = "espressif,esp32-grove";
defaultMode = <GROVE_MODE_I2C>;
pinSdaTx = <&gpio0 2 GPIO_FLAG_NONE>;
pinSclRx = <&gpio0 1 GPIO_FLAG_NONE>;
uartPort = <UART_NUM_1>;
i2cPort = <I2C_NUM_1>;
i2cClockFrequency = <400000>;
};
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>;
port_b: grove1 {
compatible = "espressif,esp32-grove";
defaultMode = <GROVE_MODE_UART>;
pinSdaTx = <&gpio0 9 GPIO_FLAG_NONE>;
pinSclRx = <&gpio0 8 GPIO_FLAG_NONE>;
uartPort = <UART_NUM_1>;
i2cPort = <I2C_NUM_1>;
i2cClockFrequency = <400000>;
};
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>;
port_c: grove2 {
compatible = "espressif,esp32-grove";
defaultMode = <GROVE_MODE_UART>;
pinSdaTx = <&gpio0 17 GPIO_FLAG_NONE>;
pinSclRx = <&gpio0 18 GPIO_FLAG_NONE>;
uartPort = <UART_NUM_2>;
i2cPort = <I2C_NUM_1>;
i2cClockFrequency = <400000>;
};
spi0 {
@@ -89,12 +94,4 @@
pin-data-in = <&gpio0 14 GPIO_FLAG_NONE>;
pin-mclk = <&gpio0 0 GPIO_FLAG_NONE>;
};
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>;
};
};
@@ -1,10 +1,14 @@
#include "Display.h"
#include <Gt911Touch.h>
#include <EpdiyDisplayHelper.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto* i2c = device_find_by_name("i2c_internal");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0,
i2c,
540,
960,
true, // swapXy
@@ -231,7 +231,7 @@ bool initBoot() {
}
// Keep Axp2101 C++ wrapper alive for Axp2101Power (backlight + battery)
axp2101 = std::make_shared<Axp2101>(I2C_NUM_0);
axp2101 = std::make_shared<Axp2101>(i2c);
return true;
}
@@ -4,13 +4,16 @@
#include <Ft6x36Touch.h>
#include <Ili934xDisplay.h>
#include <Tactility/Logger.h>
#include <Tactility/hal/i2c/I2c.h>
#include <tactility/check.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)) {
auto controller = device_find_by_name("i2c_internal");
check(controller);
if (i2c_controller_write_register(controller, AXP2101_ADDRESS, 0x99, &voltage, 1, 1000) != ERROR_NONE) { // Sets DLD01
LOGGER.error("Failed to set display backlight voltage");
}
}
+34 -29
View File
@@ -3,6 +3,7 @@
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_spi.h>
@@ -67,30 +68,43 @@
// 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>;
// 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 6 GPIO_FLAG_NONE>;
pin-rx = <&gpio0 7 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>;
port_a: grove0 {
compatible = "espressif,esp32-grove";
defaultMode = <GROVE_MODE_I2C>;
pinSdaTx = <&gpio0 2 GPIO_FLAG_NONE>;
pinSclRx = <&gpio0 1 GPIO_FLAG_NONE>;
uartPort = <UART_NUM_2>;
i2cPort = <I2C_NUM_1>;
i2cClockFrequency = <400000>;
};
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>;
port_b: grove1 {
compatible = "espressif,esp32-grove";
defaultMode = <GROVE_MODE_UART>;
pinSdaTx = <&gpio0 9 GPIO_FLAG_NONE>;
pinSclRx = <&gpio0 8 GPIO_FLAG_NONE>;
uartPort = <UART_NUM_2>;
i2cPort = <I2C_NUM_1>;
i2cClockFrequency = <400000>;
};
port_c: grove2 {
compatible = "espressif,esp32-grove";
defaultMode = <GROVE_MODE_DISABLED>;
pinSdaTx = <&gpio0 17 GPIO_FLAG_NONE>;
pinSclRx = <&gpio0 18 GPIO_FLAG_NONE>;
uartPort = <UART_NUM_2>;
i2cPort = <I2C_NUM_1>;
i2cClockFrequency = <400000>;
};
spi0 {
@@ -111,13 +125,4 @@
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>;
};
};
@@ -1,10 +1,11 @@
#include <Axp192.h>
#include <tactility/device.h>
static std::shared_ptr<Axp192> axp192 = nullptr;
std::shared_ptr<Axp192> createAxp192() {
assert(axp192 == nullptr);
auto configuration = std::make_unique<Axp192::Configuration>(I2C_NUM_0);
auto configuration = std::make_unique<Axp192::Configuration>(device_find_by_name("i2c_internal"));
axp192 = std::make_shared<Axp192>(std::move(configuration));
return axp192;
}
@@ -1,22 +1,23 @@
#include "devices/Display.h"
#include "devices/SdCard.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 <Tactility/hal/i2c/I2c.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(),
createSdCard(),
std::make_shared<Tab5Keyboard>(i2c2)
};
}
@@ -8,17 +8,23 @@
#include <PwmBacklight.h>
#include <Tactility/Logger.h>
#include <Tactility/hal/gpio/Gpio.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
static const auto LOGGER = tt::Logger("Tab5Display");
constexpr auto LCD_PIN_RESET = GPIO_NUM_0; // Match P4 EV board reset line
// 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_NUM_0,
i2c,
720,
1280,
false, // swapXY
@@ -32,8 +38,10 @@ static std::shared_ptr<tt::hal::touch::TouchDevice> createGt911Touch() {
}
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_NUM_0,
i2c,
720,
1280,
false, // swapXY
@@ -102,7 +102,7 @@ bool Ili9881cDisplay::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, cons
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
.in_color_format = LCD_COLOR_FMT_RGB565,
.out_color_format = LCD_COLOR_FMT_RGB565,
.num_fbs = 1, // TODO: 2?
.num_fbs = 2,
.video_timing =
{
.h_size = 720,
@@ -1,26 +0,0 @@
#include "SdCard.h"
#include <tactility/device.h>
#include <Tactility/hal/sdcard/SpiSdCardDevice.h>
constexpr auto SDCARD_PIN_CS = GPIO_NUM_42;
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
);
auto* spi_controller = device_find_by_name("spi0");
check(spi_controller, "spi0 not found");
return std::make_shared<SpiSdCardDevice>(
std::move(configuration),
spi_controller
);
}
@@ -1,8 +0,0 @@
#pragma once
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <memory>
using tt::hal::sdcard::SdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard();
@@ -99,7 +99,7 @@ bool St7123Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
.dpi_clock_freq_mhz = 70,
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
.num_fbs = 1,
.num_fbs = 2,
.video_timing = {
.h_size = 720,
.v_size = 1280,
@@ -1,6 +1,7 @@
#include "St7123Touch.h"
#include <Tactility/Logger.h>
#include <tactility/drivers/esp32_i2c_master.h>
#include <esp_lcd_touch_st7123.h>
#include <esp_err.h>
@@ -8,11 +9,9 @@ static const auto LOGGER = tt::Logger("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();
return esp_lcd_new_panel_io_i2c(
static_cast<esp_lcd_i2c_bus_handle_t>(configuration->port),
&io_config,
&outHandle
) == ESP_OK;
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) {
@@ -2,7 +2,7 @@
#include <EspLcdTouch.h>
#include <Tactility/TactilityCore.h>
#include <driver/i2c.h>
#include <tactility/device.h>
class St7123Touch final : public EspLcdTouch {
@@ -12,14 +12,14 @@ public:
public:
Configuration(
i2c_port_t port,
::Device* controller,
uint16_t xMax,
uint16_t yMax,
bool swapXy = false,
bool mirrorX = false,
bool mirrorY = false,
gpio_num_t pinInterrupt = GPIO_NUM_NC
) : port(port),
) : controller(controller),
xMax(xMax),
yMax(yMax),
swapXy(swapXy),
@@ -28,7 +28,7 @@ public:
pinInterrupt(pinInterrupt)
{}
i2c_port_t port;
::Device* controller;
uint16_t xMax;
uint16_t yMax;
bool swapXy;
@@ -0,0 +1,561 @@
#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 az / AZ
if (keycode >= 0x04U && keycode <= 0x1DU) {
uint32_t c = static_cast<uint32_t>('a' + (keycode - 0x04U));
return shift ? (c - 0x20U) : c;
}
// Numbers 10 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;
}
@@ -0,0 +1,85 @@
#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();
};
+79 -13
View File
@@ -3,6 +3,10 @@
#include <tactility/drivers/gpio_controller.h>
#include <tactility/log.h>
#include <Tactility/hal/keyboard/KeyboardDevice.h>
#include "devices/Tab5Keyboard.h"
#include <freertos/FreeRTOS.h>
#include <freertos/timers.h>
@@ -13,14 +17,17 @@
constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1;
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 is only touched from start()/stop(), which are called serially
// by the module manager — no atomic needed for the handle itself.
// hp_detect_timer and kb_detect_timer are only touched from start()/stop(), which are called
// serially by the module manager — no atomic needed for the handles themselves.
static TimerHandle_t hp_detect_timer = nullptr;
static TimerHandle_t kb_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 std::atomic<bool> kb_late_started { false };
static void headphoneDetectCallback(TimerHandle_t /*timer*/) {
Device* cached = io_expander0_cached.load(std::memory_order_acquire);
@@ -68,6 +75,40 @@ static void headphoneDetectCallback(TimerHandle_t /*timer*/) {
}
}
// 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" {
static error_t start() {
@@ -91,23 +132,48 @@ static error_t start() {
hp_detect_timer = nullptr;
return ERROR_RESOURCE;
}
kb_late_started = false;
kb_detect_timer = xTimerCreate("kb_detect", pdMS_TO_TICKS(KB_DETECT_POLL_MS), pdTRUE, nullptr, keyboardDetectCallback);
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;
}
static error_t stop() {
if (hp_detect_timer == nullptr) {
return ERROR_NONE;
if (hp_detect_timer != nullptr) {
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 (xTimerStop(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
LOG_W(TAG, "Failed to stop hp_detect timer");
if (kb_detect_timer != nullptr) {
if (xTimerStop(kb_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
LOG_W(TAG, "Failed to stop kb_detect timer");
}
if (xTimerDelete(kb_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
LOG_E(TAG, "Failed to delete kb_detect timer");
}
kb_detect_timer = nullptr;
}
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;
}
+1
View File
@@ -14,6 +14,7 @@ spiRamSpeed=200M
esptoolFlashFreq=80M
bluetooth=true
usbHostEnabled=true
tinyUsb=true
[display]
size=5"
+35 -25
View File
@@ -3,9 +3,10 @@
#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_grove.h>
#include <tactility/bindings/esp32_i2c_master.h>
#include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdmmc.h>
#include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_usbhost.h>
#include <bindings/bmi270.h>
@@ -27,11 +28,11 @@
};
i2c_internal: i2c0 {
compatible = "espressif,esp32-i2c";
compatible = "espressif,esp32-i2c-master";
port = <I2C_NUM_0>;
clock-frequency = <100000>;
pin-sda = <&gpio0 31 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 32 GPIO_FLAG_NONE>;
pin-sda = <&gpio0 31 GPIO_FLAG_PULL_UP>;
pin-scl = <&gpio0 32 GPIO_FLAG_PULL_UP>;
io_expander0 {
compatible = "diodes,pi4ioe5v6408";
@@ -60,20 +61,37 @@
};
};
i2c_port_a: i2c1 {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_1>;
clock-frequency = <100000>;
pin-sda = <&gpio0 53 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 54 GPIO_FLAG_NONE>;
port_a: grove0 {
compatible = "espressif,esp32-grove";
defaultMode = <GROVE_MODE_I2C>;
pinSdaTx = <&gpio0 53 GPIO_FLAG_NONE>;
pinSclRx = <&gpio0 54 GPIO_FLAG_NONE>;
uartPort = <UART_NUM_1>;
i2cPort = <I2C_NUM_1>;
i2cClockFrequency = <100000>;
};
sdcard_spi: spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
pin-mosi = <&gpio0 44 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 39 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 43 GPIO_FLAG_NONE>;
i2c_keyboard: i2c2 {
compatible = "espressif,esp32-i2c-master";
port = <LP_I2C_NUM_0>;
clock-frequency = <100000>;
clock-source = <LP_I2C_SCLK_DEFAULT>;
pin-sda = <&gpio0 0 GPIO_FLAG_PULL_UP>;
pin-scl = <&gpio0 1 GPIO_FLAG_PULL_UP>;
};
sdmmc0 {
compatible = "espressif,esp32-sdmmc";
pin-clk = <&gpio0 43 GPIO_FLAG_NONE>;
pin-cmd = <&gpio0 44 GPIO_FLAG_NONE>;
pin-d0 = <&gpio0 39 GPIO_FLAG_NONE>;
pin-d1 = <&gpio0 40 GPIO_FLAG_NONE>;
pin-d2 = <&gpio0 41 GPIO_FLAG_NONE>;
pin-d3 = <&gpio0 42 GPIO_FLAG_NONE>;
bus-width = <4>;
slot = <SDMMC_HOST_SLOT_0>;
max-freq-khz = <SDMMC_FREQ_HIGHSPEED>;
on-chip-ldo-chan = <4>;
};
// ES8388 and ES7210
@@ -87,14 +105,6 @@
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>;
};
usbhost0 {
compatible = "espressif,esp32-usbhost";
peripheral-map = <0>;
+2 -1
View File
@@ -1,4 +1,5 @@
#include "UnPhoneFeatures.h"
#include <tactility/device.h>
#include <Tactility/Logger.h>
#include <Tactility/LogMessages.h>
#include <Tactility/Preferences.h>
@@ -160,7 +161,7 @@ static bool unPhonePowerOn() {
bootStats.printInfo();
bootStats.notifyBootStart();
bq24295 = std::make_shared<Bq24295>(I2C_NUM_0);
bq24295 = std::make_shared<Bq24295>(device_find_by_name("i2c_internal"));
unPhoneFeatures = std::make_shared<UnPhoneFeatures>(bq24295);
@@ -2,12 +2,16 @@
#include <Gt911Touch.h>
#include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 38 and interrupt pin is 18
// or INT = NC, schematic and other info floating around is kinda conflicting...
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0,
i2c,
800,
480
);
+145
View File
@@ -0,0 +1,145 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Tactility is an operating system for the ESP32 microcontroller family. It runs on 40+ supported devices (CYD boards, LilyGO, M5Stack, Elecrow, etc.) and includes a desktop simulator. Built with C++23, ESP-IDF, LVGL, and FreeRTOS.
## Build Commands
### Simulator (Linux/macOS, no ESP-IDF needed)
```bash
cmake -B buildsim -G Ninja
ninja -C buildsim # build firmware + tests
./buildsim/Firmware/Tactility # run simulator
```
### ESP32 firmware
```bash
python device.py <device-id> # generate sdkconfig for device (e.g. lilygo-tdeck)
python device.py <device-id> --dev # dev mode: force 4MB partition table
idf.py build # build firmware
idf.py flash monitor # flash and monitor
```
Device IDs are the folder names under `Devices/` (e.g. `lilygo-tdeck`, `m5stack-cores3`, `cyd-2432s028r`).
### Tests
Tests use Doctest and run on simulator (POSIX) target only:
```bash
cmake -B buildsim -G Ninja
ninja -C buildsim build-tests
cd buildsim && ctest # run all tests
./buildsim/Tests/TactilityCore/TactilityCoreTests # run a single test suite
./buildsim/Tests/TactilityKernel/TactilityKernelTests
./buildsim/Tests/Tactility/TactilityTests
./buildsim/Tests/TactilityFreeRtos/TactilityFreeRtosTests
```
## Architecture
### Layer Stack (bottom to top)
- **TactilityKernel** — C API kernel: device/driver/module lifecycle, concurrency primitives (thread, mutex, timer, dispatcher), filesystem, logging. Header convention: `<tactility/*.h>` (lowercase snake_case).
- **TactilityCore** — Former kernel subproject. Deprecated, replaced by TactilityKernel. Contains C++ utilities: Bundle (key-value data), string helpers, file I/O, crypto. Header convention: `<Tactility/*.h>` (UpperCamelCase).
- **TactilityFreeRtos** — Thin C++ wrappers around FreeRTOS primitives.
- **Tactility** — Main OS layer: app framework, service framework, HAL (deprecated, replaced by TactilityKernel), LVGL integration, networking and services (Wi-Fi, BLE, NTP, ESP-NOW), settings, i18n.
- **TactilityC** — C bindings (`tt_*.h`) for TactilityCore and Tactility subprojects, used by side-loaded ELF apps on ESP32. Deprecated, replaced by TactilityKernel.
- **Firmware** — Entry point (`app_main`).
### Device/Driver/Module System (kernel layer, C API)
The kernel uses a Linux-inspired device model:
- **Module** (`struct Module`): loadable unit that registers drivers and hardware. Lifecycle: `module_construct``module_add``module_start`. Each device board and platform is a module.
- **Driver** (`struct Driver`): binds to devices via `compatible` strings (like devicetree). Has `start_device`/`stop_device` callbacks and an `api` pointer for type-specific operations.
- **Device** (`struct Device`): represents hardware. Lifecycle: `device_construct``device_add``device_start`. Has a parent-child tree, driver binding, and locking.
- **DeviceType** (`struct DeviceType`): enables discovering devices by category (e.g. `DISPLAY_TYPE`, `TOUCH_TYPE`, `UART_CONTROLLER_TYPE`).
Devices are defined via **devicetree** `.dts` files in each `Devices/<id>/` folder. A custom devicetree compiler (`Buildscripts/DevicetreeCompiler/compile.py`) generates C code from these files. Each device folder also has a `devicetree.yaml` specifying dependencies and the `.dts` file.
### App Framework
Apps implement `tt::app::App` (or just provide callbacks). Each app has an `AppManifest` with `appId`, `appName`, `appCategory`, and a factory function `createApp`. Apps are registered at startup in `Tactility.cpp`. External apps can be loaded from SD card via `manifest.properties` files, or side-loaded as ELF binaries on ESP32.
### Service Framework
Services implement `tt::service::Service` with a `ServiceManifest`. Services are long-running background processes (GUI, Wi-Fi, loader, statusbar, GPS, etc.).
### HAL Layer
#### Deprecated HAL
Located in Tactility folder.
`tt::hal::Configuration` is declared per-device board (in `Devices/<id>/Source/Configuration.cpp`). It provides `initBoot` for early hardware setup and `createDevices` to instantiate HAL device wrappers (display, touch, power, keyboard, etc.).
#### Current HAL
Located in TactilityKernel. Based on Linux driver subsystems.
#### Driver
A driver generally consists of:
- Registration of driver in parent module (optional)
- YAML bindings in the `bindings/` folder
- An `#include` that is used in the `.dts` file. The include is in `[projectname]/bindings/[drivername].h`
- The driver implementation: a `.cpp` and `.h` file. The implementation is C++, but the header exposes pure C functions.
Drivers can be stored in:
- TactilityKernel
- A subproject in Platforms/ folder
- A subproject in Devices/ folder
- A subproject in Drivers/ folder. This is a kernel module. Naming is lower case and postfixed with `-module`
#### Kernel Modules
Projects that are kernel modules:
1. Declare a `struct Module`
2. Contain a `devicetree.yaml` file that declares a list of dependencies (for parsing the devicetree) and specifies the bindings folder that contains the drivers' YAML definitions. For example:
```yaml
dependencies:
- TactilityKernel
bindings: bindings
```
### Platform Abstraction
- `Platforms/platform-esp32/` — ESP-IDF specific implementations
- `Platforms/platform-posix/` — POSIX simulator implementations (SDL for display)
### Build System
The `tactility_add_module()` CMake macro (in `Buildscripts/module.cmake`) wraps ESP-IDF's `idf_component_register` on ESP32 and standard `add_library` on POSIX, allowing the same source to build for both targets.
`device.py` reads `Devices/<id>/device.properties` and generates the `sdkconfig` file with all necessary ESP-IDF config (target chip, flash size, SPIRAM, LVGL fonts, Bluetooth, USB, etc.).
### LVGL
User interfaces should scale well for everything between very large (e.g. 1280x720) and small (e.g. 135x240) displays. Vertical and horizontal layouts are supported.
## Coding Style
Two conventions coexist; which one to use depends on the project layer:
- **C code** (TactilityKernel, drivers): `lower_snake_case` for files, functions, variables. `UpperCamelCase` for types. Files in `source/`, `include/`, `private/` directories.
- **C++ code** (TactilityCore, Tactility, apps, services): `UpperCamelCase` for files and types. `lowerCamelCase` for functions. Files in `Source/`, `Include/`, `Private/` directories.
Formatting is enforced by `.clang-format` (LLVM-based, 4-space indent, no column limit).
Never throw exceptions — use return types for error handling. Use `enum class` over plain `enum`.
Don't do null checks: caller is responsible for passing valid data.
Pointers are expected to be non-null unless documented otherwise.
## Key Conventions
- `#ifdef ESP_PLATFORM` guards ESP32-specific code; the simulator uses POSIX equivalents.
- The `Drivers/` directory contains hardware drivers (display controllers, touch controllers, PMICs, etc.) — each is its own CMake component.
- `Modules/` contains cross-cutting modules: `hal-device-module` (device lifecycle) and `lvgl-module` (LVGL task management).
- `Data/system/` and `Data/data/` are flashed as FAT filesystem images on ESP32.
- Translations are in `Translations/` as CSV files, generated via `generate.py`.
+7 -1
View File
@@ -4,12 +4,16 @@
### Beitian BH-222Q
Currently not working.
115200 baud, U-Blox 10 clone, defaults to binary messages
https://www.beitian.com/en/pd.jsp?id=1677
### Beitian BN-357ZF
Configure as MTK L76B in GPS settings, or auto-detect as MTK L76K.
9600 baud, L76K clone
https://www.beitian.com/en/sys-pd/879.html
@@ -20,7 +24,9 @@ https://www.beitian.com/en/sys-pd/879.html
https://www.datasheethub.com/gy-neo6mv2-flight-control-gps-module/
### M5Stack GPS
### M5Stack Mini GPS (AT6558)
Configure as ATGM336H.
9600 baud, AT6558 + MAX2659 (LNA)
+2 -18
View File
@@ -12,8 +12,8 @@
## Higher Priority
- Make a root device type so it can be discovered more easily.
- When device.py selects a new device, it should automatically delete the build dirs (build/, cmake-*/) when it detects that the platform has changed.
- Require either SD card present or at least 8MB of flash. This way we can increase firmware size, ensure performance, and have a single location for storing data.
- Consider storing data on SD card in `/tactility` folder (for usage with Launcher)
- Add font design tokens such as "regular", "title" and "smaller". Perhaps via the LVGL kernel module.
- Add kernel listening mechanism so that the root device init can be notified when a device becomes available:
Callback for device/start stop with filtering on device type:
@@ -24,13 +24,11 @@
- DTS: support for #defines
- DTS: support for aliases
- SPI kernel driver
- iomux kernel driver
- Kernel concepts for ELF loading (generic approach for GUI apps, console apps, libraries).
- Fix glitches when installing app via App Hub with 4.3" Waveshare
- TCA9534 keyboards should use interrupts
- GT911 drivers should use interrupts if it's stable
- Fix Cardputer (original): use LV_KEY_NEXT and _PREV in keyboard mapping instead of encoder driver hack (and check GPIO app if it then hangs too)
- Logging with a function that uses std::format
- Expose http::download() and main dispatcher to TactiltyC.
- External app loading: Check the version of Tactility and check ESP target hardware to check for compatibility
Check during installation process, but also when starting (SD card might have old app install from before Tactility OS update)
@@ -59,15 +57,11 @@
- Make WiFi setup app that starts an access point and hosts a webpage to set up the device.
This will be useful for devices without a screen, a small screen or a non-touch screen.
- Unify the way displays are dimmed. Some implementations turn off the display when it's fully dimmed. Make this a separate functionality.
- Try out ILI9342 https://github.com/jbrilha/esp_lcd_ili9342
- All drivers (e.g. display, touch, etc.) should call stop() in their destructor, or at least assert that they should not be running.
- Bug: Turn on WiFi (when testing it wasn't connected/connecting - just active). Open chat. Observe crash.
- Bug: Crash handling app cannot be exited with an EncoderDevice. (current work-around is to manually reset the device)
- I2C app should show error when I2C port is disabled when the scan button was manually pressed
## Lower Priority
- The boot button on some devices can be used as GPIO_NUM_0 at runtime
- Localize all apps
- Support hot-plugging SD card (note: this is not possible if they require the CS pin hack)
- Explore LVGL9's FreeRTOS functionality
@@ -97,7 +91,6 @@
- Audio recording app
- OTA updates
- T-Deck Plus: Create separate device config?
- Support for displays with different DPI. Consider the layer-based system like on Android.
- If present, use LED to show boot/wifi status
- Capacity based on voltage: estimation for various devices uses a linear voltage curve, but it should use a battery discharge curve.
- Wrapper for lvgl slider widget that shows "+" and "-" buttons, and also the value in a label.
@@ -121,14 +114,5 @@
- IR transceiver app
- GPS app
- Investigate CSI https://stevenmhernandez.github.io/ESP32-CSI-Tool/
- Compile unix tools to ELF apps?
- Todo list
- Calendar
- Display touch calibration
- RSS reader
- Static file web server (with option to specify path and port)
- Port TamaFi https://github.com/cifertech/TamaFi
# Notes on firmware size
- adding esp_http_client (with esp_event) added about 100kB
+1 -1
View File
@@ -8,7 +8,7 @@ class Aw9523 : public tt::hal::i2c::I2cDevice {
public:
explicit Aw9523(i2c_port_t port) : I2cDevice(port, AW9523_ADDRESS) {}
explicit Aw9523(::Device* controller) : I2cDevice(controller, AW9523_ADDRESS) {}
std::string getName() const final { return "AW9523"; }
std::string getDescription() const final { return "GPIO expander with LED driver and I2C interface."; }
+2 -2
View File
@@ -1,8 +1,8 @@
#pragma once
#include <axp192/axp192.h>
#include <tactility/device.h>
#include <Tactility/hal/power/PowerDevice.h>
#include <Tactility/hal/i2c/I2c.h>
#include <memory>
@@ -14,7 +14,7 @@ class Axp192 final : public tt::hal::power::PowerDevice {
public:
struct Configuration {
i2c_port_t port;
::Device* controller;
TickType_t readTimeout = 50 / portTICK_PERIOD_MS;
TickType_t writeTimeout = 50 / portTICK_PERIOD_MS;
};
+3 -2
View File
@@ -1,10 +1,11 @@
#include "Axp192.h"
#include <tactility/drivers/i2c_controller.h>
constexpr auto TAG = "Axp192Power";
int32_t Axp192::i2cRead(void* handle, uint8_t address, uint8_t reg, uint8_t* buffer, uint16_t size) {
const auto* device = static_cast<Axp192*>(handle);
if (tt::hal::i2c::masterReadRegister(device->configuration->port, address, reg, buffer, size, device->configuration->readTimeout)) {
if (i2c_controller_read_register(device->configuration->controller, address, reg, buffer, size, device->configuration->readTimeout) == ERROR_NONE) {
return AXP192_OK;
} else {
return 1;
@@ -13,7 +14,7 @@ int32_t Axp192::i2cRead(void* handle, uint8_t address, uint8_t reg, uint8_t* buf
int32_t Axp192::i2cWrite(void* handle, uint8_t address, uint8_t reg, const uint8_t* buffer, uint16_t size) {
const auto* device = static_cast<Axp192*>(handle);
if (tt::hal::i2c::masterWriteRegister(device->configuration->port, address, reg, buffer, size, device->configuration->writeTimeout)) {
if (i2c_controller_write_register(device->configuration->controller, address, reg, buffer, size, device->configuration->writeTimeout) == ERROR_NONE) {
return AXP192_OK;
} else {
return 1;
+1 -1
View File
@@ -54,5 +54,5 @@ bool Axp2101::getVBusVoltage(float& out) const {
}
bool Axp2101::setRegisters(uint8_t* bytePairs, size_t bytePairsSize) const {
return tt::hal::i2c::masterWriteRegisterArray(port, address, bytePairs, bytePairsSize, DEFAULT_TIMEOUT);
return i2c_controller_write_register_array(controller, address, bytePairs, bytePairsSize, DEFAULT_TIMEOUT) == ERROR_NONE;
}
+1 -1
View File
@@ -19,7 +19,7 @@ public:
CHARGE_STATUS_STANDBY = 0b00
};
explicit Axp2101(i2c_port_t port) : I2cDevice(port, AXP2101_ADDRESS) {}
explicit Axp2101(::Device* controller) : I2cDevice(controller, AXP2101_ADDRESS) {}
std::string getName() const override { return "AXP2101"; }
std::string getDescription() const override { return "Power management with I2C interface."; }
+1 -1
View File
@@ -22,7 +22,7 @@ public:
Enabled160s = 0b110000
};
explicit Bq24295(i2c_port_t port) : I2cDevice(port, BQ24295_ADDRESS) {}
explicit Bq24295(::Device* controller) : I2cDevice(controller, BQ24295_ADDRESS) {}
bool getWatchDogTimer(WatchDogTimer& out) const;
bool setWatchDogTimer(WatchDogTimer in) const;
+1 -1
View File
@@ -8,7 +8,7 @@ class Bq25896 final : public tt::hal::i2c::I2cDevice {
public:
explicit Bq25896(i2c_port_t port) : I2cDevice(port, BQ25896_ADDRESS) {
explicit Bq25896(::Device* controller) : I2cDevice(controller, BQ25896_ADDRESS) {
powerOn();
}
+1 -1
View File
@@ -89,7 +89,7 @@ public:
std::string getDescription() const override { return "I2C-controlled CEDV battery fuel gauge"; }
explicit Bq27220(i2c_port_t port) : I2cDevice(port, BQ27220_ADDRESS), accessKey(0xFFFFFFFF) {}
explicit Bq27220(::Device* controller) : I2cDevice(controller, BQ27220_ADDRESS), accessKey(0xFFFFFFFF) {}
bool configureCapacity(uint16_t designCapacity, uint16_t fullChargeCapacity);
bool getVoltage(uint16_t &value);
+1 -1
View File
@@ -5,7 +5,7 @@
static const auto LOGGER = tt::Logger("DRV2605");
Drv2605::Drv2605(i2c_port_t port, bool autoPlayStartupBuzz) : I2cDevice(port, ADDRESS), autoPlayStartupBuzz(autoPlayStartupBuzz) {
Drv2605::Drv2605(::Device* controller, bool autoPlayStartupBuzz) : I2cDevice(controller, ADDRESS), autoPlayStartupBuzz(autoPlayStartupBuzz) {
check(init(), "Initialize DRV2605");
if (autoPlayStartupBuzz) {
+1 -1
View File
@@ -62,7 +62,7 @@ class Drv2605 : public tt::hal::i2c::I2cDevice {
public:
explicit Drv2605(i2c_port_t port, bool autoPlayStartupBuzz = true);
explicit Drv2605(::Device* controller, bool autoPlayStartupBuzz = true);
std::string getName() const final { return "DRV2605"; }
std::string getDescription() const final { return "Haptic driver for ERM/LRA with waveform library & auto-resonance tracking"; }
@@ -3,6 +3,9 @@
#include <Tactility/hal/display/DisplayDriver.h>
#include <esp_lcd_panel_ops.h>
#if CONFIG_SOC_MIPI_DSI_SUPPORTED
#include <esp_lcd_mipi_dsi.h>
#endif
class EspLcdDisplayDriver : public tt::hal::display::DisplayDriver {
@@ -32,4 +35,13 @@ public:
uint16_t getPixelWidth() const override { return hRes; }
uint16_t getPixelHeight() const override { return vRes; }
#if CONFIG_SOC_MIPI_DSI_SUPPORTED
uint8_t getFrameBuffers(void* outBuffers[2]) const override {
if (outBuffers == nullptr) {
return 0;
}
return (esp_lcd_dpi_panel_get_frame_buffer(panelHandle, 2, &outBuffers[0], &outBuffers[1]) == ESP_OK) ? 2 : 0;
}
#endif
};
@@ -6,6 +6,7 @@
#include <esp_lcd_types.h>
#include <esp_lvgl_port_disp.h>
#include <hal/gpio_types.h>
constexpr auto DEFAULT_BUFFER_SIZE = 0;
@@ -18,8 +18,6 @@ bool EspLcdSpiDisplay::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
.user_ctx = nullptr,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.cs_ena_pretrans = 0,
.cs_ena_posttrans = 0,
.flags = {
.dc_high_on_cmd = 0,
.dc_low_on_data = 0,
@@ -48,6 +48,11 @@ bool EspLcdTouch::startLvgl(lv_disp_t* display) {
return false;
}
if (touchHandle == nullptr) {
LOGGER.error("Cannot start LVGL touch: touchHandle is null");
return false;
}
if (touchDriver != nullptr && touchDriver.use_count() > 1) {
LOGGER.warn("TouchDriver is still in use.");
}
+22 -9
View File
@@ -1,31 +1,44 @@
#include "Gt911Touch.h"
#include <Tactility/Logger.h>
#include <Tactility/hal/i2c/I2c.h>
#include <esp_lcd_io_i2c.h>
#include <esp_lcd_touch_gt911.h>
#include <esp_err.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/esp32_i2c.h>
#include <tactility/drivers/esp32_i2c_master.h>
#include <tactility/drivers/i2c_controller.h>
static const auto LOGGER = tt::Logger("GT911");
bool Gt911Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
/**
* When the interrupt pin is low, the address is 0x5D. Otherwise it is 0x14.
* There is not reset pin, and the current driver fails when you only specify the interrupt pin.
* Because of that, we don't use the interrupt pin and we'll simply scan the bus instead:
*/
if (tt::hal::i2c::masterHasDeviceAtAddress(configuration->port, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS)) {
auto* i2c = configuration->i2cController;
if (i2c_controller_has_device_at_address(i2c, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, pdMS_TO_TICKS(10)) == ERROR_NONE) {
io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS;
} else if (tt::hal::i2c::masterHasDeviceAtAddress(configuration->port, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP)) {
} else if (i2c_controller_has_device_at_address(i2c, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP, pdMS_TO_TICKS(10)) == ERROR_NONE) {
io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP;
} else {
LOGGER.error("No device found on I2C bus");
return false;
}
return esp_lcd_new_panel_io_i2c(configuration->port, &io_config, &outHandle) == ESP_OK;
auto* driver = device_get_driver(i2c);
if (driver_is_compatible(driver, "espressif,esp32-i2c")) {
auto port = static_cast<const Esp32I2cConfig*>(i2c->config)->port;
return esp_lcd_new_panel_io_i2c_v1(port, &io_config, &outHandle) == ESP_OK;
} else if (driver_is_compatible(driver, "espressif,esp32-i2c-master")) {
auto bus = esp32_i2c_master_get_bus_handle(i2c);
io_config.scl_speed_hz = esp32_i2c_master_get_clock_frequency(configuration->i2cController);
return esp_lcd_new_panel_io_i2c_v2(bus, &io_config, &outHandle) == ESP_OK;
}
LOGGER.error("Unsupported I2C driver");
return false;
}
bool Gt911Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) {
+5 -4
View File
@@ -2,10 +2,11 @@
#include <Tactility/hal/touch/TouchDevice.h>
#include <Tactility/TactilityCore.h>
#include <driver/i2c.h>
#include <EspLcdTouch.h>
struct Device;
class Gt911Touch final : public EspLcdTouch {
public:
@@ -14,7 +15,7 @@ public:
public:
Configuration(
i2c_port_t port,
::Device* i2cController,
uint16_t xMax,
uint16_t yMax,
bool swapXy = false,
@@ -24,7 +25,7 @@ public:
gpio_num_t pinInterrupt = GPIO_NUM_NC,
unsigned int pinResetLevel = 0,
unsigned int pinInterruptLevel = 0
) : port(port),
) : i2cController(i2cController),
xMax(xMax),
yMax(yMax),
swapXy(swapXy),
@@ -36,7 +37,7 @@ public:
pinInterruptLevel(pinInterruptLevel)
{}
i2c_port_t port;
::Device* i2cController;
uint16_t xMax;
uint16_t yMax;
bool swapXy;
@@ -39,7 +39,6 @@ bool init(gpio_num_t pin, uint32_t frequencyHz, ledc_timer_t timer, ledc_channel
.timer_sel = backlightTimer,
.duty = 0,
.hpoint = 0,
.sleep_mode = LEDC_SLEEP_MODE_NO_ALIVE_NO_PD,
.flags = {
.output_invert = 0
}
+1 -1
View File
@@ -45,7 +45,7 @@ public:
std::string getDescription() const final { return "I2C-controlled keyboard scan IC"; }
explicit Tca8418(i2c_port_t port) : I2cDevice(port, TCA8418_ADDRESS) {
explicit Tca8418(::Device* controller) : I2cDevice(controller, TCA8418_ADDRESS) {
delta_micros = 0;
last_update_micros = 0;
this_update_micros = 0;
+2 -3
View File
@@ -29,7 +29,6 @@ dependencies:
espressif/esp_lcd_touch_ft5x06: "1.0.6~1"
espressif/esp_io_expander: "1.0.1"
espressif/esp_io_expander_tca95xx_16bit: "1.0.1"
espressif/esp_lcd_axs15231b: "2.0.2"
lambage/esp_lcd_touch_ft6336u: "1.0.8"
espressif/esp_lcd_st7701:
version: "1.1.3"
@@ -64,7 +63,7 @@ dependencies:
espressif/esp_tinyusb:
version: "1.7.6~1"
rules:
- if: "target == esp32s3"
- if: "target in [esp32s3, esp32p4]"
espressif/esp_lvgl_port: "2.7.2"
lvgl/lvgl: "9.3.0"
epdiy:
@@ -81,5 +80,5 @@ dependencies:
version: "1.1.4"
rules:
- if: "target in [esp32s3, esp32p4]"
idf: '5.5.2'
idf: '>=5.2.0'
@@ -0,0 +1,29 @@
description: ESP32 Grove Port
compatible: "espressif,esp32-grove"
properties:
defaultMode:
type: int
required: true
description: "One of enum Esp32GroveMode"
pinSdaTx:
type: phandle-array
required: true
description: SDA (I2C) or TX (UART) pin
pinSclRx:
type: phandle-array
required: true
description: SCL (I2C) or RX (UART) pin
uartPort:
type: int
required: true
description: UART port number
i2cPort:
type: int
required: true
description: I2C port number
i2cClockFrequency:
type: int
required: true
description: I2C clock frequency
@@ -0,0 +1,29 @@
description: ESP32 I2C Controller
include: ["i2c-controller.yaml"]
compatible: "espressif,esp32-i2c-master"
properties:
port:
type: int
required: true
description: |
The port number, defined by i2c_port_t.
Depending on the hardware, these values are available: I2C_NUM_0, I2C_NUM_1, LP_I2C_NUM_0
clock-frequency:
type: int
required: true
description: Initial clock frequency in Hz
clock-source:
type: int
default: 0
description: |
Clock source for the I2C peripheral.
If not specified, a default clock source will be used.
pin-sda:
type: phandle-array
required: true
pin-scl:
type: phandle-array
required: true
@@ -10,7 +10,7 @@ properties:
required: true
description: |
The port number, defined by i2c_port_t.
Depending on the hardware, these values are available: I2C_NUM_0, I2C_NUM_1, LP_I2C_NUM_0
Depending on the hardware, these values are available: I2C_NUM_0, I2C_NUM_1
clock-frequency:
type: int
required: true
@@ -43,6 +43,16 @@ properties:
type: int
required: true
description: Bus width in bits
slot:
type: int
default: 1
enum: [0, 1]
description: SDMMC host slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1). On ESP32-P4, slot 0 uses the dedicated (non-GPIO-matrix) pins.
max-freq-khz:
type: int
default: 20000
minimum: 1
description: Maximum SDMMC clock frequency in kHz (e.g. 40000 for SDMMC_FREQ_HIGHSPEED)
wp-active-high:
type: boolean
default: false
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <tactility/drivers/esp32_grove.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(esp32_grove, struct Esp32GroveConfig)
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <tactility/drivers/esp32_i2c_master.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(esp32_i2c_master, struct Esp32I2cMasterConfig)
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <driver/uart.h>
#include <hal/i2c_types.h>
#include <tactility/drivers/gpio.h>
#include <tactility/drivers/grove.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Esp32GroveConfig {
enum GroveMode defaultMode;
struct GpioPinSpec pinSdaTx;
struct GpioPinSpec pinSclRx;
uart_port_t uartPort;
i2c_port_t i2cPort;
uint32_t i2cClockFrequency;
};
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/drivers/gpio.h>
#include <tactility/device.h>
#include <driver/i2c_types.h>
#include <driver/i2c_master.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Device;
struct Esp32I2cMasterConfig {
i2c_port_num_t port;
uint32_t clockFrequency;
int32_t clkSource;
struct GpioPinSpec pinSda;
struct GpioPinSpec pinScl;
};
/**
* Returns the i2c_master_bus_handle_t for an esp32_i2c_master Device.
* The device must be started. Returns NULL if the device has no driver data.
*/
i2c_master_bus_handle_t esp32_i2c_master_get_bus_handle(struct Device* device);
/**
* Returns the SCL clock frequency (Hz) configured for an esp32_i2c_master Device.
* Reads directly from the device tree config, so the device does not need to be started.
*/
uint32_t esp32_i2c_master_get_clock_frequency(struct Device* device);
#ifdef __cplusplus
}
#endif
@@ -3,6 +3,7 @@
#include <soc/soc_caps.h>
#if SOC_SDMMC_HOST_SUPPORTED
#include <driver/sdmmc_default_configs.h>
#include <sd_protocol_types.h>
#include <stdbool.h>
#include <stdint.h>
@@ -26,6 +27,8 @@ struct Esp32SdmmcConfig {
struct GpioPinSpec pin_cd;
struct GpioPinSpec pin_wp;
uint8_t bus_width;
int32_t slot;
int32_t max_freq_khz;
bool wp_active_high;
bool enable_uhs;
bool pullups;
@@ -167,4 +167,11 @@ bool ble_hci_gate_wait_idle(int max_ms);
#endif
#endif // CONFIG_ESP_HOSTED_ENABLED
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#endif // CONFIG_BT_NIMBLE_ENABLED
@@ -1,5 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
#include <driver/gpio.h>
#include <soc/soc.h>
#include <soc/gpio_periph.h>
#include <soc/gpio_struct.h>
#include <soc/io_mux_reg.h>
#include <tactility/driver.h>
#include <tactility/drivers/esp32_gpio.h>
@@ -65,30 +69,34 @@ static error_t set_flags(GpioDescriptor* descriptor, gpio_flags_t flags) {
}
static error_t get_flags(GpioDescriptor* descriptor, gpio_flags_t* flags) {
gpio_io_config_t esp_config;
if (gpio_get_io_config(static_cast<gpio_num_t>(descriptor->pin), &esp_config) != ESP_OK) {
return ERROR_RESOURCE;
}
auto pin = static_cast<gpio_num_t>(descriptor->pin);
gpio_flags_t output = 0;
if (esp_config.pu) {
// Read pull-up, pull-down, input-enable from IO MUX register
uint32_t pin_mux = REG_READ(GPIO_PIN_MUX_REG[pin]);
if (pin_mux & FUN_PU) {
output |= GPIO_FLAG_PULL_UP;
}
if (esp_config.pd) {
if (pin_mux & FUN_PD) {
output |= GPIO_FLAG_PULL_DOWN;
}
if (esp_config.ie) {
if (pin_mux & FUN_IE) {
output |= GPIO_FLAG_DIRECTION_INPUT;
}
if (esp_config.oe) {
// Read output-enable from GPIO matrix
bool oe = false;
if (pin < 32) {
oe = (GPIO.enable & (1UL << pin)) != 0;
} else {
oe = (GPIO.enable1.data & (1UL << (pin - 32))) != 0;
}
if (oe) {
output |= GPIO_FLAG_DIRECTION_OUTPUT;
}
if (esp_config.oe_inv) {
// Read output-inversion from GPIO matrix
if (GPIO.func_out_sel_cfg[pin].inv_sel) {
output |= GPIO_FLAG_ACTIVE_LOW;
}
@@ -0,0 +1,228 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/esp32_grove.h>
#include <tactility/drivers/esp32_i2c.h>
#include <tactility/drivers/esp32_i2c_master.h>
#include <tactility/drivers/esp32_uart.h>
#include <tactility/log.h>
#include <cstdio>
#include <cstring>
#include <new>
#define TAG "esp32_grove"
struct Esp32GroveInternal {
Device* child_device = nullptr;
void* child_config = nullptr;
char* child_name = nullptr;
GroveMode current_mode = GROVE_MODE_DISABLED;
};
#define GET_CONFIG(device) ((const struct Esp32GroveConfig*)device->config)
#define GET_DATA(device) ((struct Esp32GroveInternal*)device_get_driver_data(device))
extern "C" {
static error_t stop_child(Device* device) {
auto* data = GET_DATA(device);
if (!data) return ERROR_NONE;
if (data->child_device) {
if (data->child_device->internal) {
if (device_is_added(data->child_device)) {
if (device_is_ready(data->child_device)) {
if (device_stop(data->child_device) != ERROR_NONE) {
LOG_E(TAG, "%s: failed to stop child device", device->name);
return ERROR_RESOURCE;
}
}
if (device_remove(data->child_device) != ERROR_NONE) {
LOG_E(TAG, "%s: failed to remove child device", device->name);
return ERROR_RESOURCE;
}
}
check(device_destruct(data->child_device) == ERROR_NONE);
}
delete data->child_device;
data->child_device = nullptr;
}
if (data->child_config) {
if (data->current_mode == GROVE_MODE_UART) {
delete static_cast<Esp32UartConfig*>(data->child_config);
} else if (data->current_mode == GROVE_MODE_I2C) {
delete static_cast<Esp32I2cMasterConfig*>(data->child_config);
}
data->child_config = nullptr;
}
delete[] data->child_name;
data->child_name = nullptr;
data->current_mode = GROVE_MODE_DISABLED;
return ERROR_NONE;
}
static error_t start_child(Device* device, GroveMode mode) {
const auto* config = GET_CONFIG(device);
auto* data = GET_DATA(device);
if (mode == GROVE_MODE_DISABLED) {
LOG_I(TAG, "%s: Grove port disabled", device->name);
data->current_mode = GROVE_MODE_DISABLED;
return ERROR_NONE;
}
data->child_device = new(std::nothrow) Device();
if (!data->child_device) {
return ERROR_OUT_OF_MEMORY;
}
std::memset(data->child_device, 0, sizeof(Device));
size_t name_len = std::strlen(device->name) + 10;
data->child_name = new(std::nothrow) char[name_len];
if (!data->child_name) {
delete data->child_device;
data->child_device = nullptr;
return ERROR_OUT_OF_MEMORY;
}
data->child_device->parent = device;
const char* compatible = nullptr;
if (mode == GROVE_MODE_UART) {
// Device name
std::snprintf(data->child_name, name_len, "%s_uart", device->name);
data->child_device->name = data->child_name;
// Device config
auto* uart_cfg = new(std::nothrow) struct Esp32UartConfig();
if (!uart_cfg) {
delete[] data->child_name;
data->child_name = nullptr;
delete data->child_device;
data->child_device = nullptr;
return ERROR_OUT_OF_MEMORY;
}
std::memset(uart_cfg, 0, sizeof(Esp32UartConfig));
uart_cfg->port = config->uartPort;
uart_cfg->pin_rx = config->pinSclRx;
uart_cfg->pin_tx = config->pinSdaTx;
uart_cfg->pin_cts = GPIO_PIN_SPEC_NONE;
uart_cfg->pin_rts = GPIO_PIN_SPEC_NONE;
data->child_config = uart_cfg;
compatible = "espressif,esp32-uart";
LOG_I(TAG, "%s: starting UART mode on port %d", device->name, (int)uart_cfg->port);
} else if (mode == GROVE_MODE_I2C) {
// Device name
std::snprintf(data->child_name, name_len, "%s_i2c", device->name);
data->child_device->name = data->child_name;
// Device config
auto* i2c_cfg = new (std::nothrow) struct Esp32I2cMasterConfig();
if (!i2c_cfg) {
delete[] data->child_name;
data->child_name = nullptr;
delete data->child_device;
data->child_device = nullptr;
return ERROR_OUT_OF_MEMORY;
}
std::memset(i2c_cfg, 0, sizeof(Esp32I2cMasterConfig));
i2c_cfg->port = static_cast<i2c_port_num_t>(config->i2cPort);
i2c_cfg->clockFrequency = config->i2cClockFrequency;
i2c_cfg->pinSda = config->pinSdaTx;
i2c_cfg->pinScl = config->pinSclRx;
// New driver seems to require pull-up setting
i2c_cfg->pinSda.flags |= GPIO_FLAG_PULL_UP;
i2c_cfg->pinScl.flags |= GPIO_FLAG_PULL_UP;
i2c_cfg->clkSource = 0; // Default
data->child_config = i2c_cfg;
compatible = "espressif,esp32-i2c-master";
LOG_I(TAG, "%s: starting I2C mode on port %d", device->name, (int)config->i2cPort);
} else {
LOG_E(TAG, "%s: unknown mode %d", device->name, mode);
if (data->child_name != nullptr) {
delete[] data->child_name;
data->child_name = nullptr;
}
delete data->child_device;
data->child_device = nullptr;
return ERROR_INVALID_ARGUMENT;
}
data->child_device->config = data->child_config;
error_t err = device_construct_add_start(data->child_device, compatible);
if (err != ERROR_NONE) {
LOG_E(TAG, "%s: failed to start child device: %d", device->name, err);
stop_child(device);
return err;
}
data->current_mode = mode;
return ERROR_NONE;
}
static error_t start_device(Device* device) {
const auto* config = GET_CONFIG(device);
auto* data = new(std::nothrow) Esp32GroveInternal();
if (!data) return ERROR_OUT_OF_MEMORY;
device_set_driver_data(device, data);
if (start_child(device, config->defaultMode) != ERROR_NONE) {
device_set_driver_data(device, nullptr);
delete data;
return ERROR_RESOURCE;
}
return ERROR_NONE;
}
static error_t stop_device(Device* device) {
auto* data = GET_DATA(device);
if (!data) return ERROR_NONE;
stop_child(device);
delete data;
device_set_driver_data(device, nullptr);
return ERROR_NONE;
}
static error_t esp32_grove_set_mode(Device* device, enum GroveMode mode) {
auto* data = GET_DATA(device);
if (data->current_mode == mode) return ERROR_NONE;
error_t err = stop_child(device);
if (err != ERROR_NONE) return err;
return start_child(device, mode);
}
static error_t esp32_grove_get_mode(Device* device, enum GroveMode* mode) {
auto* data = GET_DATA(device);
*mode = data->current_mode;
return ERROR_NONE;
}
static const GroveApi esp32_grove_api = {
.set_mode = esp32_grove_set_mode,
.get_mode = esp32_grove_get_mode
};
extern Module platform_esp32_module;
Driver esp32_grove_driver = {
.name = "esp32_grove",
.compatible = (const char*[]) { "espressif,esp32-grove", nullptr },
.start_device = start_device,
.stop_device = stop_device,
.api = &esp32_grove_api,
.device_type = &GROVE_TYPE,
.owner = &platform_esp32_module,
.internal = nullptr
};
} // extern "C"
@@ -175,16 +175,12 @@ static error_t start(Device* device) {
check(gpio_descriptor_get_native_pin_number(sda_descriptor, &sda_pin) == ERROR_NONE);
check(gpio_descriptor_get_native_pin_number(scl_descriptor, &scl_pin) == ERROR_NONE);
gpio_flags_t sda_flags, scl_flags;
check(gpio_descriptor_get_flags(sda_descriptor, &sda_flags) == ERROR_NONE);
check(gpio_descriptor_get_flags(scl_descriptor, &scl_flags) == ERROR_NONE);
i2c_config_t esp_config = {
.mode = I2C_MODE_MASTER,
.sda_io_num = sda_pin,
.scl_io_num = scl_pin,
.sda_pullup_en = (sda_flags & GPIO_FLAG_PULL_UP) != 0,
.scl_pullup_en = (scl_flags & GPIO_FLAG_PULL_UP) != 0,
.sda_pullup_en = (sda_spec.flags & GPIO_FLAG_PULL_UP) != 0,
.scl_pullup_en = (scl_spec.flags & GPIO_FLAG_PULL_UP) != 0,
.master {
.clk_speed = dts_config->clockFrequency
},
@@ -0,0 +1,352 @@
// SPDX-License-Identifier: Apache-2.0
#include <driver/i2c_master.h>
#include <new>
#include <cstring>
#include <cstdlib>
#include <tactility/error_esp32.h>
#include <tactility/driver.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/drivers/esp32_i2c_master.h>
#include <tactility/log.h>
#include <tactility/time.h>
#define TAG "esp32_i2c_master"
struct Esp32I2cMasterInternal {
Mutex mutex {};
GpioDescriptor* sda_descriptor = nullptr;
GpioDescriptor* scl_descriptor = nullptr;
i2c_master_bus_handle_t bus_handle = nullptr;
i2c_master_dev_handle_t dev_handle = nullptr;
int current_address = -1;
Esp32I2cMasterInternal(GpioDescriptor* sda_descriptor, GpioDescriptor* scl_descriptor) :
sda_descriptor(sda_descriptor),
scl_descriptor(scl_descriptor)
{
mutex_construct(&mutex);
}
~Esp32I2cMasterInternal() {
mutex_destruct(&mutex);
}
};
#define GET_CONFIG(device) ((Esp32I2cMasterConfig*)device->config)
#define GET_DATA(device) ((Esp32I2cMasterInternal*)device_get_driver_data(device))
#define lock(data) mutex_lock(&data->mutex);
#define unlock(data) mutex_unlock(&data->mutex);
struct MyI2cMasterDev {
void* master_bus;
uint16_t device_address;
};
static esp_err_t my_i2c_master_device_change_address(i2c_master_dev_handle_t i2c_dev, uint16_t new_device_address, int timeout_ms) {
if (i2c_dev == nullptr) return ESP_ERR_INVALID_ARG;
auto* dev = reinterpret_cast<MyI2cMasterDev*>(i2c_dev);
dev->device_address = new_device_address;
return ESP_OK;
}
static esp_err_t my_i2c_master_transmit_multi_buffer(i2c_master_dev_handle_t dev_handle, uint8_t reg, const uint8_t* data, uint16_t data_size, int timeout_ms) {
uint32_t total_size = data_size + 1;
if (total_size <= 128) {
uint8_t temp_buf[128];
temp_buf[0] = reg;
if (data_size > 0 && data != nullptr) {
std::memcpy(temp_buf + 1, data, data_size);
}
return i2c_master_transmit(dev_handle, temp_buf, total_size, timeout_ms);
} else {
uint8_t* temp_buf = (uint8_t*)std::malloc(total_size);
if (temp_buf == nullptr) {
return ESP_ERR_NO_MEM;
}
temp_buf[0] = reg;
if (data_size > 0 && data != nullptr) {
std::memcpy(temp_buf + 1, data, data_size);
}
esp_err_t ret = i2c_master_transmit(dev_handle, temp_buf, total_size, timeout_ms);
std::free(temp_buf);
return ret;
}
}
// Switches the device's target address only when it differs from the currently configured one.
static esp_err_t ensure_address(Esp32I2cMasterInternal* driver_data, uint8_t address, int timeout_ms) {
if (driver_data->current_address == address) {
return ESP_OK;
}
esp_err_t esp_error = my_i2c_master_device_change_address(driver_data->dev_handle, address, timeout_ms);
if (esp_error == ESP_OK) {
driver_data->current_address = address;
}
return esp_error;
}
extern "C" {
static int ticks_to_ms(TickType_t ticks) {
if (ticks == portMAX_DELAY) return -1;
return (int)pdTICKS_TO_MS(ticks);
}
static error_t read(Device* device, uint8_t address, uint8_t* data, size_t data_size, TickType_t timeout) {
if (xPortInIsrContext()) return ERROR_ISR_STATUS;
if (data_size == 0) return ERROR_INVALID_ARGUMENT;
auto* driver_data = GET_DATA(device);
int timeout_ms = ticks_to_ms(timeout);
lock(driver_data);
esp_err_t esp_error = ensure_address(driver_data, address, timeout_ms);
if (esp_error != ESP_OK) {
LOG_E(TAG, "change_address(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
} else {
esp_error = i2c_master_receive(driver_data->dev_handle, data, data_size, timeout_ms);
if (esp_error != ESP_OK) {
LOG_E(TAG, "receive(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
}
}
unlock(driver_data);
return esp_err_to_error(esp_error);
}
static error_t write(Device* device, uint8_t address, const uint8_t* data, uint16_t data_size, TickType_t timeout) {
if (xPortInIsrContext()) return ERROR_ISR_STATUS;
if (data_size == 0) return ERROR_INVALID_ARGUMENT;
auto* driver_data = GET_DATA(device);
int timeout_ms = ticks_to_ms(timeout);
lock(driver_data);
esp_err_t esp_error = ensure_address(driver_data, address, timeout_ms);
if (esp_error != ESP_OK) {
LOG_E(TAG, "change_address(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
} else {
esp_error = i2c_master_transmit(driver_data->dev_handle, data, data_size, timeout_ms);
if (esp_error != ESP_OK) {
LOG_E(TAG, "transmit(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
}
}
unlock(driver_data);
return esp_err_to_error(esp_error);
}
static error_t write_read(Device* device, uint8_t address, const uint8_t* write_data, size_t write_data_size, uint8_t* read_data, size_t read_data_size, TickType_t timeout) {
if (xPortInIsrContext()) return ERROR_ISR_STATUS;
if (write_data_size == 0 || read_data_size == 0) return ERROR_INVALID_ARGUMENT;
auto* driver_data = GET_DATA(device);
int timeout_ms = ticks_to_ms(timeout);
lock(driver_data);
esp_err_t esp_error = ensure_address(driver_data, address, timeout_ms);
if (esp_error != ESP_OK) {
LOG_E(TAG, "change_address(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
} else {
esp_error = i2c_master_transmit_receive(driver_data->dev_handle, write_data, write_data_size, read_data, read_data_size, timeout_ms);
if (esp_error != ESP_OK) {
LOG_E(TAG, "transmit_receive(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
}
}
unlock(driver_data);
return esp_err_to_error(esp_error);
}
static error_t read_register(Device* device, uint8_t address, uint8_t reg, uint8_t* data, size_t data_size, TickType_t timeout) {
if (xPortInIsrContext()) return ERROR_ISR_STATUS;
if (data_size == 0) return ERROR_INVALID_ARGUMENT;
auto* driver_data = GET_DATA(device);
int timeout_ms = ticks_to_ms(timeout);
lock(driver_data);
esp_err_t esp_error = ensure_address(driver_data, address, timeout_ms);
if (esp_error != ESP_OK) {
LOG_E(TAG, "change_address(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
} else {
esp_error = i2c_master_transmit_receive(driver_data->dev_handle, &reg, 1, data, data_size, timeout_ms);
if (esp_error != ESP_OK) {
LOG_E(TAG, "read_register(0x%02X, reg=0x%02X) failed: %s", address, reg, esp_err_to_name(esp_error));
}
}
unlock(driver_data);
return esp_err_to_error(esp_error);
}
static error_t write_register(Device* device, uint8_t address, uint8_t reg, const uint8_t* data, uint16_t data_size, TickType_t timeout) {
if (xPortInIsrContext()) return ERROR_ISR_STATUS;
if (data_size == 0) return ERROR_INVALID_ARGUMENT;
auto* driver_data = GET_DATA(device);
int timeout_ms = ticks_to_ms(timeout);
lock(driver_data);
esp_err_t esp_error = ensure_address(driver_data, address, timeout_ms);
if (esp_error != ESP_OK) {
LOG_E(TAG, "change_address(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
} else {
esp_error = my_i2c_master_transmit_multi_buffer(driver_data->dev_handle, reg, data, data_size, timeout_ms);
if (esp_error != ESP_OK) {
LOG_E(TAG, "write_register(0x%02X, reg=0x%02X) failed: %s", address, reg, esp_err_to_name(esp_error));
}
}
unlock(driver_data);
return esp_err_to_error(esp_error);
}
static error_t probe(Device* device, uint8_t address, TickType_t timeout) {
if (xPortInIsrContext()) return ERROR_ISR_STATUS;
auto* driver_data = GET_DATA(device);
int timeout_ms = ticks_to_ms(timeout);
lock(driver_data);
esp_err_t esp_error = i2c_master_probe(driver_data->bus_handle, address, timeout_ms);
if (esp_error == ESP_ERR_NOT_FOUND) {
// Expected outcome when no device acks - e.g. hot-plug attach polling
LOG_D(TAG, "probe(0x%02X): not found", address);
} else if (esp_error != ESP_OK) {
LOG_E(TAG, "probe(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
}
unlock(driver_data);
return esp_err_to_error(esp_error);
}
static error_t start(Device* device) {
ESP_LOGI(TAG, "start %s", device->name);
auto dts_config = GET_CONFIG(device);
auto& sda_spec = dts_config->pinSda;
auto& scl_spec = dts_config->pinScl;
auto* sda_descriptor = gpio_descriptor_acquire(sda_spec.gpio_controller, sda_spec.pin, GPIO_OWNER_GPIO);
if (!sda_descriptor) {
LOG_E(TAG, "Failed to acquire pin %u", sda_spec.pin);
return ERROR_RESOURCE;
}
auto* scl_descriptor = gpio_descriptor_acquire(scl_spec.gpio_controller, scl_spec.pin, GPIO_OWNER_GPIO);
if (!scl_descriptor) {
LOG_E(TAG, "Failed to acquire pin %u", scl_spec.pin);
gpio_descriptor_release(sda_descriptor);
return ERROR_RESOURCE;
}
gpio_num_t sda_pin, scl_pin;
check(gpio_descriptor_get_native_pin_number(sda_descriptor, &sda_pin) == ERROR_NONE);
check(gpio_descriptor_get_native_pin_number(scl_descriptor, &scl_pin) == ERROR_NONE);
i2c_master_bus_config_t bus_config = {
.i2c_port = dts_config->port,
.sda_io_num = sda_pin,
.scl_io_num = scl_pin,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags = {
.enable_internal_pullup = ((sda_spec.flags & GPIO_FLAG_PULL_UP) != 0) || ((scl_spec.flags & GPIO_FLAG_PULL_UP) != 0),
}
};
#if SOC_LP_I2C_SUPPORTED
if (dts_config->port == LP_I2C_NUM_0) {
bus_config.lp_source_clk = (dts_config->clkSource == 0) ? LP_I2C_SCLK_DEFAULT : static_cast<lp_i2c_clock_source_t>(dts_config->clkSource);
} else {
bus_config.clk_source = (dts_config->clkSource == 0) ? I2C_CLK_SRC_DEFAULT : static_cast<i2c_clock_source_t>(dts_config->clkSource);
}
#else
bus_config.clk_source = (dts_config->clkSource == 0) ? I2C_CLK_SRC_DEFAULT : static_cast<i2c_clock_source_t>(dts_config->clkSource);
#endif
i2c_master_bus_handle_t bus_handle;
esp_err_t error = i2c_new_master_bus(&bus_config, &bus_handle);
if (error != ESP_OK) {
LOG_E(TAG, "Failed to create I2C bus at port %d: %s", (int)dts_config->port, esp_err_to_name(error));
gpio_descriptor_release(sda_descriptor);
gpio_descriptor_release(scl_descriptor);
return ERROR_RESOURCE;
}
i2c_device_config_t dev_config = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = 0, // Will be changed at runtime
.scl_speed_hz = dts_config->clockFrequency,
.scl_wait_us = 0,
.flags = {
.disable_ack_check = 0,
}
};
i2c_master_dev_handle_t dev_handle;
error = i2c_master_bus_add_device(bus_handle, &dev_config, &dev_handle);
if (error != ESP_OK) {
LOG_E(TAG, "Failed to add I2C device: %s", esp_err_to_name(error));
i2c_del_master_bus(bus_handle);
gpio_descriptor_release(sda_descriptor);
gpio_descriptor_release(scl_descriptor);
return ERROR_RESOURCE;
}
auto* data = new(std::nothrow) Esp32I2cMasterInternal(sda_descriptor, scl_descriptor);
if (data == nullptr) {
i2c_master_bus_rm_device(dev_handle);
i2c_del_master_bus(bus_handle);
gpio_descriptor_release(sda_descriptor);
gpio_descriptor_release(scl_descriptor);
return ERROR_OUT_OF_MEMORY;
}
data->bus_handle = bus_handle;
data->dev_handle = dev_handle;
device_set_driver_data(device, data);
return ERROR_NONE;
}
static error_t stop(Device* device) {
ESP_LOGI(TAG, "stop %s", device->name);
auto* driver_data = GET_DATA(device);
i2c_master_bus_rm_device(driver_data->dev_handle);
i2c_del_master_bus(driver_data->bus_handle);
gpio_descriptor_release(driver_data->sda_descriptor);
gpio_descriptor_release(driver_data->scl_descriptor);
device_set_driver_data(device, nullptr);
delete driver_data;
return ERROR_NONE;
}
i2c_master_bus_handle_t esp32_i2c_master_get_bus_handle(struct Device* device) {
return GET_DATA(device)->bus_handle;
}
uint32_t esp32_i2c_master_get_clock_frequency(struct Device* device) {
return GET_CONFIG(device)->clockFrequency;
}
static constexpr I2cControllerApi ESP32_I2C_MASTER_API = {
.read = read,
.write = write,
.write_read = write_read,
.read_register = read_register,
.write_register = write_register,
.probe = probe
};
extern Module platform_esp32_module;
Driver esp32_i2c_master_driver = {
.name = "esp32_i2c_master",
.compatible = (const char*[]) { "espressif,esp32-i2c", "espressif,esp32-i2c-master", nullptr },
.start_device = start,
.stop_device = stop,
.api = &ESP32_I2C_MASTER_API,
.device_type = &I2C_CONTROLLER_TYPE,
.owner = &platform_esp32_module,
.internal = nullptr
};
} // extern "C"
@@ -18,6 +18,7 @@
#include <tactility/drivers/esp32_i2s.h>
#include <new>
#include <cstring>
#define TAG "esp32_i2s"

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