mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-06-20 21:05:07 +00:00
Smart tab5keyboard (#533)
This commit is contained in:
parent
e8b9a1f2a9
commit
594b8bd27e
@ -26,7 +26,6 @@ macro(tactility_project project_name)
|
|||||||
set(COMPONENTS
|
set(COMPONENTS
|
||||||
TactilityFreeRtos
|
TactilityFreeRtos
|
||||||
bm8563-module
|
bm8563-module
|
||||||
bm8563-module
|
|
||||||
bmi270-module
|
bmi270-module
|
||||||
mpu6886-module
|
mpu6886-module
|
||||||
pi4ioe5v6408-module
|
pi4ioe5v6408-module
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
#include "devices/Display.h"
|
#include "devices/Display.h"
|
||||||
#include "devices/SdCard.h"
|
|
||||||
#include "devices/Power.h"
|
#include "devices/Power.h"
|
||||||
#include "devices/Tab5Keyboard.h"
|
#include "devices/Tab5Keyboard.h"
|
||||||
|
|
||||||
@ -13,11 +12,11 @@ using namespace tt::hal;
|
|||||||
static constexpr auto* TAG = "Tab5";
|
static constexpr auto* TAG = "Tab5";
|
||||||
|
|
||||||
static DeviceVector createDevices() {
|
static DeviceVector createDevices() {
|
||||||
::Device* i2c2 = device_find_by_name("i2c2");
|
auto* i2c2 = device_find_by_name("i2c2");
|
||||||
|
check(i2c2, "i2c2 not found");
|
||||||
return {
|
return {
|
||||||
createPower(),
|
createPower(),
|
||||||
createDisplay(),
|
createDisplay(),
|
||||||
createSdCard(),
|
|
||||||
std::make_shared<Tab5Keyboard>(i2c2)
|
std::make_shared<Tab5Keyboard>(i2c2)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,8 +38,10 @@ static std::shared_ptr<tt::hal::touch::TouchDevice> createGt911Touch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static std::shared_ptr<tt::hal::touch::TouchDevice> createSt7123Touch() {
|
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>(
|
auto configuration = std::make_unique<St7123Touch::Configuration>(
|
||||||
I2C_NUM_0,
|
i2c,
|
||||||
720,
|
720,
|
||||||
1280,
|
1280,
|
||||||
false, // swapXY
|
false, // swapXY
|
||||||
|
|||||||
@ -102,7 +102,7 @@ bool Ili9881cDisplay::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, cons
|
|||||||
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
|
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
|
||||||
.in_color_format = LCD_COLOR_FMT_RGB565,
|
.in_color_format = LCD_COLOR_FMT_RGB565,
|
||||||
.out_color_format = LCD_COLOR_FMT_RGB565,
|
.out_color_format = LCD_COLOR_FMT_RGB565,
|
||||||
.num_fbs = 1, // TODO: 2?
|
.num_fbs = 2,
|
||||||
.video_timing =
|
.video_timing =
|
||||||
{
|
{
|
||||||
.h_size = 720,
|
.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_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
|
||||||
.dpi_clock_freq_mhz = 70,
|
.dpi_clock_freq_mhz = 70,
|
||||||
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
|
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
|
||||||
.num_fbs = 1,
|
.num_fbs = 2,
|
||||||
.video_timing = {
|
.video_timing = {
|
||||||
.h_size = 720,
|
.h_size = 720,
|
||||||
.v_size = 1280,
|
.v_size = 1280,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "St7123Touch.h"
|
#include "St7123Touch.h"
|
||||||
|
|
||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
|
#include <tactility/drivers/esp32_i2c_master.h>
|
||||||
#include <esp_lcd_touch_st7123.h>
|
#include <esp_lcd_touch_st7123.h>
|
||||||
#include <esp_err.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) {
|
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();
|
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_ST7123_CONFIG();
|
||||||
return esp_lcd_new_panel_io_i2c(
|
io_config.scl_speed_hz = esp32_i2c_master_get_clock_frequency(configuration->controller);
|
||||||
static_cast<esp_lcd_i2c_bus_handle_t>(configuration->port),
|
i2c_master_bus_handle_t bus = esp32_i2c_master_get_bus_handle(configuration->controller);
|
||||||
&io_config,
|
return esp_lcd_new_panel_io_i2c_v2(bus, &io_config, &outHandle) == ESP_OK;
|
||||||
&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) {
|
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 <EspLcdTouch.h>
|
||||||
#include <Tactility/TactilityCore.h>
|
#include <Tactility/TactilityCore.h>
|
||||||
#include <driver/i2c.h>
|
#include <tactility/device.h>
|
||||||
|
|
||||||
class St7123Touch final : public EspLcdTouch {
|
class St7123Touch final : public EspLcdTouch {
|
||||||
|
|
||||||
@ -12,14 +12,14 @@ public:
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
Configuration(
|
Configuration(
|
||||||
i2c_port_t port,
|
::Device* controller,
|
||||||
uint16_t xMax,
|
uint16_t xMax,
|
||||||
uint16_t yMax,
|
uint16_t yMax,
|
||||||
bool swapXy = false,
|
bool swapXy = false,
|
||||||
bool mirrorX = false,
|
bool mirrorX = false,
|
||||||
bool mirrorY = false,
|
bool mirrorY = false,
|
||||||
gpio_num_t pinInterrupt = GPIO_NUM_NC
|
gpio_num_t pinInterrupt = GPIO_NUM_NC
|
||||||
) : port(port),
|
) : controller(controller),
|
||||||
xMax(xMax),
|
xMax(xMax),
|
||||||
yMax(yMax),
|
yMax(yMax),
|
||||||
swapXy(swapXy),
|
swapXy(swapXy),
|
||||||
@ -28,7 +28,7 @@ public:
|
|||||||
pinInterrupt(pinInterrupt)
|
pinInterrupt(pinInterrupt)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
i2c_port_t port;
|
::Device* controller;
|
||||||
uint16_t xMax;
|
uint16_t xMax;
|
||||||
uint16_t yMax;
|
uint16_t yMax;
|
||||||
bool swapXy;
|
bool swapXy;
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
#include "Tab5Keyboard.h"
|
#include "Tab5Keyboard.h"
|
||||||
#include <Tactility/app/App.h>
|
#include <Tactility/app/App.h>
|
||||||
|
#include <Tactility/lvgl/Keyboard.h>
|
||||||
|
#include <Tactility/lvgl/LvglSync.h>
|
||||||
#include <tactility/drivers/i2c_controller.h>
|
#include <tactility/drivers/i2c_controller.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
#include <esp_timer.h>
|
#include <esp_timer.h>
|
||||||
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -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) {
|
bool Tab5Keyboard::startLvgl(lv_display_t* display) {
|
||||||
if (!queue) {
|
if (!queue) {
|
||||||
LOG_E("Tab5Keyboard", "Input queue allocation failed — cannot start");
|
LOG_E("Tab5Keyboard", "Input queue allocation failed — cannot start");
|
||||||
return false;
|
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;
|
symActive = false;
|
||||||
aaSticky = false;
|
aaSticky = false;
|
||||||
aaHeld = false;
|
aaHeld = false;
|
||||||
@ -383,13 +502,14 @@ bool Tab5Keyboard::startLvgl(lv_display_t* display) {
|
|||||||
repeatRow = 0xFF;
|
repeatRow = 0xFF;
|
||||||
repeatCol = 0xFF;
|
repeatCol = 0xFF;
|
||||||
repeatLastMs = 0;
|
repeatLastMs = 0;
|
||||||
updateLeds(); // both LEDs off initially
|
|
||||||
|
|
||||||
// Enable Normal-mode interrupt (bit 0)
|
configureIrqPin(); // best-effort; falls back to polling if it fails. Must run before
|
||||||
if (!writeReg(REG_INT_CFG, 0x01)) {
|
// reinitDevice() so REG_INT_CFG is written if IRQ setup succeeded.
|
||||||
LOG_E("Tab5Keyboard", "Failed to configure interrupt register");
|
|
||||||
return false;
|
// 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();
|
kbHandle = lv_indev_create();
|
||||||
lv_indev_set_type(kbHandle, LV_INDEV_TYPE_KEYPAD);
|
lv_indev_set_type(kbHandle, LV_INDEV_TYPE_KEYPAD);
|
||||||
@ -397,7 +517,8 @@ bool Tab5Keyboard::startLvgl(lv_display_t* display) {
|
|||||||
lv_indev_set_display(kbHandle, display);
|
lv_indev_set_display(kbHandle, display);
|
||||||
lv_indev_set_user_data(kbHandle, this);
|
lv_indev_set_user_data(kbHandle, this);
|
||||||
|
|
||||||
configureIrqPin(); // best-effort; falls back to polling if it fails
|
wasAttached = isAttached();
|
||||||
|
rotationOverrideActive = false;
|
||||||
|
|
||||||
assert(inputTimer == nullptr);
|
assert(inputTimer == nullptr);
|
||||||
inputTimer = std::make_unique<tt::Timer>(tt::Timer::Type::Periodic, pdMS_TO_TICKS(20), [this] {
|
inputTimer = std::make_unique<tt::Timer>(tt::Timer::Type::Periodic, pdMS_TO_TICKS(20), [this] {
|
||||||
@ -405,6 +526,10 @@ bool Tab5Keyboard::startLvgl(lv_display_t* display) {
|
|||||||
});
|
});
|
||||||
inputTimer->start();
|
inputTimer->start();
|
||||||
|
|
||||||
|
if (wasAttached) {
|
||||||
|
applyAutoRotation(true);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,16 @@ class Tab5Keyboard final : public tt::hal::keyboard::KeyboardDevice {
|
|||||||
volatile bool irqPending = false;
|
volatile bool irqPending = false;
|
||||||
bool irqConfigured = 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)
|
// Software key-repeat state (tracked by position to survive modifier changes)
|
||||||
uint32_t repeatKey = 0;
|
uint32_t repeatKey = 0;
|
||||||
uint8_t repeatRow = 0xFF;
|
uint8_t repeatRow = 0xFF;
|
||||||
@ -44,6 +54,10 @@ class Tab5Keyboard final : public tt::hal::keyboard::KeyboardDevice {
|
|||||||
void removeIrqPin();
|
void removeIrqPin();
|
||||||
static void IRAM_ATTR irqHandler(void* arg);
|
static void IRAM_ATTR irqHandler(void* arg);
|
||||||
|
|
||||||
|
void reinitDevice();
|
||||||
|
bool applyAutoRotation(bool keyboardAttached);
|
||||||
|
void checkAttachState();
|
||||||
|
|
||||||
void drainEvents();
|
void drainEvents();
|
||||||
void processKeyboard();
|
void processKeyboard();
|
||||||
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
|
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
|
||||||
@ -62,4 +76,10 @@ public:
|
|||||||
bool stopLvgl() override;
|
bool stopLvgl() override;
|
||||||
bool isAttached() const override;
|
bool isAttached() const override;
|
||||||
lv_indev_t* getLvglIndev() override { return kbHandle; }
|
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/drivers/gpio_controller.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <Tactility/hal/keyboard/KeyboardDevice.h>
|
||||||
|
|
||||||
|
#include "devices/Tab5Keyboard.h"
|
||||||
|
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/timers.h>
|
#include <freertos/timers.h>
|
||||||
|
|
||||||
@ -13,14 +17,17 @@
|
|||||||
constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1;
|
constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1;
|
||||||
constexpr auto GPIO_EXP0_PIN_HEADPHONE_DETECT = 7;
|
constexpr auto GPIO_EXP0_PIN_HEADPHONE_DETECT = 7;
|
||||||
constexpr auto HP_DETECT_POLL_MS = 1000;
|
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
|
// hp_detect_timer and kb_detect_timer are only touched from start()/stop(), which are called
|
||||||
// by the module manager — no atomic needed for the handle itself.
|
// serially by the module manager — no atomic needed for the handles themselves.
|
||||||
static TimerHandle_t hp_detect_timer = nullptr;
|
static TimerHandle_t hp_detect_timer = nullptr;
|
||||||
|
static TimerHandle_t kb_detect_timer = nullptr;
|
||||||
static std::atomic<Device*> io_expander0_cached { nullptr };
|
static std::atomic<Device*> io_expander0_cached { nullptr };
|
||||||
// Flags are written by the timer daemon task and read by start()/stop() — use atomics.
|
// 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_last { false };
|
||||||
static std::atomic<bool> hp_detect_initialized { false };
|
static std::atomic<bool> hp_detect_initialized { false };
|
||||||
|
static std::atomic<bool> kb_late_started { false };
|
||||||
|
|
||||||
static void headphoneDetectCallback(TimerHandle_t /*timer*/) {
|
static void headphoneDetectCallback(TimerHandle_t /*timer*/) {
|
||||||
Device* cached = io_expander0_cached.load(std::memory_order_acquire);
|
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" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t start() {
|
static error_t start() {
|
||||||
@ -91,23 +132,48 @@ static error_t start() {
|
|||||||
hp_detect_timer = nullptr;
|
hp_detect_timer = nullptr;
|
||||||
return ERROR_RESOURCE;
|
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;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static error_t stop() {
|
static error_t stop() {
|
||||||
if (hp_detect_timer == nullptr) {
|
if (hp_detect_timer != nullptr) {
|
||||||
return ERROR_NONE;
|
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;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,10 @@
|
|||||||
#include <tactility/bindings/root.h>
|
#include <tactility/bindings/root.h>
|
||||||
#include <tactility/bindings/esp32_ble.h>
|
#include <tactility/bindings/esp32_ble.h>
|
||||||
#include <tactility/bindings/esp32_gpio.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_i2c_master.h>
|
||||||
#include <tactility/bindings/esp32_i2s.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_uart.h>
|
||||||
#include <tactility/bindings/esp32_usbhost.h>
|
#include <tactility/bindings/esp32_usbhost.h>
|
||||||
#include <bindings/bmi270.h>
|
#include <bindings/bmi270.h>
|
||||||
@ -28,11 +28,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
i2c_internal: i2c0 {
|
i2c_internal: i2c0 {
|
||||||
compatible = "espressif,esp32-i2c";
|
compatible = "espressif,esp32-i2c-master";
|
||||||
port = <I2C_NUM_0>;
|
port = <I2C_NUM_0>;
|
||||||
clock-frequency = <100000>;
|
clock-frequency = <100000>;
|
||||||
pin-sda = <&gpio0 31 GPIO_FLAG_NONE>;
|
pin-sda = <&gpio0 31 GPIO_FLAG_PULL_UP>;
|
||||||
pin-scl = <&gpio0 32 GPIO_FLAG_NONE>;
|
pin-scl = <&gpio0 32 GPIO_FLAG_PULL_UP>;
|
||||||
|
|
||||||
io_expander0 {
|
io_expander0 {
|
||||||
compatible = "diodes,pi4ioe5v6408";
|
compatible = "diodes,pi4ioe5v6408";
|
||||||
@ -61,12 +61,14 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
i2c_port_a: i2c1 {
|
port_a: grove0 {
|
||||||
compatible = "espressif,esp32-i2c";
|
compatible = "espressif,esp32-grove";
|
||||||
port = <I2C_NUM_1>;
|
defaultMode = <GROVE_MODE_I2C>;
|
||||||
clock-frequency = <100000>;
|
pinSdaTx = <&gpio0 53 GPIO_FLAG_NONE>;
|
||||||
pin-sda = <&gpio0 53 GPIO_FLAG_NONE>;
|
pinSclRx = <&gpio0 54 GPIO_FLAG_NONE>;
|
||||||
pin-scl = <&gpio0 54 GPIO_FLAG_NONE>;
|
uartPort = <UART_NUM_1>;
|
||||||
|
i2cPort = <I2C_NUM_1>;
|
||||||
|
i2cClockFrequency = <100000>;
|
||||||
};
|
};
|
||||||
|
|
||||||
i2c_keyboard: i2c2 {
|
i2c_keyboard: i2c2 {
|
||||||
@ -78,12 +80,18 @@
|
|||||||
pin-scl = <&gpio0 1 GPIO_FLAG_PULL_UP>;
|
pin-scl = <&gpio0 1 GPIO_FLAG_PULL_UP>;
|
||||||
};
|
};
|
||||||
|
|
||||||
sdcard_spi: spi0 {
|
sdmmc0 {
|
||||||
compatible = "espressif,esp32-spi";
|
compatible = "espressif,esp32-sdmmc";
|
||||||
host = <SPI2_HOST>;
|
pin-clk = <&gpio0 43 GPIO_FLAG_NONE>;
|
||||||
pin-mosi = <&gpio0 44 GPIO_FLAG_NONE>;
|
pin-cmd = <&gpio0 44 GPIO_FLAG_NONE>;
|
||||||
pin-miso = <&gpio0 39 GPIO_FLAG_NONE>;
|
pin-d0 = <&gpio0 39 GPIO_FLAG_NONE>;
|
||||||
pin-sclk = <&gpio0 43 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
|
// ES8388 and ES7210
|
||||||
@ -97,14 +105,6 @@
|
|||||||
pin-mclk = <&gpio0 30 GPIO_FLAG_NONE>;
|
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 {
|
usbhost0 {
|
||||||
compatible = "espressif,esp32-usbhost";
|
compatible = "espressif,esp32-usbhost";
|
||||||
peripheral-map = <0>;
|
peripheral-map = <0>;
|
||||||
|
|||||||
@ -3,6 +3,9 @@
|
|||||||
#include <Tactility/hal/display/DisplayDriver.h>
|
#include <Tactility/hal/display/DisplayDriver.h>
|
||||||
|
|
||||||
#include <esp_lcd_panel_ops.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 {
|
class EspLcdDisplayDriver : public tt::hal::display::DisplayDriver {
|
||||||
|
|
||||||
@ -32,4 +35,13 @@ public:
|
|||||||
uint16_t getPixelWidth() const override { return hRes; }
|
uint16_t getPixelWidth() const override { return hRes; }
|
||||||
|
|
||||||
uint16_t getPixelHeight() const override { return vRes; }
|
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
|
||||||
};
|
};
|
||||||
|
|||||||
@ -43,6 +43,16 @@ properties:
|
|||||||
type: int
|
type: int
|
||||||
required: true
|
required: true
|
||||||
description: Bus width in bits
|
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:
|
wp-active-high:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <tactility/drivers/gpio.h>
|
#include <tactility/drivers/gpio.h>
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <driver/i2c_types.h>
|
||||||
#include <driver/i2c_master.h>
|
#include <driver/i2c_master.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@ -18,8 +20,18 @@ struct Esp32I2cMasterConfig {
|
|||||||
struct GpioPinSpec pinScl;
|
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);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <soc/soc_caps.h>
|
#include <soc/soc_caps.h>
|
||||||
#if SOC_SDMMC_HOST_SUPPORTED
|
#if SOC_SDMMC_HOST_SUPPORTED
|
||||||
|
|
||||||
|
#include <driver/sdmmc_default_configs.h>
|
||||||
#include <sd_protocol_types.h>
|
#include <sd_protocol_types.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@ -26,6 +27,8 @@ struct Esp32SdmmcConfig {
|
|||||||
struct GpioPinSpec pin_cd;
|
struct GpioPinSpec pin_cd;
|
||||||
struct GpioPinSpec pin_wp;
|
struct GpioPinSpec pin_wp;
|
||||||
uint8_t bus_width;
|
uint8_t bus_width;
|
||||||
|
int32_t slot;
|
||||||
|
int32_t max_freq_khz;
|
||||||
bool wp_active_high;
|
bool wp_active_high;
|
||||||
bool enable_uhs;
|
bool enable_uhs;
|
||||||
bool pullups;
|
bool pullups;
|
||||||
|
|||||||
@ -286,6 +286,14 @@ static error_t stop(Device* device) {
|
|||||||
return ERROR_NONE;
|
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 = {
|
static constexpr I2cControllerApi ESP32_I2C_MASTER_API = {
|
||||||
.read = read,
|
.read = read,
|
||||||
.write = write,
|
.write = write,
|
||||||
@ -308,8 +316,4 @@ Driver esp32_i2c_master_driver = {
|
|||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
i2c_master_bus_handle_t esp32_i2c_master_get_bus_handle(Device* device) {
|
|
||||||
return GET_DATA(device)->bus_handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
#include <driver/sdmmc_host.h>
|
#include <driver/sdmmc_host.h>
|
||||||
#include <esp_vfs_fat.h>
|
#include <esp_vfs_fat.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
#include <sdmmc_cmd.h>
|
#include <sdmmc_cmd.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@ -76,6 +78,8 @@ static error_t mount(void* data) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
||||||
|
host.slot = config->slot;
|
||||||
|
host.max_freq_khz = config->max_freq_khz;
|
||||||
|
|
||||||
#if SOC_SD_PWR_CTRL_SUPPORTED
|
#if SOC_SD_PWR_CTRL_SUPPORTED
|
||||||
// Treat non-positive values as disabled to remain safe with zero-initialized configs.
|
// Treat non-positive values as disabled to remain safe with zero-initialized configs.
|
||||||
@ -89,6 +93,10 @@ static error_t mount(void* data) {
|
|||||||
return ERROR_NOT_SUPPORTED;
|
return ERROR_NOT_SUPPORTED;
|
||||||
}
|
}
|
||||||
host.pwr_ctrl_handle = fs_data->pwr_ctrl_handle;
|
host.pwr_ctrl_handle = fs_data->pwr_ctrl_handle;
|
||||||
|
|
||||||
|
// On cold boot the SD card needs time for its supply rail to ramp up after the
|
||||||
|
// on-chip LDO is enabled, otherwise the initial ACMD41 (send_op_cond) times out.
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,14 @@ public:
|
|||||||
virtual uint16_t getPixelWidth() const = 0;
|
virtual uint16_t getPixelWidth() const = 0;
|
||||||
virtual uint16_t getPixelHeight() const = 0;
|
virtual uint16_t getPixelHeight() const = 0;
|
||||||
virtual bool drawBitmap(int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) = 0;
|
virtual bool drawBitmap(int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns direct pointers to the panel's hardware frame buffer(s), if the
|
||||||
|
* underlying driver supports it (DPI/MIPI-DSI panels only).
|
||||||
|
* @param[out] outBuffers receives up to 2 frame buffer pointers
|
||||||
|
* @return number of buffers written to outBuffers (0 if unsupported)
|
||||||
|
*/
|
||||||
|
virtual uint8_t getFrameBuffers(void* outBuffers[2]) const { return 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -15,7 +15,7 @@ enum class BootMode {
|
|||||||
Flash
|
Flash
|
||||||
};
|
};
|
||||||
|
|
||||||
bool startMassStorageWithSdmmc();
|
bool startMassStorageWithSdmmc(bool fromBootMode = false);
|
||||||
void stop();
|
void stop();
|
||||||
Mode getMode();
|
Mode getMode();
|
||||||
bool isSupported();
|
bool isSupported();
|
||||||
@ -28,7 +28,7 @@ void resetUsbBootMode();
|
|||||||
BootMode getUsbBootMode();
|
BootMode getUsbBootMode();
|
||||||
|
|
||||||
// Flash-based mass storage
|
// Flash-based mass storage
|
||||||
bool startMassStorageWithFlash();
|
bool startMassStorageWithFlash(bool fromBootMode = false);
|
||||||
bool canRebootIntoMassStorageFlash();
|
bool canRebootIntoMassStorageFlash();
|
||||||
void rebootIntoMassStorageFlash();
|
void rebootIntoMassStorageFlash();
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
bool tusbIsSupported();
|
bool tusbIsSupported();
|
||||||
bool tusbStartMassStorageWithSdmmc();
|
bool tusbStartMassStorageWithSdmmc(bool fromBootMode = false);
|
||||||
bool tusbStartMassStorageWithFlash();
|
bool tusbStartMassStorageWithFlash(bool fromBootMode = false);
|
||||||
void tusbStop();
|
void tusbStop();
|
||||||
bool tusbCanStartMassStorageWithFlash();
|
bool tusbCanStartMassStorageWithFlash();
|
||||||
@ -16,9 +16,12 @@
|
|||||||
|
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
#include "Tactility/app/crashdiagnostics/CrashDiagnostics.h"
|
#include "Tactility/app/crashdiagnostics/CrashDiagnostics.h"
|
||||||
#include <Tactility/kernel/PanicHandler.h>
|
#include <Tactility/kernel/PanicHandler.h>
|
||||||
|
#include <esp_system.h>
|
||||||
#include <sdkconfig.h>
|
#include <sdkconfig.h>
|
||||||
#else
|
#else
|
||||||
#define CONFIG_TT_SPLASH_DURATION 0
|
#define CONFIG_TT_SPLASH_DURATION 0
|
||||||
@ -36,6 +39,11 @@ static std::shared_ptr<hal::display::DisplayDevice> getHalDisplay() {
|
|||||||
|
|
||||||
class BootApp : public App {
|
class BootApp : public App {
|
||||||
|
|
||||||
|
// Snapshot of hal::usb::isUsbBootMode(), taken before the boot thread starts and
|
||||||
|
// potentially clears the underlying flag via setupUsbBootMode()/resetUsbBootMode().
|
||||||
|
// onShow() reads this instead of the live flag to avoid a race between the two.
|
||||||
|
static std::atomic<bool> isUsbBootSplash;
|
||||||
|
|
||||||
Thread thread = Thread(
|
Thread thread = Thread(
|
||||||
"boot",
|
"boot",
|
||||||
5120,
|
5120,
|
||||||
@ -76,12 +84,12 @@ class BootApp : public App {
|
|||||||
auto mode = hal::usb::getUsbBootMode(); // Get mode before reset
|
auto mode = hal::usb::getUsbBootMode(); // Get mode before reset
|
||||||
hal::usb::resetUsbBootMode();
|
hal::usb::resetUsbBootMode();
|
||||||
if (mode == hal::usb::BootMode::Flash) {
|
if (mode == hal::usb::BootMode::Flash) {
|
||||||
if (!hal::usb::startMassStorageWithFlash()) {
|
if (!hal::usb::startMassStorageWithFlash(true)) {
|
||||||
LOGGER.error("Unable to start flash mass storage");
|
LOGGER.error("Unable to start flash mass storage");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (mode == hal::usb::BootMode::Sdmmc) {
|
} else if (mode == hal::usb::BootMode::Sdmmc) {
|
||||||
if (!hal::usb::startMassStorageWithSdmmc()) {
|
if (!hal::usb::startMassStorageWithSdmmc(true)) {
|
||||||
LOGGER.error("Unable to start SD mass storage");
|
LOGGER.error("Unable to start SD mass storage");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -174,6 +182,9 @@ class BootApp : public App {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
void onCreate(AppContext& app) override {
|
void onCreate(AppContext& app) override {
|
||||||
|
// Snapshot before the boot thread potentially clears the flag via setupUsbBootMode()
|
||||||
|
isUsbBootSplash = hal::usb::isUsbBootMode();
|
||||||
|
|
||||||
// Just in case this app is somehow resumed
|
// Just in case this app is somehow resumed
|
||||||
if (thread.getState() == Thread::State::Stopped) {
|
if (thread.getState() == Thread::State::Stopped) {
|
||||||
thread.start();
|
thread.start();
|
||||||
@ -197,16 +208,31 @@ public:
|
|||||||
const char* logo;
|
const char* logo;
|
||||||
// TODO: Replace with automatic asset buckets like on Android
|
// TODO: Replace with automatic asset buckets like on Android
|
||||||
if (getSmallestDimension() < 150) { // e.g. Cardputer
|
if (getSmallestDimension() < 150) { // e.g. Cardputer
|
||||||
logo = hal::usb::isUsbBootMode() ? "logo_usb.png" : "logo_small.png";
|
logo = isUsbBootSplash ? "logo_usb.png" : "logo_small.png";
|
||||||
} else {
|
} else {
|
||||||
logo = hal::usb::isUsbBootMode() ? "logo_usb.png" : "logo.png";
|
logo = isUsbBootSplash ? "logo_usb.png" : "logo.png";
|
||||||
}
|
}
|
||||||
const auto logo_path = lvgl::PATH_PREFIX + paths->getAssetsPath(logo);
|
const auto logo_path = lvgl::PATH_PREFIX + paths->getAssetsPath(logo);
|
||||||
LOGGER.info("{}", logo_path);
|
LOGGER.info("{}", logo_path);
|
||||||
lv_image_set_src(image, logo_path.c_str());
|
lv_image_set_src(image, logo_path.c_str());
|
||||||
|
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
if (isUsbBootSplash) {
|
||||||
|
auto* button = lv_button_create(parent);
|
||||||
|
lv_obj_align(button, LV_ALIGN_BOTTOM_MID, 0, -16);
|
||||||
|
auto* label = lv_label_create(button);
|
||||||
|
lv_label_set_text(label, "Return to OS");
|
||||||
|
lv_obj_add_event_cb(button, [](lv_event_t*) {
|
||||||
|
hal::usb::stop();
|
||||||
|
esp_restart();
|
||||||
|
}, LV_EVENT_SHORT_CLICKED, nullptr);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::atomic<bool> BootApp::isUsbBootSplash = false;
|
||||||
|
|
||||||
extern const AppManifest manifest = {
|
extern const AppManifest manifest = {
|
||||||
.appId = "Boot",
|
.appId = "Boot",
|
||||||
.appName = "Boot",
|
.appName = "Boot",
|
||||||
|
|||||||
@ -232,7 +232,7 @@ void I2cScannerApp::onScanTimer() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint8_t address = 0; address < 128; ++address) {
|
for (uint8_t address = 1; address < 128; ++address) {
|
||||||
if (i2c_controller_has_device_at_address(safe_port, address, 10 / portTICK_PERIOD_MS) == ERROR_NONE) {
|
if (i2c_controller_has_device_at_address(safe_port, address, 10 / portTICK_PERIOD_MS) == ERROR_NONE) {
|
||||||
logger.info("Found device at address 0x{:02X}", address);
|
logger.info("Found device at address 0x{:02X}", address);
|
||||||
if (!shouldStopScanTimer()) {
|
if (!shouldStopScanTimer()) {
|
||||||
|
|||||||
@ -26,6 +26,11 @@ static uint32_t getButtonPadding(UiDensity density, uint32_t buttonSize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int32_t computeButtonMargin(int32_t available_span, int32_t total_button_size) {
|
||||||
|
const int32_t usable = std::max<int32_t>(0, available_span - (3 * total_button_size));
|
||||||
|
return std::min<int32_t>(usable / 16, total_button_size / 2);
|
||||||
|
}
|
||||||
|
|
||||||
class LauncherApp final : public App {
|
class LauncherApp final : public App {
|
||||||
|
|
||||||
static lv_obj_t* createAppButton(lv_obj_t* parent, UiDensity uiDensity, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) {
|
static lv_obj_t* createAppButton(lv_obj_t* parent, UiDensity uiDensity, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) {
|
||||||
@ -84,6 +89,50 @@ class LauncherApp final : public App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The screen object outlives the launcher's views (it's recreated by GuiService::redraw()
|
||||||
|
// via lv_obj_clean() on every app switch), so the LV_EVENT_SIZE_CHANGED callback registered
|
||||||
|
// on it must be removed once buttons_wrapper is destroyed, to avoid a dangling user-data
|
||||||
|
// pointer on the next rotation while a different app is visible.
|
||||||
|
static void onButtonsWrapperDeleted(lv_event_t* e) {
|
||||||
|
auto* buttons_wrapper = lv_event_get_target_obj(e);
|
||||||
|
auto* screen = lv_obj_get_screen(buttons_wrapper);
|
||||||
|
lv_obj_remove_event_cb_with_user_data(screen, onButtonsWrapperResized, buttons_wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-applies the flex direction and per-button margins when the display orientation
|
||||||
|
// changes while the launcher is the visible app (these are decided once at onShow()
|
||||||
|
// based on the resolution at that time, so a later rotation needs this to catch up).
|
||||||
|
static void onButtonsWrapperResized(lv_event_t* e) {
|
||||||
|
auto* buttons_wrapper = static_cast<lv_obj_t*>(lv_event_get_user_data(e));
|
||||||
|
const auto* display = lv_obj_get_display(buttons_wrapper);
|
||||||
|
|
||||||
|
const auto button_size = lvgl_get_launcher_icon_font_height();
|
||||||
|
const auto button_padding = getButtonPadding(lvgl_get_ui_density(), button_size);
|
||||||
|
const auto total_button_size = button_size + (button_padding * 2);
|
||||||
|
|
||||||
|
const auto horizontal_px = lv_display_get_horizontal_resolution(display);
|
||||||
|
const auto vertical_px = lv_display_get_vertical_resolution(display);
|
||||||
|
const bool is_landscape_display = horizontal_px >= vertical_px;
|
||||||
|
const auto current_flow = lv_obj_get_style_flex_flow(buttons_wrapper, LV_PART_MAIN);
|
||||||
|
const bool was_landscape = current_flow == LV_FLEX_FLOW_ROW;
|
||||||
|
if (is_landscape_display == was_landscape) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_obj_set_flex_flow(buttons_wrapper, is_landscape_display ? LV_FLEX_FLOW_ROW : LV_FLEX_FLOW_COLUMN);
|
||||||
|
|
||||||
|
const int32_t margin = is_landscape_display
|
||||||
|
? computeButtonMargin(horizontal_px, total_button_size)
|
||||||
|
: computeButtonMargin(vertical_px, total_button_size);
|
||||||
|
|
||||||
|
const uint32_t child_count = lv_obj_get_child_count(buttons_wrapper);
|
||||||
|
for (uint32_t i = 0; i < child_count; i++) {
|
||||||
|
auto* button = lv_obj_get_child(buttons_wrapper, i);
|
||||||
|
lv_obj_set_style_margin_hor(button, is_landscape_display ? margin : 0, LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_style_margin_ver(button, is_landscape_display ? 0 : margin, LV_STATE_DEFAULT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void onCreate(AppContext& app) override {
|
void onCreate(AppContext& app) override {
|
||||||
@ -133,19 +182,20 @@ public:
|
|||||||
lv_obj_set_flex_flow(buttons_wrapper, LV_FLEX_FLOW_COLUMN);
|
lv_obj_set_flex_flow(buttons_wrapper, LV_FLEX_FLOW_COLUMN);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t margin;
|
const int32_t margin = is_landscape_display
|
||||||
if (is_landscape_display) {
|
? computeButtonMargin(lv_display_get_horizontal_resolution(display), total_button_size)
|
||||||
const int32_t available_width = std::max<int32_t>(0, lv_display_get_horizontal_resolution(display) - (3 * total_button_size));
|
: computeButtonMargin(lv_display_get_vertical_resolution(display), total_button_size);
|
||||||
margin = std::min<int32_t>(available_width / 16, total_button_size / 2);
|
|
||||||
} else {
|
|
||||||
const int32_t available_height = std::max<int32_t>(0, lv_display_get_vertical_resolution(display) - (3 * total_button_size));
|
|
||||||
margin = std::min<int32_t>(available_height / 16, total_button_size / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_APPS, "AppList", margin, is_landscape_display);
|
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_APPS, "AppList", margin, is_landscape_display);
|
||||||
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_FOLDER, "Files", margin, is_landscape_display);
|
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_FOLDER, "Files", margin, is_landscape_display);
|
||||||
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_SETTINGS, "Settings", margin, is_landscape_display);
|
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_SETTINGS, "Settings", margin, is_landscape_display);
|
||||||
|
|
||||||
|
// The launcher's container is several levels below the screen, and LVGL only sends
|
||||||
|
// LV_EVENT_SIZE_CHANGED to the screen object itself on a resolution change - so the
|
||||||
|
// handler is attached there, with buttons_wrapper passed through as user data.
|
||||||
|
lv_obj_add_event_cb(lv_obj_get_screen(parent), onButtonsWrapperResized, LV_EVENT_SIZE_CHANGED, buttons_wrapper);
|
||||||
|
lv_obj_add_event_cb(buttons_wrapper, onButtonsWrapperDeleted, LV_EVENT_DELETE, nullptr);
|
||||||
|
|
||||||
if (shouldShowPowerButton()) {
|
if (shouldShowPowerButton()) {
|
||||||
auto* power_button = lv_button_create(parent);
|
auto* power_button = lv_button_create(parent);
|
||||||
lv_obj_set_style_pad_all(power_button, 8, 0);
|
lv_obj_set_style_pad_all(power_button, 8, 0);
|
||||||
|
|||||||
@ -69,13 +69,13 @@ bool isSupported() {
|
|||||||
return tusbIsSupported();
|
return tusbIsSupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool startMassStorageWithSdmmc() {
|
bool startMassStorageWithSdmmc(bool fromBootMode) {
|
||||||
if (!canStartNewMode()) {
|
if (!canStartNewMode()) {
|
||||||
LOGGER.error("Can't start");
|
LOGGER.error("Can't start");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tusbStartMassStorageWithSdmmc()) {
|
if (tusbStartMassStorageWithSdmmc(fromBootMode)) {
|
||||||
currentMode = Mode::MassStorageSdmmc;
|
currentMode = Mode::MassStorageSdmmc;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -110,13 +110,13 @@ void rebootIntoMassStorageSdmmc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NEW: Flash mass storage functions
|
// NEW: Flash mass storage functions
|
||||||
bool startMassStorageWithFlash() {
|
bool startMassStorageWithFlash(bool fromBootMode) {
|
||||||
if (!canStartNewMode()) {
|
if (!canStartNewMode()) {
|
||||||
LOGGER.error("Can't start flash mass storage");
|
LOGGER.error("Can't start flash mass storage");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tusbStartMassStorageWithFlash()) {
|
if (tusbStartMassStorageWithFlash(fromBootMode)) {
|
||||||
currentMode = Mode::MassStorageFlash;
|
currentMode = Mode::MassStorageFlash;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
namespace tt::hal::usb {
|
namespace tt::hal::usb {
|
||||||
|
|
||||||
bool startMassStorageWithSdmmc() { return false; }
|
bool startMassStorageWithSdmmc(bool /*fromBootMode*/) { return false; }
|
||||||
void stop() {}
|
void stop() {}
|
||||||
Mode getMode() { return Mode::Default; }
|
Mode getMode() { return Mode::Default; }
|
||||||
BootMode getUsbBootMode() { return BootMode::None; }
|
BootMode getUsbBootMode() { return BootMode::None; }
|
||||||
@ -12,7 +12,7 @@ bool isSupported() { return false; }
|
|||||||
|
|
||||||
bool canRebootIntoMassStorageSdmmc() { return false; }
|
bool canRebootIntoMassStorageSdmmc() { return false; }
|
||||||
void rebootIntoMassStorageSdmmc() {}
|
void rebootIntoMassStorageSdmmc() {}
|
||||||
bool startMassStorageWithFlash() { return false; }
|
bool startMassStorageWithFlash(bool /*fromBootMode*/) { return false; }
|
||||||
bool canRebootIntoMassStorageFlash() { return false; }
|
bool canRebootIntoMassStorageFlash() { return false; }
|
||||||
void rebootIntoMassStorageFlash() {}
|
void rebootIntoMassStorageFlash() {}
|
||||||
bool isUsbBootMode() { return false; }
|
bool isUsbBootMode() { return false; }
|
||||||
|
|||||||
@ -8,6 +8,9 @@
|
|||||||
#if CONFIG_TINYUSB_MSC_ENABLED == 1
|
#if CONFIG_TINYUSB_MSC_ENABLED == 1
|
||||||
|
|
||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
|
#include <esp_system.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
#include <tinyusb.h>
|
#include <tinyusb.h>
|
||||||
#include <tusb_msc_storage.h>
|
#include <tusb_msc_storage.h>
|
||||||
#include <wear_levelling.h>
|
#include <wear_levelling.h>
|
||||||
@ -26,6 +29,10 @@ namespace tt::hal::usb {
|
|||||||
extern sdmmc_card_t* getCard();
|
extern sdmmc_card_t* getCard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set when mass storage was started as part of the dedicated reboot-into-MSC boot flow.
|
||||||
|
// Used to decide whether ejecting the volume should automatically reboot back to normal OS.
|
||||||
|
static bool startedFromBootMode = false;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ITF_NUM_MSC = 0,
|
ITF_NUM_MSC = 0,
|
||||||
ITF_NUM_TOTAL
|
ITF_NUM_TOTAL
|
||||||
@ -99,6 +106,15 @@ static uint8_t const msc_hs_configuration_desc[] = {
|
|||||||
static void storage_mount_changed_cb(tinyusb_msc_event_t* event) {
|
static void storage_mount_changed_cb(tinyusb_msc_event_t* event) {
|
||||||
if (event->mount_changed_data.is_mounted) {
|
if (event->mount_changed_data.is_mounted) {
|
||||||
LOGGER.info("MSC Mounted");
|
LOGGER.info("MSC Mounted");
|
||||||
|
// Storage is only (re)mounted into our own filesystem after the host sends a SCSI
|
||||||
|
// START STOP UNIT eject (see tud_msc_start_stop_cb() in tusb_msc_storage.c). Windows
|
||||||
|
// is known not to send this reliably, so this is a best-effort path for hosts that do
|
||||||
|
// (e.g. Linux/macOS) - the "Return to OS" button on the boot screen is the primary one.
|
||||||
|
// If we got here while booted into MSC mode, it's safe to reboot back into normal OS now.
|
||||||
|
if (startedFromBootMode) {
|
||||||
|
LOGGER.info("MSC ejected by host, rebooting into normal OS");
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LOGGER.info("MSC Unmounted");
|
LOGGER.info("MSC Unmounted");
|
||||||
}
|
}
|
||||||
@ -147,8 +163,11 @@ static bool ensureDriverInstalled() {
|
|||||||
|
|
||||||
bool tusbIsSupported() { return true; }
|
bool tusbIsSupported() { return true; }
|
||||||
|
|
||||||
bool tusbStartMassStorageWithSdmmc() {
|
bool tusbStartMassStorageWithSdmmc(bool fromBootMode) {
|
||||||
ensureDriverInstalled();
|
if (!ensureDriverInstalled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
startedFromBootMode = fromBootMode;
|
||||||
|
|
||||||
auto* card = tt::hal::usb::getCard();
|
auto* card = tt::hal::usb::getCard();
|
||||||
if (card == nullptr) {
|
if (card == nullptr) {
|
||||||
@ -179,9 +198,12 @@ bool tusbStartMassStorageWithSdmmc() {
|
|||||||
return result == ESP_OK;
|
return result == ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool tusbStartMassStorageWithFlash() {
|
bool tusbStartMassStorageWithFlash(bool fromBootMode) {
|
||||||
LOGGER.info("Starting flash MSC");
|
LOGGER.info("Starting flash MSC");
|
||||||
ensureDriverInstalled();
|
if (!ensureDriverInstalled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
startedFromBootMode = fromBootMode;
|
||||||
|
|
||||||
wl_handle_t handle = tt::getDataPartitionWlHandle();
|
wl_handle_t handle = tt::getDataPartitionWlHandle();
|
||||||
if (handle == WL_INVALID_HANDLE) {
|
if (handle == WL_INVALID_HANDLE) {
|
||||||
@ -212,6 +234,12 @@ bool tusbStartMassStorageWithFlash() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void tusbStop() {
|
void tusbStop() {
|
||||||
|
// Actively signal a disconnect to the host before tearing down the peripheral, otherwise
|
||||||
|
// a subsequent esp_restart() resets the chip too fast for the host to notice the device
|
||||||
|
// went away, leaving it stuck showing the old MSC device until the cable is replugged.
|
||||||
|
tud_disconnect();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(250));
|
||||||
|
|
||||||
tinyusb_msc_storage_deinit();
|
tinyusb_msc_storage_deinit();
|
||||||
#if CONFIG_IDF_TARGET_ESP32P4
|
#if CONFIG_IDF_TARGET_ESP32P4
|
||||||
usb_wrap_ll_phy_select(&USB_WRAP, 1);
|
usb_wrap_ll_phy_select(&USB_WRAP, 1);
|
||||||
@ -225,8 +253,8 @@ bool tusbCanStartMassStorageWithFlash() {
|
|||||||
#else
|
#else
|
||||||
|
|
||||||
bool tusbIsSupported() { return false; }
|
bool tusbIsSupported() { return false; }
|
||||||
bool tusbStartMassStorageWithSdmmc() { return false; }
|
bool tusbStartMassStorageWithSdmmc(bool /*fromBootMode*/) { return false; }
|
||||||
bool tusbStartMassStorageWithFlash() { return false; }
|
bool tusbStartMassStorageWithFlash(bool /*fromBootMode*/) { return false; }
|
||||||
void tusbStop() {}
|
void tusbStop() {}
|
||||||
bool tusbCanStartMassStorageWithFlash() { return false; }
|
bool tusbCanStartMassStorageWithFlash() { return false; }
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,10 @@ if (DEFINED ENV{ESP_IDF_VERSION})
|
|||||||
list(APPEND PRIV_REQUIRES_LIST elf_loader)
|
list(APPEND PRIV_REQUIRES_LIST elf_loader)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
if (IDF_TARGET STREQUAL "esp32p4")
|
||||||
|
list(APPEND PRIV_REQUIRES_LIST esp_driver_ppa esp_mm)
|
||||||
|
endif ()
|
||||||
|
|
||||||
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
||||||
|
|
||||||
tactility_add_module(TactilityC
|
tactility_add_module(TactilityC
|
||||||
|
|||||||
@ -86,6 +86,15 @@ uint16_t tt_hal_display_driver_get_pixel_height(DisplayDriverHandle handle);
|
|||||||
*/
|
*/
|
||||||
void tt_hal_display_driver_draw_bitmap(DisplayDriverHandle handle, int xStart, int yStart, int xEnd, int yEnd, const void* pixelData);
|
void tt_hal_display_driver_draw_bitmap(DisplayDriverHandle handle, int xStart, int yStart, int xEnd, int yEnd, const void* pixelData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get direct pointers to the display's hardware frame buffer(s), if supported.
|
||||||
|
* Only available for panels with direct CPU-addressable frame buffers (e.g. MIPI-DSI/DPI).
|
||||||
|
* @param[in] handle the display driver handle
|
||||||
|
* @param[out] outBuffers receives up to 2 frame buffer pointers
|
||||||
|
* @return number of buffers written to outBuffers (0 if unsupported)
|
||||||
|
*/
|
||||||
|
uint8_t tt_hal_display_driver_get_frame_buffers(DisplayDriverHandle handle, void* outBuffers[2]);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -135,6 +135,10 @@ int __gtdf2(double a, double b);
|
|||||||
// GCC integer/bitwise helpers (compiler-rt)
|
// GCC integer/bitwise helpers (compiler-rt)
|
||||||
int __clzsi2(unsigned int x);
|
int __clzsi2(unsigned int x);
|
||||||
|
|
||||||
|
// GCC 64-bit integer arithmetic helpers (needed for 64-bit div on 32-bit RISC-V)
|
||||||
|
long long __divdi3(long long a, long long b);
|
||||||
|
unsigned long long __udivdi3(unsigned long long a, unsigned long long b);
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
||||||
const esp_elfsym gcc_soft_float_symbols[] = {
|
const esp_elfsym gcc_soft_float_symbols[] = {
|
||||||
@ -261,6 +265,10 @@ const esp_elfsym gcc_soft_float_symbols[] = {
|
|||||||
// GCC integer/bitwise helpers
|
// GCC integer/bitwise helpers
|
||||||
ESP_ELFSYM_EXPORT(__clzsi2),
|
ESP_ELFSYM_EXPORT(__clzsi2),
|
||||||
|
|
||||||
|
// GCC 64-bit integer arithmetic helpers
|
||||||
|
ESP_ELFSYM_EXPORT(__divdi3),
|
||||||
|
ESP_ELFSYM_EXPORT(__udivdi3),
|
||||||
|
|
||||||
ESP_ELFSYM_END
|
ESP_ELFSYM_END
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -85,4 +85,9 @@ void tt_hal_display_driver_draw_bitmap(DisplayDriverHandle handle, int xStart, i
|
|||||||
wrapper->driver->drawBitmap(xStart, yStart, xEnd, yEnd, pixelData);
|
wrapper->driver->drawBitmap(xStart, yStart, xEnd, yEnd, pixelData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t tt_hal_display_driver_get_frame_buffers(DisplayDriverHandle handle, void* outBuffers[2]) {
|
||||||
|
auto wrapper = static_cast<DriverWrapper*>(handle);
|
||||||
|
return wrapper->driver->getFrameBuffers(outBuffers);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -62,9 +62,15 @@
|
|||||||
#include <driver/i2s_std.h>
|
#include <driver/i2s_std.h>
|
||||||
#include <driver/gpio.h>
|
#include <driver/gpio.h>
|
||||||
|
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32P4
|
||||||
|
#include <driver/ppa.h>
|
||||||
|
#include <esp_cache.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
extern double __floatsidf(int x);
|
extern double __floatsidf(int x);
|
||||||
|
extern void _esp_error_check_failed(esp_err_t rc, const char *file, int line, const char *function, const char *expression);
|
||||||
|
|
||||||
const esp_elfsym main_symbols[] {
|
const esp_elfsym main_symbols[] {
|
||||||
// stdlib.h
|
// stdlib.h
|
||||||
@ -77,11 +83,13 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(rand_r),
|
ESP_ELFSYM_EXPORT(rand_r),
|
||||||
ESP_ELFSYM_EXPORT(atoi),
|
ESP_ELFSYM_EXPORT(atoi),
|
||||||
ESP_ELFSYM_EXPORT(atol),
|
ESP_ELFSYM_EXPORT(atol),
|
||||||
|
ESP_ELFSYM_EXPORT(system),
|
||||||
// esp random
|
// esp random
|
||||||
ESP_ELFSYM_EXPORT(esp_random),
|
ESP_ELFSYM_EXPORT(esp_random),
|
||||||
ESP_ELFSYM_EXPORT(esp_fill_random),
|
ESP_ELFSYM_EXPORT(esp_fill_random),
|
||||||
// esp other
|
// esp other
|
||||||
ESP_ELFSYM_EXPORT(__floatsidf),
|
ESP_ELFSYM_EXPORT(__floatsidf),
|
||||||
|
ESP_ELFSYM_EXPORT(_esp_error_check_failed),
|
||||||
// unistd.h
|
// unistd.h
|
||||||
ESP_ELFSYM_EXPORT(usleep),
|
ESP_ELFSYM_EXPORT(usleep),
|
||||||
ESP_ELFSYM_EXPORT(sleep),
|
ESP_ELFSYM_EXPORT(sleep),
|
||||||
@ -205,6 +213,7 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(fwrite),
|
ESP_ELFSYM_EXPORT(fwrite),
|
||||||
ESP_ELFSYM_EXPORT(getc),
|
ESP_ELFSYM_EXPORT(getc),
|
||||||
ESP_ELFSYM_EXPORT(putc),
|
ESP_ELFSYM_EXPORT(putc),
|
||||||
|
ESP_ELFSYM_EXPORT(putchar),
|
||||||
ESP_ELFSYM_EXPORT(puts),
|
ESP_ELFSYM_EXPORT(puts),
|
||||||
ESP_ELFSYM_EXPORT(printf),
|
ESP_ELFSYM_EXPORT(printf),
|
||||||
ESP_ELFSYM_EXPORT(sscanf),
|
ESP_ELFSYM_EXPORT(sscanf),
|
||||||
@ -212,6 +221,7 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(sprintf),
|
ESP_ELFSYM_EXPORT(sprintf),
|
||||||
ESP_ELFSYM_EXPORT(vsprintf),
|
ESP_ELFSYM_EXPORT(vsprintf),
|
||||||
ESP_ELFSYM_EXPORT(vsnprintf),
|
ESP_ELFSYM_EXPORT(vsnprintf),
|
||||||
|
ESP_ELFSYM_EXPORT(vfprintf),
|
||||||
// cstring
|
// cstring
|
||||||
ESP_ELFSYM_EXPORT(strlen),
|
ESP_ELFSYM_EXPORT(strlen),
|
||||||
ESP_ELFSYM_EXPORT(strcmp),
|
ESP_ELFSYM_EXPORT(strcmp),
|
||||||
@ -236,6 +246,7 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(memcmp),
|
ESP_ELFSYM_EXPORT(memcmp),
|
||||||
ESP_ELFSYM_EXPORT(memchr),
|
ESP_ELFSYM_EXPORT(memchr),
|
||||||
ESP_ELFSYM_EXPORT(memmove),
|
ESP_ELFSYM_EXPORT(memmove),
|
||||||
|
ESP_ELFSYM_EXPORT(strdup),
|
||||||
|
|
||||||
// ctype
|
// ctype
|
||||||
ESP_ELFSYM_EXPORT(isalnum),
|
ESP_ELFSYM_EXPORT(isalnum),
|
||||||
@ -296,6 +307,7 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(tt_hal_display_driver_lock),
|
ESP_ELFSYM_EXPORT(tt_hal_display_driver_lock),
|
||||||
ESP_ELFSYM_EXPORT(tt_hal_display_driver_unlock),
|
ESP_ELFSYM_EXPORT(tt_hal_display_driver_unlock),
|
||||||
ESP_ELFSYM_EXPORT(tt_hal_display_driver_supported),
|
ESP_ELFSYM_EXPORT(tt_hal_display_driver_supported),
|
||||||
|
ESP_ELFSYM_EXPORT(tt_hal_display_driver_get_frame_buffers),
|
||||||
ESP_ELFSYM_EXPORT(tt_hal_touch_driver_supported),
|
ESP_ELFSYM_EXPORT(tt_hal_touch_driver_supported),
|
||||||
ESP_ELFSYM_EXPORT(tt_hal_touch_driver_alloc),
|
ESP_ELFSYM_EXPORT(tt_hal_touch_driver_alloc),
|
||||||
ESP_ELFSYM_EXPORT(tt_hal_touch_driver_free),
|
ESP_ELFSYM_EXPORT(tt_hal_touch_driver_free),
|
||||||
@ -363,6 +375,8 @@ const esp_elfsym main_symbols[] {
|
|||||||
|
|
||||||
// stdio.h
|
// stdio.h
|
||||||
ESP_ELFSYM_EXPORT(rename),
|
ESP_ELFSYM_EXPORT(rename),
|
||||||
|
ESP_ELFSYM_EXPORT(rewind),
|
||||||
|
ESP_ELFSYM_EXPORT(remove),
|
||||||
// dirent.h
|
// dirent.h
|
||||||
ESP_ELFSYM_EXPORT(opendir),
|
ESP_ELFSYM_EXPORT(opendir),
|
||||||
ESP_ELFSYM_EXPORT(closedir),
|
ESP_ELFSYM_EXPORT(closedir),
|
||||||
@ -439,6 +453,7 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(heap_caps_get_allocated_size),
|
ESP_ELFSYM_EXPORT(heap_caps_get_allocated_size),
|
||||||
ESP_ELFSYM_EXPORT(heap_caps_get_free_size),
|
ESP_ELFSYM_EXPORT(heap_caps_get_free_size),
|
||||||
ESP_ELFSYM_EXPORT(heap_caps_get_largest_free_block),
|
ESP_ELFSYM_EXPORT(heap_caps_get_largest_free_block),
|
||||||
|
ESP_ELFSYM_EXPORT(heap_caps_aligned_alloc),
|
||||||
ESP_ELFSYM_EXPORT(heap_caps_malloc),
|
ESP_ELFSYM_EXPORT(heap_caps_malloc),
|
||||||
ESP_ELFSYM_EXPORT(heap_caps_calloc),
|
ESP_ELFSYM_EXPORT(heap_caps_calloc),
|
||||||
ESP_ELFSYM_EXPORT(heap_caps_free),
|
ESP_ELFSYM_EXPORT(heap_caps_free),
|
||||||
@ -449,6 +464,14 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(esp_timer_start_periodic),
|
ESP_ELFSYM_EXPORT(esp_timer_start_periodic),
|
||||||
ESP_ELFSYM_EXPORT(esp_timer_start_once),
|
ESP_ELFSYM_EXPORT(esp_timer_start_once),
|
||||||
ESP_ELFSYM_EXPORT(esp_timer_get_time),
|
ESP_ELFSYM_EXPORT(esp_timer_get_time),
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32P4
|
||||||
|
// driver/ppa.h
|
||||||
|
ESP_ELFSYM_EXPORT(ppa_register_client),
|
||||||
|
ESP_ELFSYM_EXPORT(ppa_unregister_client),
|
||||||
|
ESP_ELFSYM_EXPORT(ppa_do_scale_rotate_mirror),
|
||||||
|
// esp_cache.h
|
||||||
|
ESP_ELFSYM_EXPORT(esp_cache_msync),
|
||||||
|
#endif
|
||||||
// delimiter
|
// delimiter
|
||||||
ESP_ELFSYM_END
|
ESP_ELFSYM_END
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
#include <tactility/drivers/usb_host_midi.h>
|
#include <tactility/drivers/usb_host_midi.h>
|
||||||
#include <tactility/drivers/usb_host_msc.h>
|
#include <tactility/drivers/usb_host_msc.h>
|
||||||
#include <tactility/drivers/gpio_controller.h>
|
#include <tactility/drivers/gpio_controller.h>
|
||||||
|
#include <tactility/drivers/grove.h>
|
||||||
#include <tactility/drivers/i2c_controller.h>
|
#include <tactility/drivers/i2c_controller.h>
|
||||||
#include <tactility/drivers/i2s_controller.h>
|
#include <tactility/drivers/i2s_controller.h>
|
||||||
#include <tactility/drivers/root.h>
|
#include <tactility/drivers/root.h>
|
||||||
@ -85,6 +86,10 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(gpio_controller_init_descriptors),
|
DEFINE_MODULE_SYMBOL(gpio_controller_init_descriptors),
|
||||||
DEFINE_MODULE_SYMBOL(gpio_controller_deinit_descriptors),
|
DEFINE_MODULE_SYMBOL(gpio_controller_deinit_descriptors),
|
||||||
DEFINE_MODULE_SYMBOL(GPIO_CONTROLLER_TYPE),
|
DEFINE_MODULE_SYMBOL(GPIO_CONTROLLER_TYPE),
|
||||||
|
// drivers/grove
|
||||||
|
DEFINE_MODULE_SYMBOL(grove_set_mode),
|
||||||
|
DEFINE_MODULE_SYMBOL(grove_get_mode),
|
||||||
|
DEFINE_MODULE_SYMBOL(GROVE_TYPE),
|
||||||
// drivers/i2c_controller
|
// drivers/i2c_controller
|
||||||
DEFINE_MODULE_SYMBOL(i2c_controller_read),
|
DEFINE_MODULE_SYMBOL(i2c_controller_read),
|
||||||
DEFINE_MODULE_SYMBOL(i2c_controller_write),
|
DEFINE_MODULE_SYMBOL(i2c_controller_write),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user