Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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,22 @@
|
||||
#include "PwmBacklight.h"
|
||||
#include "devices/Display.h"
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#include <Tactility/hal/Configuration.h>
|
||||
|
||||
using namespace tt::hal;
|
||||
|
||||
static bool initBoot() {
|
||||
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT);
|
||||
}
|
||||
|
||||
static DeviceVector createDevices() {
|
||||
return {
|
||||
createDisplay()
|
||||
};
|
||||
}
|
||||
|
||||
extern const Configuration hardwareConfiguration = {
|
||||
.initBoot = initBoot,
|
||||
.createDevices = createDevices
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
#include "Display.h"
|
||||
|
||||
#include <Ft6x36Touch.h>
|
||||
#include <Ili934xDisplay.h>
|
||||
#include <PwmBacklight.h>
|
||||
|
||||
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
|
||||
auto configuration = std::make_unique<Ft6x36Touch::Configuration>(
|
||||
I2C_NUM_0,
|
||||
240, // xMax (native portrait width)
|
||||
320, // yMax (native portrait height)
|
||||
true, // swapXy
|
||||
true, // mirrorX
|
||||
false, // mirrorY
|
||||
GPIO_NUM_18, // Touch Reset (RST)
|
||||
GPIO_NUM_17 // Touch Interrupt (INT)
|
||||
);
|
||||
|
||||
return std::make_shared<Ft6x36Touch>(std::move(configuration));
|
||||
}
|
||||
|
||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
||||
Ili934xDisplay::Configuration panel_configuration = {
|
||||
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
|
||||
.verticalResolution = LCD_VERTICAL_RESOLUTION,
|
||||
.gapX = 0,
|
||||
.gapY = 0,
|
||||
.swapXY = true,
|
||||
.mirrorX = false,
|
||||
.mirrorY = false,
|
||||
.invertColor = false,
|
||||
.swapBytes = true,
|
||||
.bufferSize = LCD_BUFFER_SIZE,
|
||||
.touch = createTouch(),
|
||||
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
|
||||
.resetPin = GPIO_NUM_NC,
|
||||
.rgbElementOrder = LCD_RGB_ELEMENT_ORDER_BGR
|
||||
};
|
||||
|
||||
auto spi_configuration = std::make_shared<Ili934xDisplay::SpiConfiguration>(Ili934xDisplay::SpiConfiguration {
|
||||
.spiHostDevice = LCD_SPI_HOST,
|
||||
.csPin = LCD_PIN_CS,
|
||||
.dcPin = LCD_PIN_DC,
|
||||
.pixelClockFrequency = 40'000'000,
|
||||
.transactionQueueDepth = 10
|
||||
});
|
||||
|
||||
return std::make_shared<Ili934xDisplay>(panel_configuration, spi_configuration, true);
|
||||
}
|
||||
@@ -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,48 @@
|
||||
/dts-v1/;
|
||||
|
||||
#include <tactility/bindings/root.h>
|
||||
#include <tactility/bindings/esp32_ble.h>
|
||||
#include <tactility/bindings/esp32_gpio.h>
|
||||
#include <tactility/bindings/esp32_i2c.h>
|
||||
#include <tactility/bindings/esp32_sdmmc.h>
|
||||
#include <tactility/bindings/esp32_spi.h>
|
||||
|
||||
/ {
|
||||
compatible = "root";
|
||||
model = "LCDWIKI ES3C28P";
|
||||
|
||||
ble0 {
|
||||
compatible = "espressif,esp32-ble";
|
||||
};
|
||||
|
||||
gpio0 {
|
||||
compatible = "espressif,esp32-gpio";
|
||||
gpio-count = <49>;
|
||||
};
|
||||
|
||||
i2c0 {
|
||||
compatible = "espressif,esp32-i2c";
|
||||
port = <I2C_NUM_0>;
|
||||
clock-frequency = <400000>;
|
||||
pin-sda = <&gpio0 16 GPIO_FLAG_NONE>;
|
||||
pin-scl = <&gpio0 15 GPIO_FLAG_NONE>;
|
||||
};
|
||||
|
||||
display_spi: spi0 {
|
||||
compatible = "espressif,esp32-spi";
|
||||
host = <SPI2_HOST>;
|
||||
pin-mosi = <&gpio0 11 GPIO_FLAG_NONE>;
|
||||
pin-sclk = <&gpio0 12 GPIO_FLAG_NONE>;
|
||||
};
|
||||
|
||||
sdmmc0 {
|
||||
compatible = "espressif,esp32-sdmmc";
|
||||
pin-clk = <&gpio0 38 GPIO_FLAG_NONE>;
|
||||
pin-cmd = <&gpio0 40 GPIO_FLAG_NONE>;
|
||||
pin-d0 = <&gpio0 39 GPIO_FLAG_NONE>;
|
||||
pin-d1 = <&gpio0 41 GPIO_FLAG_NONE>;
|
||||
pin-d2 = <&gpio0 48 GPIO_FLAG_NONE>;
|
||||
pin-d3 = <&gpio0 47 GPIO_FLAG_NONE>;
|
||||
bus-width = <4>;
|
||||
};
|
||||
};
|
||||
@@ -5,21 +5,22 @@
|
||||
#include <PwmBacklight.h>
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/device.h>
|
||||
|
||||
constexpr auto LCD_PIN_RESET = GPIO_NUM_0; // Match P4 EV board reset line
|
||||
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_23;
|
||||
constexpr auto LCD_HORIZONTAL_RESOLUTION = 1024;
|
||||
constexpr auto LCD_VERTICAL_RESOLUTION = 600;
|
||||
|
||||
constexpr auto TOUCH_I2C_PORT = I2C_NUM_0;
|
||||
constexpr auto TOUCH_I2C_SDA = GPIO_NUM_7;
|
||||
constexpr auto TOUCH_I2C_SCL = GPIO_NUM_8;
|
||||
constexpr auto TOUCH_PIN_RESET = GPIO_NUM_NC;
|
||||
constexpr auto TOUCH_PIN_INTERRUPT = GPIO_NUM_NC;
|
||||
|
||||
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
|
||||
auto* i2c = device_find_by_name("i2c_internal");
|
||||
check(i2c);
|
||||
auto configuration = std::make_unique<Gt911Touch::Configuration>(
|
||||
TOUCH_I2C_PORT,
|
||||
i2c,
|
||||
LCD_HORIZONTAL_RESOLUTION,
|
||||
LCD_VERTICAL_RESOLUTION,
|
||||
false, // swapXY
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
#include <Gt911Touch.h>
|
||||
#include <PwmBacklight.h>
|
||||
#include <RgbDisplay.h>
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/device.h>
|
||||
|
||||
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
|
||||
// Note for future changes: Reset pin is 38 and interrupt pin is 18
|
||||
// or INT = NC, schematic and other info floating around is kinda conflicting...
|
||||
auto* i2c = device_find_by_name("i2c0");
|
||||
check(i2c);
|
||||
auto configuration = std::make_unique<Gt911Touch::Configuration>(
|
||||
I2C_NUM_0,
|
||||
i2c,
|
||||
800,
|
||||
480
|
||||
);
|
||||
|
||||
@@ -7,16 +7,20 @@
|
||||
|
||||
#include <Tactility/hal/Configuration.h>
|
||||
#include <Tactility/lvgl/LvglSync.h>
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/device.h>
|
||||
|
||||
bool initBoot();
|
||||
|
||||
using namespace tt::hal;
|
||||
|
||||
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
|
||||
auto* i2c_internal = device_find_by_name("i2c0");
|
||||
check(i2c_internal);
|
||||
return {
|
||||
createPower(),
|
||||
createDisplay(),
|
||||
std::make_shared<TdeckKeyboard>(),
|
||||
std::make_shared<TdeckKeyboard>(i2c_internal),
|
||||
std::make_shared<KeyboardBacklightDevice>(),
|
||||
std::make_shared<TrackballDevice>(),
|
||||
createSdCard()
|
||||
|
||||
@@ -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`.
|
||||
@@ -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;
|
||||
|
||||
@@ -29,7 +29,6 @@ dependencies:
|
||||
espressif/esp_lcd_touch_ft5x06: "1.0.6~1"
|
||||
espressif/esp_io_expander: "1.0.1"
|
||||
espressif/esp_io_expander_tca95xx_16bit: "1.0.1"
|
||||
espressif/esp_lcd_axs15231b: "2.0.2"
|
||||
lambage/esp_lcd_touch_ft6336u: "1.0.8"
|
||||
espressif/esp_lcd_st7701:
|
||||
version: "1.1.3"
|
||||
@@ -64,7 +63,7 @@ dependencies:
|
||||
espressif/esp_tinyusb:
|
||||
version: "1.7.6~1"
|
||||
rules:
|
||||
- if: "target == esp32s3"
|
||||
- if: "target in [esp32s3, esp32p4]"
|
||||
espressif/esp_lvgl_port: "2.7.2"
|
||||
lvgl/lvgl: "9.3.0"
|
||||
epdiy:
|
||||
@@ -81,5 +80,5 @@ dependencies:
|
||||
version: "1.1.4"
|
||||
rules:
|
||||
- if: "target in [esp32s3, esp32p4]"
|
||||
idf: '5.5.2'
|
||||
idf: '>=5.2.0'
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
description: ESP32 Grove Port
|
||||
|
||||
compatible: "espressif,esp32-grove"
|
||||
|
||||
properties:
|
||||
defaultMode:
|
||||
type: int
|
||||
required: true
|
||||
description: "One of enum Esp32GroveMode"
|
||||
pinSdaTx:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: SDA (I2C) or TX (UART) pin
|
||||
pinSclRx:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: SCL (I2C) or RX (UART) pin
|
||||
uartPort:
|
||||
type: int
|
||||
required: true
|
||||
description: UART port number
|
||||
i2cPort:
|
||||
type: int
|
||||
required: true
|
||||
description: I2C port number
|
||||
i2cClockFrequency:
|
||||
type: int
|
||||
required: true
|
||||
description: I2C clock frequency
|
||||
@@ -0,0 +1,29 @@
|
||||
description: ESP32 I2C Controller
|
||||
|
||||
include: ["i2c-controller.yaml"]
|
||||
|
||||
compatible: "espressif,esp32-i2c-master"
|
||||
|
||||
properties:
|
||||
port:
|
||||
type: int
|
||||
required: true
|
||||
description: |
|
||||
The port number, defined by i2c_port_t.
|
||||
Depending on the hardware, these values are available: I2C_NUM_0, I2C_NUM_1, LP_I2C_NUM_0
|
||||
clock-frequency:
|
||||
type: int
|
||||
required: true
|
||||
description: Initial clock frequency in Hz
|
||||
clock-source:
|
||||
type: int
|
||||
default: 0
|
||||
description: |
|
||||
Clock source for the I2C peripheral.
|
||||
If not specified, a default clock source will be used.
|
||||
pin-sda:
|
||||
type: phandle-array
|
||||
required: true
|
||||
pin-scl:
|
||||
type: phandle-array
|
||||
required: true
|
||||
@@ -10,7 +10,7 @@ properties:
|
||||
required: true
|
||||
description: |
|
||||
The port number, defined by i2c_port_t.
|
||||
Depending on the hardware, these values are available: I2C_NUM_0, I2C_NUM_1, LP_I2C_NUM_0
|
||||
Depending on the hardware, these values are available: I2C_NUM_0, I2C_NUM_1
|
||||
clock-frequency:
|
||||
type: int
|
||||
required: true
|
||||
|
||||
@@ -43,6 +43,16 @@ properties:
|
||||
type: int
|
||||
required: true
|
||||
description: Bus width in bits
|
||||
slot:
|
||||
type: int
|
||||
default: 1
|
||||
enum: [0, 1]
|
||||
description: SDMMC host slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1). On ESP32-P4, slot 0 uses the dedicated (non-GPIO-matrix) pins.
|
||||
max-freq-khz:
|
||||
type: int
|
||||
default: 20000
|
||||
minimum: 1
|
||||
description: Maximum SDMMC clock frequency in kHz (e.g. 40000 for SDMMC_FREQ_HIGHSPEED)
|
||||
wp-active-high:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/bindings/bindings.h>
|
||||
#include <tactility/drivers/esp32_grove.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DEFINE_DEVICETREE(esp32_grove, struct Esp32GroveConfig)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/bindings/bindings.h>
|
||||
#include <tactility/drivers/esp32_i2c_master.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DEFINE_DEVICETREE(esp32_i2c_master, struct Esp32I2cMasterConfig)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <driver/uart.h>
|
||||
#include <hal/i2c_types.h>
|
||||
#include <tactility/drivers/gpio.h>
|
||||
#include <tactility/drivers/grove.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Esp32GroveConfig {
|
||||
enum GroveMode defaultMode;
|
||||
struct GpioPinSpec pinSdaTx;
|
||||
struct GpioPinSpec pinSclRx;
|
||||
uart_port_t uartPort;
|
||||
i2c_port_t i2cPort;
|
||||
uint32_t i2cClockFrequency;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/drivers/gpio.h>
|
||||
#include <tactility/device.h>
|
||||
#include <driver/i2c_types.h>
|
||||
#include <driver/i2c_master.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Device;
|
||||
|
||||
struct Esp32I2cMasterConfig {
|
||||
i2c_port_num_t port;
|
||||
uint32_t clockFrequency;
|
||||
int32_t clkSource;
|
||||
struct GpioPinSpec pinSda;
|
||||
struct GpioPinSpec pinScl;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the i2c_master_bus_handle_t for an esp32_i2c_master Device.
|
||||
* The device must be started. Returns NULL if the device has no driver data.
|
||||
*/
|
||||
i2c_master_bus_handle_t esp32_i2c_master_get_bus_handle(struct Device* device);
|
||||
|
||||
/**
|
||||
* Returns the SCL clock frequency (Hz) configured for an esp32_i2c_master Device.
|
||||
* Reads directly from the device tree config, so the device does not need to be started.
|
||||
*/
|
||||
uint32_t esp32_i2c_master_get_clock_frequency(struct Device* device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <soc/soc_caps.h>
|
||||
#if SOC_SDMMC_HOST_SUPPORTED
|
||||
|
||||
#include <driver/sdmmc_default_configs.h>
|
||||
#include <sd_protocol_types.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
@@ -26,6 +27,8 @@ struct Esp32SdmmcConfig {
|
||||
struct GpioPinSpec pin_cd;
|
||||
struct GpioPinSpec pin_wp;
|
||||
uint8_t bus_width;
|
||||
int32_t slot;
|
||||
int32_t max_freq_khz;
|
||||
bool wp_active_high;
|
||||
bool enable_uhs;
|
||||
bool pullups;
|
||||
|
||||
@@ -167,4 +167,11 @@ bool ble_hci_gate_wait_idle(int max_ms);
|
||||
#endif
|
||||
#endif // CONFIG_ESP_HOSTED_ENABLED
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
#endif // CONFIG_BT_NIMBLE_ENABLED
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <driver/gpio.h>
|
||||
#include <soc/soc.h>
|
||||
#include <soc/gpio_periph.h>
|
||||
#include <soc/gpio_struct.h>
|
||||
#include <soc/io_mux_reg.h>
|
||||
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/esp32_gpio.h>
|
||||
@@ -65,30 +69,34 @@ static error_t set_flags(GpioDescriptor* descriptor, gpio_flags_t flags) {
|
||||
}
|
||||
|
||||
static error_t get_flags(GpioDescriptor* descriptor, gpio_flags_t* flags) {
|
||||
gpio_io_config_t esp_config;
|
||||
if (gpio_get_io_config(static_cast<gpio_num_t>(descriptor->pin), &esp_config) != ESP_OK) {
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
auto pin = static_cast<gpio_num_t>(descriptor->pin);
|
||||
gpio_flags_t output = 0;
|
||||
|
||||
if (esp_config.pu) {
|
||||
// Read pull-up, pull-down, input-enable from IO MUX register
|
||||
uint32_t pin_mux = REG_READ(GPIO_PIN_MUX_REG[pin]);
|
||||
if (pin_mux & FUN_PU) {
|
||||
output |= GPIO_FLAG_PULL_UP;
|
||||
}
|
||||
|
||||
if (esp_config.pd) {
|
||||
if (pin_mux & FUN_PD) {
|
||||
output |= GPIO_FLAG_PULL_DOWN;
|
||||
}
|
||||
|
||||
if (esp_config.ie) {
|
||||
if (pin_mux & FUN_IE) {
|
||||
output |= GPIO_FLAG_DIRECTION_INPUT;
|
||||
}
|
||||
|
||||
if (esp_config.oe) {
|
||||
// Read output-enable from GPIO matrix
|
||||
bool oe = false;
|
||||
if (pin < 32) {
|
||||
oe = (GPIO.enable & (1UL << pin)) != 0;
|
||||
} else {
|
||||
oe = (GPIO.enable1.data & (1UL << (pin - 32))) != 0;
|
||||
}
|
||||
if (oe) {
|
||||
output |= GPIO_FLAG_DIRECTION_OUTPUT;
|
||||
}
|
||||
|
||||
if (esp_config.oe_inv) {
|
||||
// Read output-inversion from GPIO matrix
|
||||
if (GPIO.func_out_sel_cfg[pin].inv_sel) {
|
||||
output |= GPIO_FLAG_ACTIVE_LOW;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/esp32_grove.h>
|
||||
#include <tactility/drivers/esp32_i2c.h>
|
||||
#include <tactility/drivers/esp32_i2c_master.h>
|
||||
#include <tactility/drivers/esp32_uart.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
|
||||
#define TAG "esp32_grove"
|
||||
|
||||
struct Esp32GroveInternal {
|
||||
Device* child_device = nullptr;
|
||||
void* child_config = nullptr;
|
||||
char* child_name = nullptr;
|
||||
GroveMode current_mode = GROVE_MODE_DISABLED;
|
||||
};
|
||||
|
||||
#define GET_CONFIG(device) ((const struct Esp32GroveConfig*)device->config)
|
||||
#define GET_DATA(device) ((struct Esp32GroveInternal*)device_get_driver_data(device))
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t stop_child(Device* device) {
|
||||
auto* data = GET_DATA(device);
|
||||
if (!data) return ERROR_NONE;
|
||||
|
||||
if (data->child_device) {
|
||||
if (data->child_device->internal) {
|
||||
if (device_is_added(data->child_device)) {
|
||||
if (device_is_ready(data->child_device)) {
|
||||
if (device_stop(data->child_device) != ERROR_NONE) {
|
||||
LOG_E(TAG, "%s: failed to stop child device", device->name);
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
}
|
||||
if (device_remove(data->child_device) != ERROR_NONE) {
|
||||
LOG_E(TAG, "%s: failed to remove child device", device->name);
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
}
|
||||
check(device_destruct(data->child_device) == ERROR_NONE);
|
||||
}
|
||||
delete data->child_device;
|
||||
data->child_device = nullptr;
|
||||
}
|
||||
|
||||
if (data->child_config) {
|
||||
if (data->current_mode == GROVE_MODE_UART) {
|
||||
delete static_cast<Esp32UartConfig*>(data->child_config);
|
||||
} else if (data->current_mode == GROVE_MODE_I2C) {
|
||||
delete static_cast<Esp32I2cMasterConfig*>(data->child_config);
|
||||
}
|
||||
data->child_config = nullptr;
|
||||
}
|
||||
|
||||
delete[] data->child_name;
|
||||
data->child_name = nullptr;
|
||||
|
||||
data->current_mode = GROVE_MODE_DISABLED;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t start_child(Device* device, GroveMode mode) {
|
||||
const auto* config = GET_CONFIG(device);
|
||||
auto* data = GET_DATA(device);
|
||||
|
||||
if (mode == GROVE_MODE_DISABLED) {
|
||||
LOG_I(TAG, "%s: Grove port disabled", device->name);
|
||||
data->current_mode = GROVE_MODE_DISABLED;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
data->child_device = new(std::nothrow) Device();
|
||||
if (!data->child_device) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
std::memset(data->child_device, 0, sizeof(Device));
|
||||
|
||||
size_t name_len = std::strlen(device->name) + 10;
|
||||
data->child_name = new(std::nothrow) char[name_len];
|
||||
if (!data->child_name) {
|
||||
delete data->child_device;
|
||||
data->child_device = nullptr;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
data->child_device->parent = device;
|
||||
const char* compatible = nullptr;
|
||||
|
||||
if (mode == GROVE_MODE_UART) {
|
||||
// Device name
|
||||
std::snprintf(data->child_name, name_len, "%s_uart", device->name);
|
||||
data->child_device->name = data->child_name;
|
||||
// Device config
|
||||
auto* uart_cfg = new(std::nothrow) struct Esp32UartConfig();
|
||||
if (!uart_cfg) {
|
||||
delete[] data->child_name;
|
||||
data->child_name = nullptr;
|
||||
delete data->child_device;
|
||||
data->child_device = nullptr;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
std::memset(uart_cfg, 0, sizeof(Esp32UartConfig));
|
||||
uart_cfg->port = config->uartPort;
|
||||
uart_cfg->pin_rx = config->pinSclRx;
|
||||
uart_cfg->pin_tx = config->pinSdaTx;
|
||||
uart_cfg->pin_cts = GPIO_PIN_SPEC_NONE;
|
||||
uart_cfg->pin_rts = GPIO_PIN_SPEC_NONE;
|
||||
data->child_config = uart_cfg;
|
||||
compatible = "espressif,esp32-uart";
|
||||
LOG_I(TAG, "%s: starting UART mode on port %d", device->name, (int)uart_cfg->port);
|
||||
} else if (mode == GROVE_MODE_I2C) {
|
||||
// Device name
|
||||
std::snprintf(data->child_name, name_len, "%s_i2c", device->name);
|
||||
data->child_device->name = data->child_name;
|
||||
// Device config
|
||||
auto* i2c_cfg = new (std::nothrow) struct Esp32I2cMasterConfig();
|
||||
if (!i2c_cfg) {
|
||||
delete[] data->child_name;
|
||||
data->child_name = nullptr;
|
||||
delete data->child_device;
|
||||
data->child_device = nullptr;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
std::memset(i2c_cfg, 0, sizeof(Esp32I2cMasterConfig));
|
||||
i2c_cfg->port = static_cast<i2c_port_num_t>(config->i2cPort);
|
||||
i2c_cfg->clockFrequency = config->i2cClockFrequency;
|
||||
i2c_cfg->pinSda = config->pinSdaTx;
|
||||
i2c_cfg->pinScl = config->pinSclRx;
|
||||
// New driver seems to require pull-up setting
|
||||
i2c_cfg->pinSda.flags |= GPIO_FLAG_PULL_UP;
|
||||
i2c_cfg->pinScl.flags |= GPIO_FLAG_PULL_UP;
|
||||
i2c_cfg->clkSource = 0; // Default
|
||||
data->child_config = i2c_cfg;
|
||||
compatible = "espressif,esp32-i2c-master";
|
||||
LOG_I(TAG, "%s: starting I2C mode on port %d", device->name, (int)config->i2cPort);
|
||||
} else {
|
||||
LOG_E(TAG, "%s: unknown mode %d", device->name, mode);
|
||||
if (data->child_name != nullptr) {
|
||||
delete[] data->child_name;
|
||||
data->child_name = nullptr;
|
||||
}
|
||||
delete data->child_device;
|
||||
data->child_device = nullptr;
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
data->child_device->config = data->child_config;
|
||||
|
||||
error_t err = device_construct_add_start(data->child_device, compatible);
|
||||
if (err != ERROR_NONE) {
|
||||
LOG_E(TAG, "%s: failed to start child device: %d", device->name, err);
|
||||
stop_child(device);
|
||||
return err;
|
||||
}
|
||||
|
||||
data->current_mode = mode;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t start_device(Device* device) {
|
||||
const auto* config = GET_CONFIG(device);
|
||||
|
||||
auto* data = new(std::nothrow) Esp32GroveInternal();
|
||||
if (!data) return ERROR_OUT_OF_MEMORY;
|
||||
device_set_driver_data(device, data);
|
||||
|
||||
if (start_child(device, config->defaultMode) != ERROR_NONE) {
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete data;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop_device(Device* device) {
|
||||
auto* data = GET_DATA(device);
|
||||
if (!data) return ERROR_NONE;
|
||||
|
||||
stop_child(device);
|
||||
delete data;
|
||||
device_set_driver_data(device, nullptr);
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t esp32_grove_set_mode(Device* device, enum GroveMode mode) {
|
||||
auto* data = GET_DATA(device);
|
||||
if (data->current_mode == mode) return ERROR_NONE;
|
||||
|
||||
error_t err = stop_child(device);
|
||||
if (err != ERROR_NONE) return err;
|
||||
|
||||
return start_child(device, mode);
|
||||
}
|
||||
|
||||
static error_t esp32_grove_get_mode(Device* device, enum GroveMode* mode) {
|
||||
auto* data = GET_DATA(device);
|
||||
*mode = data->current_mode;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static const GroveApi esp32_grove_api = {
|
||||
.set_mode = esp32_grove_set_mode,
|
||||
.get_mode = esp32_grove_get_mode
|
||||
};
|
||||
|
||||
extern Module platform_esp32_module;
|
||||
|
||||
Driver esp32_grove_driver = {
|
||||
.name = "esp32_grove",
|
||||
.compatible = (const char*[]) { "espressif,esp32-grove", nullptr },
|
||||
.start_device = start_device,
|
||||
.stop_device = stop_device,
|
||||
.api = &esp32_grove_api,
|
||||
.device_type = &GROVE_TYPE,
|
||||
.owner = &platform_esp32_module,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
@@ -175,16 +175,12 @@ static error_t start(Device* device) {
|
||||
check(gpio_descriptor_get_native_pin_number(sda_descriptor, &sda_pin) == ERROR_NONE);
|
||||
check(gpio_descriptor_get_native_pin_number(scl_descriptor, &scl_pin) == ERROR_NONE);
|
||||
|
||||
gpio_flags_t sda_flags, scl_flags;
|
||||
check(gpio_descriptor_get_flags(sda_descriptor, &sda_flags) == ERROR_NONE);
|
||||
check(gpio_descriptor_get_flags(scl_descriptor, &scl_flags) == ERROR_NONE);
|
||||
|
||||
i2c_config_t esp_config = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = sda_pin,
|
||||
.scl_io_num = scl_pin,
|
||||
.sda_pullup_en = (sda_flags & GPIO_FLAG_PULL_UP) != 0,
|
||||
.scl_pullup_en = (scl_flags & GPIO_FLAG_PULL_UP) != 0,
|
||||
.sda_pullup_en = (sda_spec.flags & GPIO_FLAG_PULL_UP) != 0,
|
||||
.scl_pullup_en = (scl_spec.flags & GPIO_FLAG_PULL_UP) != 0,
|
||||
.master {
|
||||
.clk_speed = dts_config->clockFrequency
|
||||
},
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <driver/i2c_master.h>
|
||||
|
||||
#include <new>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <tactility/error_esp32.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/gpio_controller.h>
|
||||
#include <tactility/drivers/i2c_controller.h>
|
||||
#include <tactility/drivers/esp32_i2c_master.h>
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/time.h>
|
||||
|
||||
#define TAG "esp32_i2c_master"
|
||||
|
||||
struct Esp32I2cMasterInternal {
|
||||
Mutex mutex {};
|
||||
GpioDescriptor* sda_descriptor = nullptr;
|
||||
GpioDescriptor* scl_descriptor = nullptr;
|
||||
i2c_master_bus_handle_t bus_handle = nullptr;
|
||||
i2c_master_dev_handle_t dev_handle = nullptr;
|
||||
int current_address = -1;
|
||||
|
||||
Esp32I2cMasterInternal(GpioDescriptor* sda_descriptor, GpioDescriptor* scl_descriptor) :
|
||||
sda_descriptor(sda_descriptor),
|
||||
scl_descriptor(scl_descriptor)
|
||||
{
|
||||
mutex_construct(&mutex);
|
||||
}
|
||||
|
||||
~Esp32I2cMasterInternal() {
|
||||
mutex_destruct(&mutex);
|
||||
}
|
||||
};
|
||||
|
||||
#define GET_CONFIG(device) ((Esp32I2cMasterConfig*)device->config)
|
||||
#define GET_DATA(device) ((Esp32I2cMasterInternal*)device_get_driver_data(device))
|
||||
|
||||
#define lock(data) mutex_lock(&data->mutex);
|
||||
#define unlock(data) mutex_unlock(&data->mutex);
|
||||
|
||||
struct MyI2cMasterDev {
|
||||
void* master_bus;
|
||||
uint16_t device_address;
|
||||
};
|
||||
|
||||
static esp_err_t my_i2c_master_device_change_address(i2c_master_dev_handle_t i2c_dev, uint16_t new_device_address, int timeout_ms) {
|
||||
if (i2c_dev == nullptr) return ESP_ERR_INVALID_ARG;
|
||||
auto* dev = reinterpret_cast<MyI2cMasterDev*>(i2c_dev);
|
||||
dev->device_address = new_device_address;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t my_i2c_master_transmit_multi_buffer(i2c_master_dev_handle_t dev_handle, uint8_t reg, const uint8_t* data, uint16_t data_size, int timeout_ms) {
|
||||
uint32_t total_size = data_size + 1;
|
||||
if (total_size <= 128) {
|
||||
uint8_t temp_buf[128];
|
||||
temp_buf[0] = reg;
|
||||
if (data_size > 0 && data != nullptr) {
|
||||
std::memcpy(temp_buf + 1, data, data_size);
|
||||
}
|
||||
return i2c_master_transmit(dev_handle, temp_buf, total_size, timeout_ms);
|
||||
} else {
|
||||
uint8_t* temp_buf = (uint8_t*)std::malloc(total_size);
|
||||
if (temp_buf == nullptr) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
temp_buf[0] = reg;
|
||||
if (data_size > 0 && data != nullptr) {
|
||||
std::memcpy(temp_buf + 1, data, data_size);
|
||||
}
|
||||
esp_err_t ret = i2c_master_transmit(dev_handle, temp_buf, total_size, timeout_ms);
|
||||
std::free(temp_buf);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// Switches the device's target address only when it differs from the currently configured one.
|
||||
static esp_err_t ensure_address(Esp32I2cMasterInternal* driver_data, uint8_t address, int timeout_ms) {
|
||||
if (driver_data->current_address == address) {
|
||||
return ESP_OK;
|
||||
}
|
||||
esp_err_t esp_error = my_i2c_master_device_change_address(driver_data->dev_handle, address, timeout_ms);
|
||||
if (esp_error == ESP_OK) {
|
||||
driver_data->current_address = address;
|
||||
}
|
||||
return esp_error;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
static int ticks_to_ms(TickType_t ticks) {
|
||||
if (ticks == portMAX_DELAY) return -1;
|
||||
return (int)pdTICKS_TO_MS(ticks);
|
||||
}
|
||||
|
||||
static error_t read(Device* device, uint8_t address, uint8_t* data, size_t data_size, TickType_t timeout) {
|
||||
if (xPortInIsrContext()) return ERROR_ISR_STATUS;
|
||||
if (data_size == 0) return ERROR_INVALID_ARGUMENT;
|
||||
auto* driver_data = GET_DATA(device);
|
||||
int timeout_ms = ticks_to_ms(timeout);
|
||||
|
||||
lock(driver_data);
|
||||
esp_err_t esp_error = ensure_address(driver_data, address, timeout_ms);
|
||||
if (esp_error != ESP_OK) {
|
||||
LOG_E(TAG, "change_address(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
|
||||
} else {
|
||||
esp_error = i2c_master_receive(driver_data->dev_handle, data, data_size, timeout_ms);
|
||||
if (esp_error != ESP_OK) {
|
||||
LOG_E(TAG, "receive(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
|
||||
}
|
||||
}
|
||||
unlock(driver_data);
|
||||
return esp_err_to_error(esp_error);
|
||||
}
|
||||
|
||||
static error_t write(Device* device, uint8_t address, const uint8_t* data, uint16_t data_size, TickType_t timeout) {
|
||||
if (xPortInIsrContext()) return ERROR_ISR_STATUS;
|
||||
if (data_size == 0) return ERROR_INVALID_ARGUMENT;
|
||||
auto* driver_data = GET_DATA(device);
|
||||
int timeout_ms = ticks_to_ms(timeout);
|
||||
|
||||
lock(driver_data);
|
||||
esp_err_t esp_error = ensure_address(driver_data, address, timeout_ms);
|
||||
if (esp_error != ESP_OK) {
|
||||
LOG_E(TAG, "change_address(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
|
||||
} else {
|
||||
esp_error = i2c_master_transmit(driver_data->dev_handle, data, data_size, timeout_ms);
|
||||
if (esp_error != ESP_OK) {
|
||||
LOG_E(TAG, "transmit(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
|
||||
}
|
||||
}
|
||||
unlock(driver_data);
|
||||
return esp_err_to_error(esp_error);
|
||||
}
|
||||
|
||||
static error_t write_read(Device* device, uint8_t address, const uint8_t* write_data, size_t write_data_size, uint8_t* read_data, size_t read_data_size, TickType_t timeout) {
|
||||
if (xPortInIsrContext()) return ERROR_ISR_STATUS;
|
||||
if (write_data_size == 0 || read_data_size == 0) return ERROR_INVALID_ARGUMENT;
|
||||
auto* driver_data = GET_DATA(device);
|
||||
int timeout_ms = ticks_to_ms(timeout);
|
||||
|
||||
lock(driver_data);
|
||||
esp_err_t esp_error = ensure_address(driver_data, address, timeout_ms);
|
||||
if (esp_error != ESP_OK) {
|
||||
LOG_E(TAG, "change_address(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
|
||||
} else {
|
||||
esp_error = i2c_master_transmit_receive(driver_data->dev_handle, write_data, write_data_size, read_data, read_data_size, timeout_ms);
|
||||
if (esp_error != ESP_OK) {
|
||||
LOG_E(TAG, "transmit_receive(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
|
||||
}
|
||||
}
|
||||
unlock(driver_data);
|
||||
return esp_err_to_error(esp_error);
|
||||
}
|
||||
|
||||
static error_t read_register(Device* device, uint8_t address, uint8_t reg, uint8_t* data, size_t data_size, TickType_t timeout) {
|
||||
if (xPortInIsrContext()) return ERROR_ISR_STATUS;
|
||||
if (data_size == 0) return ERROR_INVALID_ARGUMENT;
|
||||
auto* driver_data = GET_DATA(device);
|
||||
int timeout_ms = ticks_to_ms(timeout);
|
||||
|
||||
lock(driver_data);
|
||||
esp_err_t esp_error = ensure_address(driver_data, address, timeout_ms);
|
||||
if (esp_error != ESP_OK) {
|
||||
LOG_E(TAG, "change_address(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
|
||||
} else {
|
||||
esp_error = i2c_master_transmit_receive(driver_data->dev_handle, ®, 1, data, data_size, timeout_ms);
|
||||
if (esp_error != ESP_OK) {
|
||||
LOG_E(TAG, "read_register(0x%02X, reg=0x%02X) failed: %s", address, reg, esp_err_to_name(esp_error));
|
||||
}
|
||||
}
|
||||
unlock(driver_data);
|
||||
return esp_err_to_error(esp_error);
|
||||
}
|
||||
|
||||
static error_t write_register(Device* device, uint8_t address, uint8_t reg, const uint8_t* data, uint16_t data_size, TickType_t timeout) {
|
||||
if (xPortInIsrContext()) return ERROR_ISR_STATUS;
|
||||
if (data_size == 0) return ERROR_INVALID_ARGUMENT;
|
||||
auto* driver_data = GET_DATA(device);
|
||||
int timeout_ms = ticks_to_ms(timeout);
|
||||
|
||||
lock(driver_data);
|
||||
esp_err_t esp_error = ensure_address(driver_data, address, timeout_ms);
|
||||
if (esp_error != ESP_OK) {
|
||||
LOG_E(TAG, "change_address(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
|
||||
} else {
|
||||
esp_error = my_i2c_master_transmit_multi_buffer(driver_data->dev_handle, reg, data, data_size, timeout_ms);
|
||||
if (esp_error != ESP_OK) {
|
||||
LOG_E(TAG, "write_register(0x%02X, reg=0x%02X) failed: %s", address, reg, esp_err_to_name(esp_error));
|
||||
}
|
||||
}
|
||||
unlock(driver_data);
|
||||
return esp_err_to_error(esp_error);
|
||||
}
|
||||
|
||||
static error_t probe(Device* device, uint8_t address, TickType_t timeout) {
|
||||
if (xPortInIsrContext()) return ERROR_ISR_STATUS;
|
||||
auto* driver_data = GET_DATA(device);
|
||||
int timeout_ms = ticks_to_ms(timeout);
|
||||
|
||||
lock(driver_data);
|
||||
esp_err_t esp_error = i2c_master_probe(driver_data->bus_handle, address, timeout_ms);
|
||||
if (esp_error == ESP_ERR_NOT_FOUND) {
|
||||
// Expected outcome when no device acks - e.g. hot-plug attach polling
|
||||
LOG_D(TAG, "probe(0x%02X): not found", address);
|
||||
} else if (esp_error != ESP_OK) {
|
||||
LOG_E(TAG, "probe(0x%02X) failed: %s", address, esp_err_to_name(esp_error));
|
||||
}
|
||||
unlock(driver_data);
|
||||
return esp_err_to_error(esp_error);
|
||||
}
|
||||
|
||||
static error_t start(Device* device) {
|
||||
ESP_LOGI(TAG, "start %s", device->name);
|
||||
auto dts_config = GET_CONFIG(device);
|
||||
|
||||
auto& sda_spec = dts_config->pinSda;
|
||||
auto& scl_spec = dts_config->pinScl;
|
||||
auto* sda_descriptor = gpio_descriptor_acquire(sda_spec.gpio_controller, sda_spec.pin, GPIO_OWNER_GPIO);
|
||||
if (!sda_descriptor) {
|
||||
LOG_E(TAG, "Failed to acquire pin %u", sda_spec.pin);
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
auto* scl_descriptor = gpio_descriptor_acquire(scl_spec.gpio_controller, scl_spec.pin, GPIO_OWNER_GPIO);
|
||||
if (!scl_descriptor) {
|
||||
LOG_E(TAG, "Failed to acquire pin %u", scl_spec.pin);
|
||||
gpio_descriptor_release(sda_descriptor);
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
gpio_num_t sda_pin, scl_pin;
|
||||
check(gpio_descriptor_get_native_pin_number(sda_descriptor, &sda_pin) == ERROR_NONE);
|
||||
check(gpio_descriptor_get_native_pin_number(scl_descriptor, &scl_pin) == ERROR_NONE);
|
||||
|
||||
i2c_master_bus_config_t bus_config = {
|
||||
.i2c_port = dts_config->port,
|
||||
.sda_io_num = sda_pin,
|
||||
.scl_io_num = scl_pin,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = ((sda_spec.flags & GPIO_FLAG_PULL_UP) != 0) || ((scl_spec.flags & GPIO_FLAG_PULL_UP) != 0),
|
||||
}
|
||||
};
|
||||
|
||||
#if SOC_LP_I2C_SUPPORTED
|
||||
if (dts_config->port == LP_I2C_NUM_0) {
|
||||
bus_config.lp_source_clk = (dts_config->clkSource == 0) ? LP_I2C_SCLK_DEFAULT : static_cast<lp_i2c_clock_source_t>(dts_config->clkSource);
|
||||
} else {
|
||||
bus_config.clk_source = (dts_config->clkSource == 0) ? I2C_CLK_SRC_DEFAULT : static_cast<i2c_clock_source_t>(dts_config->clkSource);
|
||||
}
|
||||
#else
|
||||
bus_config.clk_source = (dts_config->clkSource == 0) ? I2C_CLK_SRC_DEFAULT : static_cast<i2c_clock_source_t>(dts_config->clkSource);
|
||||
#endif
|
||||
|
||||
i2c_master_bus_handle_t bus_handle;
|
||||
esp_err_t error = i2c_new_master_bus(&bus_config, &bus_handle);
|
||||
if (error != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to create I2C bus at port %d: %s", (int)dts_config->port, esp_err_to_name(error));
|
||||
gpio_descriptor_release(sda_descriptor);
|
||||
gpio_descriptor_release(scl_descriptor);
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
i2c_device_config_t dev_config = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = 0, // Will be changed at runtime
|
||||
.scl_speed_hz = dts_config->clockFrequency,
|
||||
.scl_wait_us = 0,
|
||||
.flags = {
|
||||
.disable_ack_check = 0,
|
||||
}
|
||||
};
|
||||
|
||||
i2c_master_dev_handle_t dev_handle;
|
||||
error = i2c_master_bus_add_device(bus_handle, &dev_config, &dev_handle);
|
||||
if (error != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to add I2C device: %s", esp_err_to_name(error));
|
||||
i2c_del_master_bus(bus_handle);
|
||||
gpio_descriptor_release(sda_descriptor);
|
||||
gpio_descriptor_release(scl_descriptor);
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
auto* data = new(std::nothrow) Esp32I2cMasterInternal(sda_descriptor, scl_descriptor);
|
||||
if (data == nullptr) {
|
||||
i2c_master_bus_rm_device(dev_handle);
|
||||
i2c_del_master_bus(bus_handle);
|
||||
gpio_descriptor_release(sda_descriptor);
|
||||
gpio_descriptor_release(scl_descriptor);
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
data->bus_handle = bus_handle;
|
||||
data->dev_handle = dev_handle;
|
||||
|
||||
device_set_driver_data(device, data);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop(Device* device) {
|
||||
ESP_LOGI(TAG, "stop %s", device->name);
|
||||
auto* driver_data = GET_DATA(device);
|
||||
|
||||
i2c_master_bus_rm_device(driver_data->dev_handle);
|
||||
i2c_del_master_bus(driver_data->bus_handle);
|
||||
|
||||
gpio_descriptor_release(driver_data->sda_descriptor);
|
||||
gpio_descriptor_release(driver_data->scl_descriptor);
|
||||
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete driver_data;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
i2c_master_bus_handle_t esp32_i2c_master_get_bus_handle(struct Device* device) {
|
||||
return GET_DATA(device)->bus_handle;
|
||||
}
|
||||
|
||||
uint32_t esp32_i2c_master_get_clock_frequency(struct Device* device) {
|
||||
return GET_CONFIG(device)->clockFrequency;
|
||||
}
|
||||
|
||||
static constexpr I2cControllerApi ESP32_I2C_MASTER_API = {
|
||||
.read = read,
|
||||
.write = write,
|
||||
.write_read = write_read,
|
||||
.read_register = read_register,
|
||||
.write_register = write_register,
|
||||
.probe = probe
|
||||
};
|
||||
|
||||
extern Module platform_esp32_module;
|
||||
|
||||
Driver esp32_i2c_master_driver = {
|
||||
.name = "esp32_i2c_master",
|
||||
.compatible = (const char*[]) { "espressif,esp32-i2c", "espressif,esp32-i2c-master", nullptr },
|
||||
.start_device = start,
|
||||
.stop_device = stop,
|
||||
.api = &ESP32_I2C_MASTER_API,
|
||||
.device_type = &I2C_CONTROLLER_TYPE,
|
||||
.owner = &platform_esp32_module,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <tactility/drivers/esp32_i2s.h>
|
||||
|
||||
#include <new>
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "esp32_i2s"
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user