Compare commits

...

7 Commits

Author SHA1 Message Date
Ken Van Hoeylandt
eebe5b03fa Fix for I2C 2026-06-18 21:38:59 +02:00
Ken Van Hoeylandt
780b8accfb USB host warning fix 2026-06-18 21:28:14 +02:00
Ken Van Hoeylandt
8e63ceb460 I2X fixes 2026-06-18 21:28:10 +02:00
Ken Van Hoeylandt
9836342e7f Fix 2026-06-18 20:40:30 +02:00
Ken Van Hoeylandt
15056b8fe1 Build fix & docs added. 2026-06-18 18:55:46 +02:00
Ken Van Hoeylandt
168ec34c16 Fix for build 2026-06-18 18:50:55 +02:00
Ken Van Hoeylandt
c39b3d95a8 Refactor I2C 2026-06-18 18:39:43 +02:00
35 changed files with 246 additions and 164 deletions

3
.gitignore vendored
View File

@ -23,3 +23,6 @@ dependencies.lock
sdkconfig.board.*.dev sdkconfig.board.*.dev
.tactility/ .tactility/
.caveman.json
.ai/mcp

1
CLAUDE.md Symbolic link
View File

@ -0,0 +1 @@
Documentation/README.md

View File

@ -3,11 +3,15 @@
#include <Gt911Touch.h> #include <Gt911Touch.h>
#include <PwmBacklight.h> #include <PwmBacklight.h>
#include <RgbDisplay.h> #include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() { std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 41 and interrupt pin is 40 // 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>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0, i2c,
800, 800,
480 480
); );

View File

@ -3,10 +3,14 @@
#include <Gt911Touch.h> #include <Gt911Touch.h>
#include <Ili934xDisplay.h> #include <Ili934xDisplay.h>
#include <PwmBacklight.h> #include <PwmBacklight.h>
#include <tactility/check.h>
#include <tactility/device.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() { 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>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0, i2c,
LCD_HORIZONTAL_RESOLUTION, LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION LCD_VERTICAL_RESOLUTION
); );

View File

@ -3,10 +3,14 @@
#include <Gt911Touch.h> #include <Gt911Touch.h>
#include <PwmBacklight.h> #include <PwmBacklight.h>
#include <St7796Display.h> #include <St7796Display.h>
#include <tactility/check.h>
#include <tactility/device.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() { 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>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0, i2c,
LCD_HORIZONTAL_RESOLUTION, LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION LCD_VERTICAL_RESOLUTION
); );

View File

@ -3,6 +3,8 @@
#include <Gt911Touch.h> #include <Gt911Touch.h>
#include <PwmBacklight.h> #include <PwmBacklight.h>
#include <Tactility/Logger.h> #include <Tactility/Logger.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <driver/gpio.h> #include <driver/gpio.h>
#include <esp_err.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() { std::shared_ptr<tt::hal::touch::TouchDevice> St7701Display::getTouchDevice() {
if (touchDevice == nullptr) { if (touchDevice == nullptr) {
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0, i2c,
480, 480,
480 480
); );

View File

@ -3,12 +3,16 @@
#include <Gt911Touch.h> #include <Gt911Touch.h>
#include <PwmBacklight.h> #include <PwmBacklight.h>
#include <RgbDisplay.h> #include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() { std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 38 and interrupt pin is 18 // 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... // 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>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0, i2c,
800, 800,
480 480
); );

View File

@ -3,10 +3,14 @@
#include <Gt911Touch.h> #include <Gt911Touch.h>
#include <PwmBacklight.h> #include <PwmBacklight.h>
#include <Ili9488Display.h> #include <Ili9488Display.h>
#include <tactility/check.h>
#include <tactility/device.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() { 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>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0, i2c,
320, 320,
480 480
); );

View File

@ -2,12 +2,16 @@
#include <Gt911Touch.h> #include <Gt911Touch.h>
#include <RgbDisplay.h> #include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() { std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 38 and interrupt pin is 18 // 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... // 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>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0, i2c,
800, 800,
480 480
); );

View File

@ -3,12 +3,16 @@
#include <Gt911Touch.h> #include <Gt911Touch.h>
#include <PwmBacklight.h> #include <PwmBacklight.h>
#include <RgbDisplay.h> #include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() { std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 38 and interrupt pin is 18 // 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... // 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>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0, i2c,
800, 800,
480 480
); );

View File

