Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 047c7c87d7 | |||
| 94b5cdbfc4 | |||
| 5c535490ea | |||
| e50659a3fb | |||
| aeccc15ab6 | |||
| 594b8bd27e | |||
| e8b9a1f2a9 | |||
| a35c88c8fd | |||
| 8dabda2b5b | |||
| 8dd9bee8d0 | |||
| 543390a977 | |||
| 62266dff58 |
+4
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -0,0 +1,105 @@
|
||||
#include "PwmBacklight.h"
|
||||
#include "devices/Display.h"
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/i2c_controller.h>
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/freertos/freertos.h>
|
||||
|
||||
#include <Tactility/hal/Configuration.h>
|
||||
|
||||
using namespace tt::hal;
|
||||
|
||||
static constexpr auto* TAG = "ES3C28P";
|
||||
static constexpr uint8_t ES8311_I2C_ADDR = 0x18;
|
||||
|
||||
static error_t initSound(::Device* i2c_controller) {
|
||||
static constexpr uint8_t ENABLED_BULK_DATA[] = {
|
||||
0x00, 0x80, // RESET/CSM POWER ON
|
||||
0x01, 0x3F, // CLOCK_MANAGER/ use MCLK pin (external), all clocks on
|
||||
0x02, 0x00, // CLOCK_MANAGER/ pre_div=1 pre_multi=1 (256x MCLK is correct ratio)
|
||||
0x0D, 0x01, // SYSTEM/ Power up analog circuitry
|
||||
0x12, 0x00, // SYSTEM/ power-up DAC
|
||||
0x13, 0x10, // SYSTEM/ Enable output to HP drive
|
||||
0x32, 0xBF, // DAC/ DAC volume (0xBF == ±0 dB)
|
||||
0x37, 0x08, // DAC/ Bypass DAC equalizer
|
||||
};
|
||||
|
||||
return i2c_controller_write_register_array(
|
||||
i2c_controller,
|
||||
ES8311_I2C_ADDR,
|
||||
ENABLED_BULK_DATA,
|
||||
sizeof(ENABLED_BULK_DATA),
|
||||
pdMS_TO_TICKS(1000)
|
||||
);
|
||||
}
|
||||
|
||||
static error_t initMicrophone(::Device* i2c_controller) {
|
||||
static constexpr uint8_t ENABLED_BULK_DATA[] = {
|
||||
0x00, 0x80, // RESET/CSM POWER ON
|
||||
0x01, 0x3F, // CLOCK_MANAGER/ use MCLK pin (external), all clocks on
|
||||
0x02, 0x00, // CLOCK_MANAGER/ pre_div=1 pre_multi=1
|
||||
0x0D, 0x01, // SYSTEM/ Power up analog circuitry
|
||||
0x0E, 0x02, // SYSTEM/ Enable analog PGA, enable ADC modulator
|
||||
0x14, 0x10, // ADC_REG14: select Mic1p-Mic1n / PGA GAIN (minimum)
|
||||
0x17, 0xFF, // ADC_REG17: ADC_VOLUME (MAXGAIN)
|
||||
0x1C, 0x6A, // ADC_REG1C: ADC Equalizer bypass, cancel DC offset
|
||||
};
|
||||
|
||||
return i2c_controller_write_register_array(
|
||||
i2c_controller,
|
||||
ES8311_I2C_ADDR,
|
||||
ENABLED_BULK_DATA,
|
||||
sizeof(ENABLED_BULK_DATA),
|
||||
pdMS_TO_TICKS(1000)
|
||||
);
|
||||
}
|
||||
|
||||
static bool initBoot() {
|
||||
// 1. Initialize display backlight
|
||||
if (!driver::pwmbacklight::init(LCD_PIN_BACKLIGHT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Initialize and power on the FM8002E Audio Amplifier (GPIO 1, Active LOW)
|
||||
gpio_config_t io_conf = {};
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << GPIO_NUM_1);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
// Drive LOW to enable the speaker amplifier
|
||||
gpio_set_level(GPIO_NUM_1, 0);
|
||||
|
||||
// 3. Configure the ES8311 Codec over the I2C bus
|
||||
auto* i2c_bus = device_find_by_name("i2c0");
|
||||
if (i2c_bus != nullptr) {
|
||||
error_t error = initSound(i2c_bus);
|
||||
if (error != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to enable ES8311 speaker: %s", error_to_string(error));
|
||||
}
|
||||
error = initMicrophone(i2c_bus);
|
||||
if (error != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to enable ES8311 microphone: %s", error_to_string(error));
|
||||
}
|
||||
} else {
|
||||
LOG_E(TAG, "i2c0 bus not found, skipping ES8311 initialization");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
- Platforms/platform-esp32
|
||||
dts: es3c28p.dts
|
||||
@@ -0,0 +1,59 @@
|
||||
/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>
|
||||
#include <tactility/bindings/esp32_i2s.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>;
|
||||
};
|
||||
|
||||
i2s0 {
|
||||
compatible = "espressif,esp32-i2s";
|
||||
port = <I2S_NUM_0>;
|
||||
pin-bclk = <&gpio0 5 GPIO_FLAG_NONE>;
|
||||
pin-ws = <&gpio0 7 GPIO_FLAG_NONE>;
|
||||
pin-data-out = <&gpio0 6 GPIO_FLAG_NONE>;
|
||||
pin-data-in = <&gpio0 8 GPIO_FLAG_NONE>;
|
||||
pin-mclk = <&gpio0 4 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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 a–z / A–Z
|
||||
if (keycode >= 0x04U && keycode <= 0x1DU) {
|
||||
uint32_t c = static_cast<uint32_t>('a' + (keycode - 0x04U));
|
||||
return shift ? (c - 0x20U) : c;
|
||||
}
|
||||
|
||||
// Numbers 1–0 and their shifted symbols
|
||||
if (keycode >= 0x1EU && keycode <= 0x27U) {
|
||||
static constexpr char nums[] = "1234567890";
|
||||
static constexpr char snums[] = "!@#$%^&*()";
|
||||
return shift ? static_cast<uint32_t>(snums[keycode - 0x1EU])
|
||||
: static_cast<uint32_t>(nums[keycode - 0x1EU]);
|
||||
}
|
||||
|
||||
// Space and punctuation - all codes present in the Tab5 matrix
|
||||
switch (keycode) {
|
||||
case 0x2C: return ' ';
|
||||
case 0x2D: return shift ? '_' : '-';
|
||||
case 0x2E: return shift ? '+' : '=';
|
||||
case 0x2F: return shift ? '{' : '[';
|
||||
case 0x30: return shift ? '}' : ']';
|
||||
case 0x31: return shift ? '|' : '\\';
|
||||
case 0x33: return shift ? ':' : ';';
|
||||
case 0x34: return shift ? '"' : '\'';
|
||||
case 0x35: return shift ? '~' : '`';
|
||||
case 0x36: return shift ? '<' : ',';
|
||||
case 0x37: return shift ? '>' : '.';
|
||||
case 0x38: return shift ? '?' : '/';
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// I2C helpers - use Tactility I2C controller API
|
||||
// ---------------------------------------------------------------------------
|
||||
bool Tab5Keyboard::readReg(uint8_t reg, uint8_t& value) {
|
||||
return i2c_controller_read_register(i2cController, I2C_ADDRESS, reg, &value, 1, pdMS_TO_TICKS(50)) == ERROR_NONE;
|
||||
}
|
||||
|
||||
bool Tab5Keyboard::writeReg(uint8_t reg, uint8_t value) {
|
||||
return i2c_controller_write_register(i2cController, I2C_ADDRESS, reg, &value, 1, pdMS_TO_TICKS(50)) == ERROR_NONE;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// LED helpers - LED0 = Sym indicator (green), LED1 = Aa indicator (red)
|
||||
// RGB register layout: [B, G, R] per LED, stride 4 (byte 3 reserved)
|
||||
// ---------------------------------------------------------------------------
|
||||
void Tab5Keyboard::updateLeds() {
|
||||
// [LED0: B,G,R, reserved, LED1: B,G,R]
|
||||
uint8_t buf[7] = {
|
||||
0x00, symActive ? uint8_t(0xA0) : uint8_t(0x00), 0x00, 0x00, // LED0: green if Sym
|
||||
0x00, 0x00, aaSticky ? uint8_t(0xA0) : uint8_t(0x00), // LED1: red if Aa latched
|
||||
};
|
||||
// Write 7-byte block starting at REG_RGB_BASE
|
||||
i2c_controller_write_register(i2cController, I2C_ADDRESS, REG_RGB_BASE, buf, 7, pdMS_TO_TICKS(50));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IRQ pin - GPIO 50, active-low, falling edge
|
||||
// ---------------------------------------------------------------------------
|
||||
void IRAM_ATTR Tab5Keyboard::irqHandler(void* arg) {
|
||||
auto* self = static_cast<Tab5Keyboard*>(arg);
|
||||
self->irqPending = true;
|
||||
}
|
||||
|
||||
bool Tab5Keyboard::configureIrqPin() {
|
||||
gpio_config_t io_conf{};
|
||||
io_conf.pin_bit_mask = (1ULL << INT_PIN);
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.intr_type = GPIO_INTR_NEGEDGE;
|
||||
if (gpio_config(&io_conf) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const esp_err_t svc = gpio_install_isr_service(0);
|
||||
if (svc != ESP_OK && svc != ESP_ERR_INVALID_STATE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gpio_isr_handler_add(INT_PIN, irqHandler, this) != ESP_OK) {
|
||||
gpio_set_intr_type(INT_PIN, GPIO_INTR_DISABLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
irqConfigured = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Tab5Keyboard::removeIrqPin() {
|
||||
if (!irqConfigured) return;
|
||||
gpio_isr_handler_remove(INT_PIN);
|
||||
irqConfigured = false;
|
||||
irqPending = false;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// drainEvents - reads all pending events from the device queue
|
||||
// ---------------------------------------------------------------------------
|
||||
void Tab5Keyboard::drainEvents() {
|
||||
uint8_t count = 0;
|
||||
if (!readReg(REG_EVENT_NUM, count) || count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (count > 0) {
|
||||
uint8_t raw = 0;
|
||||
if (!readReg(REG_KEY_EVENT, raw) || raw == KEY_EVENT_EMPTY) {
|
||||
break;
|
||||
}
|
||||
|
||||
const bool pressed = (raw & 0x80U) != 0U;
|
||||
const uint8_t row = (raw >> 4U) & 0x07U;
|
||||
const uint8_t col = raw & 0x0FU;
|
||||
|
||||
// Modifier keys: update state, no key output
|
||||
if (row == MOD_ROW_SYM && col == MOD_COL_SYM) {
|
||||
symActive = pressed;
|
||||
updateLeds();
|
||||
count--;
|
||||
continue;
|
||||
}
|
||||
if (row == MOD_ROW_AA && col == MOD_COL_AA) {
|
||||
if (pressed) {
|
||||
aaHeld = true;
|
||||
aaTapped = true; // assume tap until a real key is pressed while held
|
||||
} else {
|
||||
// Only latch sticky if no non-modifier key was pressed during this hold
|
||||
if (aaTapped) {
|
||||
aaSticky = !aaSticky;
|
||||
}
|
||||
aaHeld = false;
|
||||
aaTapped = false;
|
||||
}
|
||||
updateLeds();
|
||||
count--;
|
||||
continue;
|
||||
}
|
||||
if (row == MOD_ROW_CTRL && col == MOD_COL_CTRL) {
|
||||
ctrlHeld = pressed;
|
||||
count--;
|
||||
continue;
|
||||
}
|
||||
if (row == MOD_ROW_ALT && col == MOD_COL_ALT) {
|
||||
count--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (row < 5U && col < 14U) {
|
||||
const bool aaActive = aaHeld || aaSticky;
|
||||
const HidMapping& m = symActive
|
||||
? KEY_MATRIX_HID_SYM[row * 14U + col]
|
||||
: KEY_MATRIX_HID_BASE[row * 14U + col];
|
||||
if (m.keycode != 0U) {
|
||||
const uint8_t modifier = static_cast<uint8_t>(m.modifier | (aaActive ? 0x02U : 0U));
|
||||
const uint32_t lv_key = tab5TranslateKey(m.keycode, modifier, ctrlHeld);
|
||||
if (lv_key != 0U) {
|
||||
if (pressed) {
|
||||
// A real key was pressed — this hold is a chord, not a tap
|
||||
aaTapped = false;
|
||||
if (lv_key == LV_KEY_ESC) {
|
||||
tt::app::stop();
|
||||
} else {
|
||||
xQueueSend(queue, &lv_key, 0);
|
||||
// Arm software repeat tracking by row/col to survive modifier changes
|
||||
const uint32_t now_ms = static_cast<uint32_t>(esp_timer_get_time() / 1000);
|
||||
repeatKey = lv_key;
|
||||
repeatRow = row;
|
||||
repeatCol = col;
|
||||
repeatStartMs = now_ms;
|
||||
repeatLastMs = 0;
|
||||
// Consume sticky Aa after one keypress
|
||||
if (aaSticky) {
|
||||
aaSticky = false;
|
||||
aaHeld = false;
|
||||
updateLeds();
|
||||
}
|
||||
}
|
||||
} else if (row == repeatRow && col == repeatCol) {
|
||||
// Match release by position, not translated value — survives sticky Aa clear
|
||||
repeatKey = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
count--;
|
||||
}
|
||||
|
||||
// Clear INT status after draining so the line de-asserts
|
||||
writeReg(REG_INT_STAT, 0x00);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// processKeyboard - called from 20ms Timer; IRQ-gated when INT is wired
|
||||
// ---------------------------------------------------------------------------
|
||||
void Tab5Keyboard::processKeyboard() {
|
||||
bool shouldDrain = false;
|
||||
if (irqConfigured) {
|
||||
if (irqPending) {
|
||||
irqPending = false;
|
||||
shouldDrain = true;
|
||||
}
|
||||
} else {
|
||||
// Polling: check INT_STA first — bit 0 = Normal mode event pending
|
||||
uint8_t status = 0;
|
||||
if (readReg(REG_INT_STAT, status) && (status & 0x01U)) {
|
||||
shouldDrain = true;
|
||||
}
|
||||
}
|
||||
if (shouldDrain) {
|
||||
drainEvents();
|
||||
}
|
||||
|
||||
// Software key-repeat (runs every tick regardless of IRQ)
|
||||
if (repeatKey != 0U) {
|
||||
const uint32_t now_ms = static_cast<uint32_t>(esp_timer_get_time() / 1000);
|
||||
if ((now_ms - repeatStartMs) >= REPEAT_INITIAL_MS) {
|
||||
const uint32_t last = repeatLastMs;
|
||||
if (last == 0 || (now_ms - last) >= REPEAT_RATE_MS) {
|
||||
repeatLastMs = now_ms;
|
||||
xQueueSend(queue, &repeatKey, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkAttachState();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// applyAutoRotation - on attach, switches to landscape if not already (saving
|
||||
// the prior rotation); on detach, restores the saved rotation if we were the
|
||||
// ones who changed it. Only affects the live LVGL rotation, never persisted
|
||||
// display settings.
|
||||
// ---------------------------------------------------------------------------
|
||||
bool Tab5Keyboard::applyAutoRotation(bool keyboardAttached) {
|
||||
auto* display = lv_indev_get_display(kbHandle);
|
||||
if (display == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tt::lvgl::lock(pdMS_TO_TICKS(100))) {
|
||||
return false; // retry next poll
|
||||
}
|
||||
|
||||
if (keyboardAttached) {
|
||||
if (lv_display_get_rotation(display) != LV_DISPLAY_ROTATION_90) {
|
||||
savedRotation = lv_display_get_rotation(display);
|
||||
rotationOverrideActive = true;
|
||||
lv_display_set_rotation(display, LV_DISPLAY_ROTATION_90);
|
||||
}
|
||||
} else {
|
||||
// Only restore if rotation is still what we set it to - if the user manually
|
||||
// changed it since attaching, respect their choice instead.
|
||||
if (rotationOverrideActive && lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_90) {
|
||||
lv_display_set_rotation(display, savedRotation);
|
||||
}
|
||||
rotationOverrideActive = false;
|
||||
}
|
||||
|
||||
tt::lvgl::unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// checkAttachState - throttled (~1s) hot-plug detection. Reapplies device
|
||||
// register configuration and auto-rotation on detach/attach transitions.
|
||||
// ---------------------------------------------------------------------------
|
||||
void Tab5Keyboard::checkAttachState() {
|
||||
static constexpr uint32_t ATTACH_CHECK_TICKS = 50; // ~1s at 20ms/tick
|
||||
|
||||
if (++attachCheckTickCounter < ATTACH_CHECK_TICKS) {
|
||||
return;
|
||||
}
|
||||
attachCheckTickCounter = 0;
|
||||
|
||||
const bool attached = isAttached();
|
||||
if (attached == wasAttached) {
|
||||
pendingAttachConfirmCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Require the new state to be confirmed on a second consecutive check before acting -
|
||||
// a single probe on a floating/half-connected bus (e.g. mid-unplug) can false-positive.
|
||||
if (attached != pendingAttachState || pendingAttachConfirmCount == 0) {
|
||||
pendingAttachState = attached;
|
||||
pendingAttachConfirmCount = 1;
|
||||
return;
|
||||
}
|
||||
pendingAttachConfirmCount = 0;
|
||||
|
||||
if (attached) {
|
||||
reinitDevice();
|
||||
}
|
||||
if (!applyAutoRotation(attached)) {
|
||||
return; // keep prior state so transition is retried
|
||||
}
|
||||
wasAttached = attached;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// lateStart - see header comment. Brings up LVGL input handling for a keyboard
|
||||
// that wasn't attached at boot (startLvgl() wasn't called from attachDevices()).
|
||||
// ---------------------------------------------------------------------------
|
||||
bool Tab5Keyboard::lateStart() {
|
||||
if (kbHandle != nullptr) {
|
||||
return true; // already started
|
||||
}
|
||||
|
||||
auto* display = lv_display_get_default();
|
||||
if (display == nullptr) {
|
||||
return false; // LVGL not ready yet
|
||||
}
|
||||
|
||||
if (!tt::lvgl::lock(pdMS_TO_TICKS(100))) {
|
||||
return false; // try again on the next attach-state check
|
||||
}
|
||||
|
||||
bool started = startLvgl(display);
|
||||
if (started) {
|
||||
tt::lvgl::hardware_keyboard_set_indev(kbHandle);
|
||||
|
||||
// redraw() assigns every indev that exists at the time to the active screen's
|
||||
// input group. This indev didn't exist yet at the last redraw(), so it has no
|
||||
// group and won't deliver key events until the next app switch. Join the
|
||||
// current default group now so input works immediately on the visible screen.
|
||||
lv_indev_set_group(kbHandle, lv_group_get_default());
|
||||
}
|
||||
|
||||
tt::lvgl::unlock();
|
||||
|
||||
return started;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// LVGL read callback - called from the LVGL task
|
||||
// ---------------------------------------------------------------------------
|
||||
void Tab5Keyboard::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
auto* self = static_cast<Tab5Keyboard*>(lv_indev_get_user_data(indev));
|
||||
uint32_t lv_key = 0;
|
||||
if (xQueueReceive(self->queue, &lv_key, 0) == pdTRUE) {
|
||||
data->key = lv_key;
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
data->continue_reading = (uxQueueMessagesWaiting(self->queue) > 0);
|
||||
} else {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// KeyboardDevice interface
|
||||
// ---------------------------------------------------------------------------
|
||||
Tab5Keyboard::~Tab5Keyboard() {
|
||||
if (inputTimer) {
|
||||
stopLvgl(); // tears down LVGL indev, IRQ, I2C bus
|
||||
}
|
||||
if (queue) {
|
||||
vQueueDelete(queue);
|
||||
queue = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// reinitDevice - (re)applies the device register configuration. Used at
|
||||
// startLvgl() and again on hot-plug reattach, since the device's RGB mode and
|
||||
// interrupt configuration are volatile and reset to power-on defaults when
|
||||
// the keyboard is unplugged and reconnected.
|
||||
// ---------------------------------------------------------------------------
|
||||
void Tab5Keyboard::reinitDevice() {
|
||||
writeReg(REG_KEYBOARD_MODE, 0x00); // Normal mode
|
||||
writeReg(REG_EVENT_NUM, 0x00); // flush event queue
|
||||
writeReg(REG_INT_STAT, 0x00); // clear pending INT
|
||||
writeReg(REG_RGB_MODE, 0x01); // Custom RGB mode (manual LED control)
|
||||
writeReg(REG_BRIGHTNESS, 50); // 50% brightness
|
||||
updateLeds(); // restore current LED state
|
||||
|
||||
if (irqConfigured) {
|
||||
writeReg(REG_INT_CFG, 0x01); // re-enable Normal-mode interrupt (bit 0)
|
||||
}
|
||||
}
|
||||
|
||||
bool Tab5Keyboard::startLvgl(lv_display_t* display) {
|
||||
if (!queue) {
|
||||
LOG_E("Tab5Keyboard", "Input queue allocation failed — cannot start");
|
||||
return false;
|
||||
}
|
||||
|
||||
symActive = false;
|
||||
aaSticky = false;
|
||||
aaHeld = false;
|
||||
aaTapped = false;
|
||||
ctrlHeld = false;
|
||||
repeatKey = 0;
|
||||
repeatRow = 0xFF;
|
||||
repeatCol = 0xFF;
|
||||
repeatLastMs = 0;
|
||||
|
||||
configureIrqPin(); // best-effort; falls back to polling if it fails. Must run before
|
||||
// reinitDevice() so REG_INT_CFG is written if IRQ setup succeeded.
|
||||
|
||||
// Best-effort: if the keyboard isn't attached yet (e.g. started speculatively at
|
||||
// boot so it can be detected later via hot-plug), these I2C writes fail silently
|
||||
// and reinitDevice() runs again once attach is detected.
|
||||
reinitDevice();
|
||||
|
||||
kbHandle = lv_indev_create();
|
||||
lv_indev_set_type(kbHandle, LV_INDEV_TYPE_KEYPAD);
|
||||
lv_indev_set_read_cb(kbHandle, readCallback);
|
||||
lv_indev_set_display(kbHandle, display);
|
||||
lv_indev_set_user_data(kbHandle, this);
|
||||
|
||||
wasAttached = isAttached();
|
||||
rotationOverrideActive = false;
|
||||
|
||||
assert(inputTimer == nullptr);
|
||||
inputTimer = std::make_unique<tt::Timer>(tt::Timer::Type::Periodic, pdMS_TO_TICKS(20), [this] {
|
||||
processKeyboard();
|
||||
});
|
||||
inputTimer->start();
|
||||
|
||||
if (wasAttached) {
|
||||
applyAutoRotation(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Tab5Keyboard::stopLvgl() {
|
||||
if (!inputTimer) {
|
||||
return false; // Not started
|
||||
}
|
||||
inputTimer->stop();
|
||||
inputTimer = nullptr;
|
||||
|
||||
removeIrqPin();
|
||||
if (queue) {
|
||||
xQueueReset(queue); // discard unread keycodes so a restart begins with an empty buffer
|
||||
}
|
||||
writeReg(REG_INT_CFG, 0x00); // disable all interrupts
|
||||
symActive = false;
|
||||
aaSticky = false;
|
||||
aaHeld = false;
|
||||
updateLeds(); // turn LEDs off
|
||||
|
||||
lv_indev_delete(kbHandle);
|
||||
kbHandle = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Tab5Keyboard::isAttached() const {
|
||||
return i2c_controller_has_device_at_address(i2cController, I2C_ADDRESS, pdMS_TO_TICKS(100)) == ERROR_NONE;
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ spiRamSpeed=200M
|
||||
esptoolFlashFreq=80M
|
||||
bluetooth=true
|
||||
usbHostEnabled=true
|
||||
tinyUsb=true
|
||||
|
||||
[display]
|
||||
size=5"
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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`.
|
||||
@@ -0,0 +1,73 @@
|
||||
# GameBoy side-loaded ELF support notes
|
||||
|
||||
This branch records the firmware-side changes used to make the Tactility GameBoy emulator prototype load past the generic `missing symbol` failure on an ESP32-S3 ES3C28P board.
|
||||
|
||||
## Problem
|
||||
|
||||
Side-loaded `.app` packages are relocated by the firmware ELF loader. The app can build successfully against the local SDK but still fail at runtime if the board firmware does not export every dynamic symbol used by the app.
|
||||
|
||||
For the GameBoy prototype, USB serial logs showed the emulator app was being found and started, but firmware without the needed LVGL exports showed missing-symbol failures for the canvas path.
|
||||
|
||||
## Firmware changes in this branch
|
||||
|
||||
`TactilityC/Source/tt_init.cpp` now includes LVGL and exports the core LVGL canvas/display functions the GameBoy app needs:
|
||||
|
||||
- `lv_canvas_create`
|
||||
- `lv_canvas_set_buffer`
|
||||
- `lv_obj_invalidate`
|
||||
- `lv_display_get_horizontal_resolution`
|
||||
- `lv_display_get_vertical_resolution`
|
||||
|
||||
These are exported through the existing `main_symbols[]` table with `ESP_ELFSYM_EXPORT(...)`, making them visible to side-loaded ESP32-S3 app ELFs.
|
||||
|
||||
The branch also removes the `AlwaysRun` devicetree delete target from `Firmware/CMakeLists.txt`. During iterative firmware builds this target deleted `Generated/devicetree.c` while compile rules still depended on it, causing intermittent build failures. The devicetree generation rule now depends directly on the source YAML.
|
||||
|
||||
## Verification performed
|
||||
|
||||
On the iMac build host:
|
||||
|
||||
```bash
|
||||
cd ~/Projects/gitea/tactility
|
||||
. ~/esp/esp-idf/export.sh
|
||||
python3 device.py es3c28p
|
||||
idf.py build
|
||||
idf.py -p /dev/ttyACM0 flash
|
||||
```
|
||||
|
||||
Build result:
|
||||
|
||||
```text
|
||||
Tactility.bin binary size 0x2b82b0 bytes
|
||||
Smallest app partition is 0x400000 bytes
|
||||
0x147d50 bytes (32%) free
|
||||
```
|
||||
|
||||
Flash target:
|
||||
|
||||
```text
|
||||
ESP32-S3 ES3C28P
|
||||
USB serial: /dev/ttyACM0
|
||||
MAC: 14:c1:9f:d0:5c:5c
|
||||
```
|
||||
|
||||
Runtime serial showed the GameBoy core loop running and reporting approximately 49-50 FPS:
|
||||
|
||||
```text
|
||||
GAMEBOY_FPS 50
|
||||
GAMEBOY_FPS 49
|
||||
GAMEBOY_FPS 49
|
||||
```
|
||||
|
||||
## Current caveat
|
||||
|
||||
The app loop/FPS is running, but the photographed device showed the GameBoy framebuffer was not visibly rendering yet. That remaining issue is in the app/UI canvas presentation path, not the firmware missing-symbol issue addressed by this branch.
|
||||
|
||||
## Related app work
|
||||
|
||||
The app-side prototype lives in the separate Gitea repo:
|
||||
|
||||
```text
|
||||
https://git.reynafamily.com/adolforeyna/tactility_apps.git
|
||||
```
|
||||
|
||||
The app branch changes include restoring the LVGL canvas path, using `/data/roms/gb/default.gb` for the flashed data-image test, and printing `GAMEBOY_FPS` markers over USB serial.
|
||||
@@ -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
@@ -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
|
||||
@@ -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."; }
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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."; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -100,17 +100,13 @@ endif ()
|
||||
#
|
||||
# Devicetree code generation
|
||||
#
|
||||
|
||||
add_custom_target(AlwaysRun
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -f "${GENERATED_DIR}/devicetree.c"
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT "${GENERATED_DIR}/devicetree.c"
|
||||
"${GENERATED_DIR}/devicetree.h"
|
||||
COMMAND python "${CMAKE_SOURCE_DIR}/Buildscripts/DevicetreeCompiler/compile.py"
|
||||
"${DEVICETREE_LOCATION}" "${GENERATED_DIR}"
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
DEPENDS AlwaysRun "${DEVICETREE_LOCATION}/devicetree.yaml" # AlwaysRun ensures it always gets built
|
||||
DEPENDS "${DEVICETREE_LOCATION}/devicetree.yaml"
|
||||
COMMENT "Generating devicetree source files..."
|
||||
)
|
||||
add_custom_target(Generated DEPENDS "${GENERATED_DIR}/devicetree.c")
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user