Implement M5Stack Cardputer + minor Tactility improvements (#331)

- Implement M5Stack Cardputer: display, SD card and keyboard
- `St7789Display` now supports a "gap" configuration
- `ElfApp` has improved errors
This commit is contained in:
Ken Van Hoeylandt
2025-09-14 02:25:10 +02:00
committed by GitHub
parent d83b98e99b
commit 62c613477a
28 changed files with 1385 additions and 5 deletions
@@ -0,0 +1,54 @@
#include "CardputerEncoder.h"
void CardputerEncoder::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
CardputerEncoder* self = static_cast<CardputerEncoder*>(lv_indev_get_user_data(indev));
self->keyboard.updateKeyList();
data->state = LV_INDEV_STATE_RELEASED;
if (self->keyboard.keyList().size() != self->lastKeyNum) {
// If key pressed
if (self->keyboard.keyList().size() != 0) {
// Update states and values
self->keyboard.updateKeysState();
if (self->keyboard.keysState().fn) {
if (self->keyboard.keysState().enter) {
data->key = LV_KEY_ENTER;
data->state = LV_INDEV_STATE_PRESSED;
} else {
for (auto& i : self->keyboard.keysState().values) {
if (i == ';') { // Up
data->enc_diff = -1;
} else if (i == '.') { // Down
data->enc_diff = 1;
}
break; // We only care about the first value
}
}
}
self->lastKeyNum = self->keyboard.keyList().size();
} else {
self->lastKeyNum = 0;
}
}
}
bool CardputerEncoder::startLvgl(lv_display_t* display) {
keyboard.init();
lvglDevice = lv_indev_create();
lv_indev_set_type(lvglDevice, LV_INDEV_TYPE_ENCODER);
lv_indev_set_read_cb(lvglDevice, &readCallback);
lv_indev_set_display(lvglDevice, display);
lv_indev_set_user_data(lvglDevice, this);
return true;
}
bool CardputerEncoder::stopLvgl() {
lv_indev_delete(lvglDevice);
lvglDevice = nullptr;
return true;
}
@@ -0,0 +1,29 @@
#pragma once
#include <Tactility/hal/encoder/EncoderDevice.h>
#include "keyboard/keyboard.h"
/**
* Wrapper around the keyboard that uses the following buttons to simulate an encoder:
* - Up
* - Down
* - ok (fn + enter)
*/
class CardputerEncoder final : public tt::hal::encoder::EncoderDevice {
KEYBOARD::Keyboard keyboard;
int lastKeyNum = 0;
lv_indev_t* _Nullable lvglDevice = nullptr;
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
public:
std::string getName() const override { return "Cardputer Encoder"; }
std::string getDescription() const override { return "Cardputer keyboard up/down acting as encoder"; }
bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override;
lv_indev_t* _Nullable getLvglIndev() override { return lvglDevice; }
};
@@ -0,0 +1,90 @@
#include "CardputerKeyboard.h"
#include <Tactility/Log.h>
constexpr auto* TAG = "Keyboard";
bool CardputerKeyboard::startLvgl(lv_display_t* display) {
keyboard.init();
lvglDevice = lv_indev_create();
lv_indev_set_type(lvglDevice, LV_INDEV_TYPE_KEYPAD);
lv_indev_set_read_cb(lvglDevice, &readCallback);
lv_indev_set_display(lvglDevice, display);
lv_indev_set_user_data(lvglDevice, this);
return true;
}
bool CardputerKeyboard::stopLvgl() {
lv_indev_delete(lvglDevice);
lvglDevice = nullptr;
return true;
}
void CardputerKeyboard::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
CardputerKeyboard* self = static_cast<CardputerKeyboard*>(lv_indev_get_user_data(indev));
data->key = 0;
data->state = LV_INDEV_STATE_RELEASED;
self->keyboard.updateKeyList();
if (self->keyboard.keyList().size() != self->lastKeyNum) {
// If key pressed
if (self->keyboard.keyList().size() != 0) {
// Update states and values
self->keyboard.updateKeysState();
if (!self->keyboard.keysState().fn) {
if (self->keyboard.keysState().enter) {
data->key = LV_KEY_ENTER;
data->state = LV_INDEV_STATE_PRESSED;
} else if (self->keyboard.keysState().space) {
data->key = ' ';
data->state = LV_INDEV_STATE_PRESSED;
} else if (self->keyboard.keysState().del) {
data->key = LV_KEY_BACKSPACE;
data->state = LV_INDEV_STATE_PRESSED;
} else {
// Normal chars
for (auto& i : self->keyboard.keysState().values) {
data->key = i;
data->state = LV_INDEV_STATE_PRESSED;
break; // We only support 1 keypress for now
}
}
} else {
if (self->keyboard.keysState().del) {
TT_LOG_I(TAG, "del");
data->key = LV_KEY_DEL;
data->state = LV_INDEV_STATE_PRESSED;
} else {
for (auto& i : self->keyboard.keysState().values) {
if (i == ';') { // Up
data->key = LV_KEY_UP;
data->state = LV_INDEV_STATE_PRESSED;
} else if (i == '.') { // Down
data->key = LV_KEY_DOWN;
data->state = LV_INDEV_STATE_PRESSED;
} else if (i == ',') { // Left
data->key = LV_KEY_LEFT;
data->state = LV_INDEV_STATE_PRESSED;
} else if (i == '/') { // Right
data->key = LV_KEY_RIGHT;
data->state = LV_INDEV_STATE_PRESSED;
} else if (i == '`') { // Escape
data->key = LV_KEY_ESC;
data->state = LV_INDEV_STATE_PRESSED;
}
break; // We only support 1 keypress for now
}
}
}
self->lastKeyNum = self->keyboard.keyList().size();
} else {
self->lastKeyNum = 0;
}
}
}
@@ -0,0 +1,26 @@
#pragma once
#include <Tactility/Thread.h>
#include <Tactility/hal/keyboard/KeyboardDevice.h>
#include "keyboard/keyboard.h"
class CardputerKeyboard : public tt::hal::keyboard::KeyboardDevice {
KEYBOARD::Keyboard keyboard;
int lastKeyNum = 0;
lv_indev_t* _Nullable lvglDevice = nullptr;
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
public:
std::string getName() const override { return "Cardputer Keyboard"; }
std::string getDescription() const override { return "Cardputer internal keyboard"; }
bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override;
bool isAttached() const override { return true; }
lv_indev_t* _Nullable getLvglIndev() override { return lvglDevice; }
};
@@ -0,0 +1,28 @@
#include "Display.h"
#include <PwmBacklight.h>
#include <St7789Display.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto configuration = std::make_unique<St7789Display::Configuration>(
LCD_SPI_HOST,
LCD_PIN_CS,
LCD_PIN_DC,
LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION,
nullptr,
true,
true,
false,
true,
0,
53, // Should be 52 according to https://github.com/m5stack/M5GFX/blob/master/src/M5GFX.cpp but this leaves a gap at the bottom
40
);
configuration->resetPin = LCD_PIN_RESET;
configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty;
auto display = std::make_shared<St7789Display>(std::move(configuration));
return std::reinterpret_pointer_cast<tt::hal::display::DisplayDevice>(display);
}
@@ -0,0 +1,15 @@
#pragma once
#include "Tactility/hal/display/DisplayDevice.h"
#include <memory>
#define LCD_SPI_HOST SPI2_HOST
#define LCD_PIN_CS GPIO_NUM_37
#define LCD_PIN_DC GPIO_NUM_34 // RS
#define LCD_PIN_RESET GPIO_NUM_33
#define LCD_HORIZONTAL_RESOLUTION 240
#define LCD_VERTICAL_RESOLUTION 135
#define LCD_DRAW_BUFFER_HEIGHT (LCD_VERTICAL_RESOLUTION / 10)
#define LCD_DRAW_BUFFER_SIZE (LCD_HORIZONTAL_RESOLUTION * LCD_DRAW_BUFFER_HEIGHT)
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
@@ -0,0 +1,25 @@
#include "SdCard.h"
#include <Tactility/hal/sdcard/SpiSdCardDevice.h>
constexpr auto SDCARD_PIN_CS = GPIO_NUM_12;
constexpr auto LCD_PIN_CS = GPIO_NUM_37;
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,
tt::hal::spi::getLock(SPI3_HOST),
std::vector { LCD_PIN_CS },
SPI3_HOST
);
return std::make_shared<SpiSdCardDevice>(
std::move(configuration)
);
}
@@ -0,0 +1,7 @@
#pragma once
#include "Tactility/hal/sdcard/SdCardDevice.h"
using tt::hal::sdcard::SdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard();