Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Devices/m5stack-tab5/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c esp_lcd_st7123 esp_lcd_touch_st7123 GT911 PwmBacklight driver esp_driver_i2c vfs fatfs ina226-module
REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c esp_lcd_st7123 esp_lcd_touch_st7123 GT911Ng PwmBacklight driver esp_driver_i2c vfs fatfs ina226-module
)
10 changes: 6 additions & 4 deletions Devices/m5stack-tab5/Source/Configuration.cpp
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
#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>
#include <Tactility/hal/keyboard/KeyboardDevice.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/settings/DisplaySettings.h>

using namespace tt::hal;

static constexpr auto* TAG = "Tab5";

static DeviceVector createDevices() {
::Device* i2c2 = device_find_by_name("i2c2");
auto* i2c2 = device_find_by_name("i2c2");
check(i2c2, "i2c2 not found");
return {
createPower(),
createDisplay(),
createSdCard(),
std::make_shared<Tab5Keyboard>(i2c2)
};
}
Expand Down
14 changes: 9 additions & 5 deletions Devices/m5stack-tab5/Source/devices/Display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "St7123Display.h"
#include "St7123Touch.h"

#include <Gt911Touch.h>
#include <Gt911TouchNg.h>
#include <PwmBacklight.h>
#include <Tactility/Logger.h>
#include <Tactility/hal/gpio/Gpio.h>
Expand All @@ -19,8 +19,10 @@ 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 configuration = std::make_unique<Gt911Touch::Configuration>(
I2C_NUM_0,
auto* i2c0 = device_find_by_name("i2c0");
check(i2c0, "i2c0 not found");
auto configuration = std::make_unique<Gt911TouchNg::Configuration>(
i2c0,
720,
1280,
false, // swapXY
Expand All @@ -30,12 +32,14 @@ static std::shared_ptr<tt::hal::touch::TouchDevice> createGt911Touch() {
GPIO_NUM_NC // "GPIO_NUM_23 cannot be used due to resistor to 3V3"
// https://github.com/espressif/esp-bsp/blob/ad668c765cbad177495a122181df0a70ff9f8f61/bsp/m5stack_tab5/src/m5stack_tab5.c#L76234
);
return std::make_shared<Gt911Touch>(std::move(configuration));
return std::make_shared<Gt911TouchNg>(std::move(configuration));
}

static std::shared_ptr<tt::hal::touch::TouchDevice> createSt7123Touch() {
auto* i2c0 = device_find_by_name("i2c0");
check(i2c0, "i2c0 not found");
auto configuration = std::make_unique<St7123Touch::Configuration>(
I2C_NUM_0,
i2c0,
720,
1280,
false, // swapXY
Expand Down
2 changes: 1 addition & 1 deletion Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 0 additions & 26 deletions Devices/m5stack-tab5/Source/devices/SdCard.cpp

This file was deleted.

8 changes: 0 additions & 8 deletions Devices/m5stack-tab5/Source/devices/SdCard.h

This file was deleted.

2 changes: 1 addition & 1 deletion Devices/m5stack-tab5/Source/devices/St7123Display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 4 additions & 5 deletions Devices/m5stack-tab5/Source/devices/St7123Touch.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
#include "St7123Touch.h"

#include <Tactility/Logger.h>
#include <tactility/drivers/esp32_i2c_master.h>
#include <esp_lcd_touch_st7123.h>
#include <esp_err.h>

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) {
Expand Down
8 changes: 4 additions & 4 deletions Devices/m5stack-tab5/Source/devices/St7123Touch.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#include <EspLcdTouch.h>
#include <Tactility/TactilityCore.h>
#include <driver/i2c.h>
#include <tactility/device.h>

class St7123Touch final : public EspLcdTouch {

Expand All @@ -12,14 +12,14 @@ class St7123Touch final : public EspLcdTouch {
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),
Expand All @@ -28,7 +28,7 @@ class St7123Touch final : public EspLcdTouch {
pinInterrupt(pinInterrupt)
{}

i2c_port_t port;
::Device* controller;
uint16_t xMax;
uint16_t yMax;
bool swapXy;
Expand Down
157 changes: 141 additions & 16 deletions Devices/m5stack-tab5/Source/devices/Tab5Keyboard.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#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>
Expand Down Expand Up @@ -329,6 +331,113 @@ void Tab5Keyboard::processKeyboard() {
}
}
}

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
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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;
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -359,21 +468,31 @@ Tab5Keyboard::~Tab5Keyboard() {
}
}

// ---------------------------------------------------------------------------
// 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;
}

// Set Normal mode explicitly — device may power up in a different mode
if (!writeReg(REG_KEYBOARD_MODE, 0x00)) {
LOG_E("Tab5Keyboard", "Failed to set keyboard mode");
return false;
}
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
symActive = false;
aaSticky = false;
aaHeld = false;
Expand All @@ -383,28 +502,34 @@ bool Tab5Keyboard::startLvgl(lv_display_t* display) {
repeatRow = 0xFF;
repeatCol = 0xFF;
repeatLastMs = 0;
updateLeds(); // both LEDs off initially

// Enable Normal-mode interrupt (bit 0)
if (!writeReg(REG_INT_CFG, 0x01)) {
LOG_E("Tab5Keyboard", "Failed to configure interrupt register");
return false;
}
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);

configureIrqPin(); // best-effort; falls back to polling if it fails
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;
}

Expand Down
Loading
Loading