@ -5,21 +5,22 @@
#include <PwmBacklight.h> #include <PwmBacklight.h>
#include <Tactility/Logger.h> #include <Tactility/Logger.h>
#include <Tactility/Mutex.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_RESET = GPIO_NUM_0; // Match P4 EV board reset line
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_23; constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_23;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 1024; constexpr auto LCD_HORIZONTAL_RESOLUTION = 1024;
constexpr auto LCD_VERTICAL_RESOLUTION = 600; 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_RESET = GPIO_NUM_NC;
constexpr auto TOUCH_PIN_INTERRUPT = GPIO_NUM_NC; constexpr auto TOUCH_PIN_INTERRUPT = GPIO_NUM_NC;
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() { 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>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
TOUCH_I2C_PORT, i2c,
LCD_HORIZONTAL_RESOLUTION, LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION, LCD_VERTICAL_RESOLUTION,
false, // swapXY false, // swapXY

View File

@ -3,12 +3,16 @@
#include <Gt911Touch.h> #include <Gt911Touch.h>
#include <PwmBacklight.h> #include <PwmBacklight.h>
#include <RgbDisplay.h> #include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() { std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 38 and interrupt pin is 18 // 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... // 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>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0, i2c,
800, 800,
480 480
); );

View File

@ -7,16 +7,20 @@
#include <Tactility/hal/Configuration.h> #include <Tactility/hal/Configuration.h>
#include <Tactility/lvgl/LvglSync.h> #include <Tactility/lvgl/LvglSync.h>
#include <tactility/check.h>
#include <tactility/device.h>
bool initBoot(); bool initBoot();
using namespace tt::hal; using namespace tt::hal;
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() { static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
auto* i2c_internal = device_find_by_name("i2c0");
check(i2c_internal);
return { return {
createPower(), createPower(),
createDisplay(), createDisplay(),
std::make_shared<TdeckKeyboard>(), std::make_shared<TdeckKeyboard>(i2c_internal),
std::make_shared<KeyboardBacklightDevice>(), std::make_shared<KeyboardBacklightDevice>(),
std::make_shared<TrackballDevice>(), std::make_shared<TrackballDevice>(),
createSdCard() createSdCard()

View File

@ -3,10 +3,14 @@
#include <Gt911Touch.h> #include <Gt911Touch.h>
#include <PwmBacklight.h> #include <PwmBacklight.h>
#include <St7789Display.h> #include <St7789Display.h>
#include <tactility/check.h>
#include <tactility/device.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() { 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>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0, i2c,
240, 240,
320, 320,
true, true,

View File

@ -1,6 +1,5 @@
#include "KeyboardBacklight.h" #include "KeyboardBacklight.h"
#include <KeyboardBacklight/KeyboardBacklight.h> // Driver #include <KeyboardBacklight/KeyboardBacklight.h> // Driver
#include <Tactility/hal/i2c/I2c.h>
#include <Tactility/settings/KeyboardSettings.h> #include <Tactility/settings/KeyboardSettings.h>
// TODO: Add Mutex and consider refactoring into a class // TODO: Add Mutex and consider refactoring into a class

View File

@ -1,27 +1,18 @@
#include "TdeckKeyboard.h" #include "TdeckKeyboard.h"
#include "../../../../TactilityKernel/include/tactility/drivers/i2c_controller.h"
#include <KeyboardBacklight/KeyboardBacklight.h> #include <KeyboardBacklight/KeyboardBacklight.h>
#include <Tactility/Logger.h> #include <Tactility/Logger.h>
#include <Tactility/hal/display/DisplayDevice.h> #include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/i2c/I2c.h>
#include <Tactility/settings/DisplaySettings.h> #include <Tactility/settings/DisplaySettings.h>
#include <Tactility/settings/KeyboardSettings.h> #include <Tactility/settings/KeyboardSettings.h>
#include <driver/i2c.h>
#include <lvgl.h> #include <lvgl.h>
#include <tactility/hal/Device.h> #include <tactility/drivers/i2c_controller.h>
using tt::hal::findFirstDevice; using tt::hal::findFirstDevice;
static const auto LOGGER = tt::Logger("TdeckKeyboard"); static const auto LOGGER = tt::Logger("TdeckKeyboard");
constexpr auto TDECK_KEYBOARD_I2C_BUS_HANDLE = I2C_NUM_0; constexpr uint8_t TDECK_KEYBOARD_SLAVE_ADDRESS = 0x55;
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);
}
/** /**
* The callback simulates press and release events, because the T-Deck * The callback simulates press and release events, because the T-Deck
@ -40,7 +31,8 @@ static void keyboard_read_callback(lv_indev_t* indev, lv_indev_data_t* data) {
data->key = 0; data->key = 0;
data->state = LV_INDEV_STATE_RELEASED; 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 (read_buffer == 0 && read_buffer != last_buffer) {
if (LOGGER.isLoggingDebug()) { if (LOGGER.isLoggingDebug()) {
LOGGER.debug("Released {}", last_buffer); LOGGER.debug("Released {}", last_buffer);
@ -94,6 +86,5 @@ bool TdeckKeyboard::stopLvgl() {
} }
bool TdeckKeyboard::isAttached() const { bool TdeckKeyboard::isAttached() const {
auto controller = device_find_by_name("i2c_internal"); return i2c_controller_has_device_at_address(i2cController, TDECK_KEYBOARD_SLAVE_ADDRESS, 100) == ERROR_NONE;
return i2c_controller_has_device_at_address(controller, TDECK_KEYBOARD_SLAVE_ADDRESS, 100) == ERROR_NONE;
} }

View File

@ -1,17 +1,23 @@
#pragma once #pragma once
#include <Tactility/hal/keyboard/KeyboardDevice.h> #include <Tactility/hal/keyboard/KeyboardDevice.h>
#include <Tactility/TactilityCore.h>
struct Device;
class TdeckKeyboard final : public tt::hal::keyboard::KeyboardDevice { class TdeckKeyboard final : public tt::hal::keyboard::KeyboardDevice {
::Device* i2cController;
lv_indev_t* deviceHandle = nullptr; lv_indev_t* deviceHandle = nullptr;
public: public:
explicit TdeckKeyboard(::Device* i2cController) : i2cController(i2cController) {}
std::string getName() const override { return "T-Deck Keyboard"; } std::string getName() const override { return "T-Deck Keyboard"; }
std::string getDescription() const override { return "I2C keyboard"; } std::string getDescription() const override { return "I2C keyboard"; }
::Device* getI2cController() const { return i2cController; }
bool startLvgl(lv_display_t* display) override; bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override; bool stopLvgl() override;
bool isAttached() const override; bool isAttached() const override;

View File

@ -1,6 +1,5 @@
#include "TpagerKeyboard.h" #include "TpagerKeyboard.h"
#include <Tactility/hal/i2c/I2c.h>
#include <Tactility/Logger.h> #include <Tactility/Logger.h>
#include <driver/i2c.h> #include <driver/i2c.h>

View File

@ -5,8 +5,6 @@
static const auto LOGGER = tt::Logger("TpagerPower"); static const auto LOGGER = tt::Logger("TpagerPower");
constexpr auto TPAGER_GAUGE_I2C_BUS_HANDLE = I2C_NUM_0;
TpagerPower::~TpagerPower() {} TpagerPower::~TpagerPower() {}
bool TpagerPower::supportsMetric(MetricType type) const { bool TpagerPower::supportsMetric(MetricType type) const {

View File

@ -3,8 +3,6 @@
constexpr auto* TAG = "CardputerKeyb"; constexpr auto* TAG = "CardputerKeyb";
constexpr auto BACKLIGHT = GPIO_NUM_46;
constexpr auto KB_ROWS = 14; constexpr auto KB_ROWS = 14;
constexpr auto KB_COLS = 4; constexpr auto KB_COLS = 4;

View File

@ -4,7 +4,6 @@
#include <Ft5x06Touch.h> #include <Ft5x06Touch.h>
#include <Ili934xDisplay.h> #include <Ili934xDisplay.h>
#include <Tactility/Logger.h> #include <Tactility/Logger.h>
#include <Tactility/hal/i2c/I2c.h>
static const auto LOGGER = tt::Logger("CoreS3Display"); static const auto LOGGER = tt::Logger("CoreS3Display");

View File

@ -1,10 +1,14 @@
#include "Display.h" #include "Display.h"
#include <Gt911Touch.h> #include <Gt911Touch.h>
#include <EpdiyDisplayHelper.h> #include <EpdiyDisplayHelper.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() { 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>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0, i2c,
540, 540,
960, 960,
true, // swapXy true, // swapXy

View File

@ -4,7 +4,6 @@
#include <Ft6x36Touch.h> #include <Ft6x36Touch.h>
#include <Ili934xDisplay.h> #include <Ili934xDisplay.h>
#include <Tactility/Logger.h> #include <Tactility/Logger.h>
#include <Tactility/hal/i2c/I2c.h>
static const auto LOGGER = tt::Logger("StackChanDisplay"); static const auto LOGGER = tt::Logger("StackChanDisplay");

View File

@ -7,7 +7,6 @@
#include <tactility/drivers/i2c_controller.h> #include <tactility/drivers/i2c_controller.h>
#include <Tactility/hal/Configuration.h> #include <Tactility/hal/Configuration.h>
#include <Tactility/hal/i2c/I2c.h>
using namespace tt::hal; using namespace tt::hal;

View File

@ -8,6 +8,8 @@
#include <PwmBacklight.h> #include <PwmBacklight.h>
#include <Tactility/Logger.h> #include <Tactility/Logger.h>
#include <Tactility/hal/gpio/Gpio.h> #include <Tactility/hal/gpio/Gpio.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/task.h> #include <freertos/task.h>
@ -19,8 +21,10 @@ constexpr auto LCD_PIN_RESET = GPIO_NUM_NC;
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_22; constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_22;
static std::shared_ptr<tt::hal::touch::TouchDevice> createGt911Touch() { 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>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0, i2c,
720, 720,
1280, 1280,
false, // swapXY false, // swapXY

View File

@ -2,12 +2,16 @@
#include <Gt911Touch.h> #include <Gt911Touch.h>
#include <RgbDisplay.h> #include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() { std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 38 and interrupt pin is 18 // 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... // 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>( auto configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0, i2c,
800, 800,
480 480
); );

141
Documentation/README.md Normal file
View File

@ -0,0 +1,141 @@
# 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 Tactilty 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.).
## 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`.

View File

@ -3,7 +3,6 @@
#include <axp192/axp192.h> #include <axp192/axp192.h>
#include <tactility/device.h> #include <tactility/device.h>
#include <Tactility/hal/power/PowerDevice.h> #include <Tactility/hal/power/PowerDevice.h>
#include <Tactility/hal/i2c/I2c.h>
#include <memory> #include <memory>

View File

@ -1,31 +1,30 @@
#include "Gt911Touch.h" #include "Gt911Touch.h"
#include <Tactility/Logger.h> #include <Tactility/Logger.h>
#include <Tactility/hal/i2c/I2c.h>
#include <esp_lcd_touch_gt911.h> #include <esp_lcd_touch_gt911.h>
#include <esp_err.h> #include <esp_err.h>
#include <tactility/drivers/esp32_i2c.h>
#include <tactility/drivers/i2c_controller.h>
static const auto LOGGER = tt::Logger("GT911"); static const auto LOGGER = tt::Logger("GT911");
bool Gt911Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) { 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(); esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
/** auto* i2c = configuration->i2cController;
* 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. if (i2c_controller_has_device_at_address(i2c, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, pdMS_TO_TICKS(10)) == ERROR_NONE) {
* 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)) {
io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS; 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; io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP;
} else { } else {
LOGGER.error("No device found on I2C bus"); LOGGER.error("No device found on I2C bus");
return false; return false;
} }
return esp_lcd_new_panel_io_i2c(configuration->port, &io_config, &outHandle) == ESP_OK; auto port = static_cast<const Esp32I2cConfig*>(i2c->config)->port;
return esp_lcd_new_panel_io_i2c(port, &io_config, &outHandle) == ESP_OK;
} }
bool Gt911Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) { bool Gt911Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) {

View File

@ -2,11 +2,11 @@
#include <Tactility/hal/touch/TouchDevice.h> #include <Tactility/hal/touch/TouchDevice.h>
#include <Tactility/TactilityCore.h> #include <Tactility/TactilityCore.h>
#include <tactility/device.h>
#include <driver/i2c.h>
#include <EspLcdTouch.h> #include <EspLcdTouch.h>
struct Device;
class Gt911Touch final : public EspLcdTouch { class Gt911Touch final : public EspLcdTouch {
public: public:
@ -15,7 +15,7 @@ public:
public: public:
Configuration( Configuration(
i2c_port_t port, ::Device* i2cController,
uint16_t xMax, uint16_t xMax,
uint16_t yMax, uint16_t yMax,
bool swapXy = false, bool swapXy = false,
@ -25,7 +25,7 @@ public:
gpio_num_t pinInterrupt = GPIO_NUM_NC, gpio_num_t pinInterrupt = GPIO_NUM_NC,
unsigned int pinResetLevel = 0, unsigned int pinResetLevel = 0,
unsigned int pinInterruptLevel = 0 unsigned int pinInterruptLevel = 0
) : port(port), ) : i2cController(i2cController),
xMax(xMax), xMax(xMax),
yMax(yMax), yMax(yMax),
swapXy(swapXy), swapXy(swapXy),
@ -37,7 +37,7 @@ public:
pinInterruptLevel(pinInterruptLevel) pinInterruptLevel(pinInterruptLevel)
{} {}
i2c_port_t port; ::Device* i2cController;
uint16_t xMax; uint16_t xMax;
uint16_t yMax; uint16_t yMax;
bool swapXy; bool swapXy;

View File

@ -71,9 +71,7 @@ static error_t start_device(struct Device* device) {
.intr_flags = ESP_INTR_FLAG_LEVEL1, .intr_flags = ESP_INTR_FLAG_LEVEL1,
.enum_filter_cb = nullptr, .enum_filter_cb = nullptr,
.fifo_settings_custom = {}, .fifo_settings_custom = {},
#if CONFIG_IDF_TARGET_ESP32P4
.peripheral_map = cfg->peripheral_map, .peripheral_map = cfg->peripheral_map,
#endif
}; };
esp_err_t ret = usb_host_install(&host_cfg); esp_err_t ret = usb_host_install(&host_cfg);

View File

@ -1,15 +0,0 @@
#pragma once
#include "./I2cCompat.h"
#include "Tactility/Lock.h"
#include <Tactility/freertoscompat/RTOS.h>
namespace tt::hal::i2c {
constexpr TickType_t defaultTimeout = 10 / portTICK_PERIOD_MS;
/** @return true when a device is detected at the specified address */
bool masterHasDeviceAtAddress(i2c_port_t port, uint8_t address, TickType_t timeout = defaultTimeout);
} // namespace

View File

@ -1,38 +0,0 @@
#pragma once
#ifdef ESP_PLATFORM
#include <hal/i2c_types.h>
#include <driver/i2c.h>
#else
#include <cstdint>
enum i2c_port_t {
I2C_NUM_0 = 0,
I2C_NUM_1,
LP_I2C_NUM_0,
I2C_NUM_MAX,
};
enum i2c_mode_t {
I2C_MODE_MASTER,
I2C_MODE_MAX,
};
struct i2c_config_t {
i2c_mode_t mode;
int sda_io_num;
int scl_io_num;
bool sda_pullup_en;
bool scl_pullup_en;
union {
struct {
uint32_t clk_speed;
} master;
};
uint32_t clk_flags;
};
#endif

View File

@ -2,7 +2,6 @@
#include <tactility/hal/Device.h> #include <tactility/hal/Device.h>
#include <tactility/drivers/i2c_controller.h> #include <tactility/drivers/i2c_controller.h>
#include "I2c.h"
namespace tt::hal::i2c { namespace tt::hal::i2c {

View File

@ -1,50 +0,0 @@
#include <Tactility/hal/i2c/I2c.h>
#include <Tactility/Mutex.h>
#include <tactility/check.h>
#include <tactility/drivers/i2c_controller.h>
#ifdef ESP_PLATFORM
#include <tactility/drivers/esp32_i2c.h>
#endif
namespace tt::hal::i2c {
Device* findDevice(i2c_port_t port) {
#ifdef ESP_PLATFORM
struct Params {
i2c_port_t port;
Device* device;
};
Params params = {
.port = port,
.device = nullptr
};
device_for_each_of_type(&I2C_CONTROLLER_TYPE, &params, [](auto* device, auto* context) {
auto* params_ptr = (Params*)context;
auto* driver = device_get_driver(device);
if (driver == nullptr) return true;
if (!driver_is_compatible(driver, "espressif,esp32-i2c")) return true;
const auto* config = static_cast<const Esp32I2cConfig*>(device->config);
if (config->port != params_ptr->port) return true;
// Found it, stop iterating
params_ptr->device = device;
return false;
});
return params.device;
#else
return nullptr;
#endif
}
bool masterHasDeviceAtAddress(i2c_port_t port, uint8_t address, TickType_t timeout) {
auto* device = findDevice(port);
if (device == nullptr) return false;
return i2c_controller_has_device_at_address(device, address, timeout) == ERROR_NONE;
}
} // namespace