New kernel drivers and device implementation updates (#561)

- Added modular device support and devicetree bindings for ILI9341, ILI9488, CST816S, XPT2046, and GPIO button input, updating several board configurations for display/touch/backlight/keyboard/battery.
- Added a setting to control deprecated HAL usage (device property + Kconfig).
This commit is contained in:
Ken Van Hoeylandt
2026-07-12 23:54:55 +02:00
committed by GitHub
parent 50c0a14a93
commit fa4a6e255c
148 changed files with 5837 additions and 2145 deletions
+3 -3
View File
@@ -1,7 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight driver esp_adc EstimatedPower vfs fatfs TCA8418
INCLUDE_DIRS "source"
REQUIRES TactilityKernel
)
@@ -1,30 +0,0 @@
#include "devices/Display.h"
#include "devices/CardputerKeyboard.h"
#include "devices/CardputerPower.h"
#include <tactility/device.h>
#include <Tactility/hal/Configuration.h>
#include <PwmBacklight.h>
#include <Tca8418.h>
using namespace tt::hal;
static bool initBoot() {
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT, 512);
}
static DeviceVector createDevices() {
auto tca8418 = std::make_shared<Tca8418>(device_find_by_name("i2c_internal"));
return {
createDisplay(),
tca8418,
std::make_shared<CardputerKeyboard>(tca8418),
std::make_shared<CardputerPower>()
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,153 +0,0 @@
#include "CardputerKeyboard.h"
#include <tactility/drivers/i2c_controller.h>
constexpr auto* TAG = "CardputerKeyb";
constexpr auto KB_ROWS = 14;
constexpr auto KB_COLS = 4;
// Lowercase Keymap
static constexpr char keymap_lc[KB_COLS][KB_ROWS] = {
{'`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '_', '=', LV_KEY_BACKSPACE},
{'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'},
{'\0', '\0', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', LV_KEY_ENTER},
{'\0', '\0', '\0', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', ' '}
};
// Uppercase Keymap
static constexpr char keymap_uc[KB_COLS][KB_ROWS] = {
{'~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '+', LV_KEY_DEL},
{'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|'},
{'\0', '\0', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', LV_KEY_ENTER},
{'\0', '\0', '\0', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', ' '}
};
// Symbol Keymap
static constexpr char keymap_sy[KB_COLS][KB_ROWS] = {
{LV_KEY_ESC, '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'},
{'\t', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'},
{'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', LV_KEY_PREV, '\0', LV_KEY_ENTER},
{'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', LV_KEY_LEFT, LV_KEY_NEXT, LV_KEY_RIGHT, '\0'}
};
void CardputerKeyboard::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
auto keyboard = static_cast<CardputerKeyboard*>(lv_indev_get_user_data(indev));
char keypress = 0;
if (xQueueReceive(keyboard->queue, &keypress, 0) == pdPASS) {
data->key = keypress;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->key = 0;
data->state = LV_INDEV_STATE_RELEASED;
}
}
void CardputerKeyboard::remap(uint8_t& row, uint8_t& col) {
// Col
uint8_t coltemp = row * 2;
if (col > 3) coltemp++;
// Row
uint8_t rowtemp = (col + 4) % 4;
row = rowtemp;
col = coltemp;
}
void CardputerKeyboard::processKeyboard() {
static bool shift_pressed = false;
static bool sym_pressed = false;
static bool cap_toggle = false;
static bool cap_toggle_armed = true;
if (keypad->update()) {
// Check if symbol or shift is pressed
for (int i = 0; i < keypad->pressed_key_count; i++) {
// Swap rows and columns
uint8_t row = keypad->pressed_list[i].row;
uint8_t column = keypad->pressed_list[i].col;
remap(row, column);
if ((row == 2) && (column == 0)) {
sym_pressed = true;
}
if ((row == 2) && (column == 1)) {
shift_pressed = true;
}
}
// Toggle caps lock
if ((sym_pressed && shift_pressed) && cap_toggle_armed) {
cap_toggle = !cap_toggle;
cap_toggle_armed = false;
}
// Process regular key input given the processed modifiers
for (int i = 0; i < keypad->pressed_key_count; i++) {
auto row = keypad->pressed_list[i].row;
auto column = keypad->pressed_list[i].col;
remap(row, column);
char chr = '\0';
if (sym_pressed) {
chr = keymap_sy[row][column];
} else if (shift_pressed || cap_toggle) {
chr = keymap_uc[row][column];
} else {
chr = keymap_lc[row][column];
}
if (chr != '\0') xQueueSend(queue, &chr, 50 / portTICK_PERIOD_MS);
}
for (int i = 0; i < keypad->released_key_count; i++) {
auto row = keypad->released_list[i].row;
auto column = keypad->released_list[i].col;
remap(row, column);
if ((row == 2) && (column == 0)) {
sym_pressed = false;
}
if ((row == 2) && (column == 1)) {
shift_pressed = false;
}
}
if ((!sym_pressed && !shift_pressed) && !cap_toggle_armed) {
cap_toggle_armed = true;
}
}
}
bool CardputerKeyboard::startLvgl(lv_display_t* display) {
keypad->init(7, 8);
assert(inputTimer == nullptr);
inputTimer = std::make_unique<tt::Timer>(tt::Timer::Type::Periodic, pdMS_TO_TICKS(20), [this] {
processKeyboard();
});
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);
inputTimer->start();
return true;
}
bool CardputerKeyboard::stopLvgl() {
assert(inputTimer);
inputTimer->stop();
inputTimer = nullptr;
lv_indev_delete(kbHandle);
kbHandle = nullptr;
return true;
}
bool CardputerKeyboard::isAttached() const {
return i2c_controller_has_device_at_address(keypad->getController(), keypad->getAddress(), 100) == ERROR_NONE;
}
@@ -1,46 +0,0 @@
#pragma once
#include <Tactility/hal/keyboard/KeyboardDevice.h>
#include <Tca8418.h>
#include <Tactility/Timer.h>
#include <freertos/queue.h>
class CardputerKeyboard final : public tt::hal::keyboard::KeyboardDevice {
lv_indev_t* kbHandle = nullptr;
QueueHandle_t queue = nullptr;
std::shared_ptr<Tca8418> keypad;
std::unique_ptr<tt::Timer> inputTimer;
void processKeyboard();
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
/**
* Remaps wiring coordinates to keyboard mapping coordinates.
* Wiring is 7x8 (rows & colums), but our keyboard definition is 4x14)
*/
static void remap(uint8_t& row, uint8_t& column);
public:
explicit CardputerKeyboard(const std::shared_ptr<Tca8418>& tca) : keypad(tca) {
queue = xQueueCreate(20, sizeof(char));
}
~CardputerKeyboard() override {
vQueueDelete(queue);
}
std::string getName() const override { return "TCA8418"; }
std::string getDescription() const override { return "TCA8418 I2C keyboard"; }
bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override;
bool isAttached() const override;
lv_indev_t* getLvglIndev() override { return kbHandle; }
};
@@ -1,85 +0,0 @@
#include "CardputerPower.h"
#include <driver/adc.h>
#include <tactility/log.h>
constexpr auto* TAG = "CardputerPower";
bool CardputerPower::adcInitCalibration() {
bool calibrated = false;
esp_err_t efuse_read_result = esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP_FIT);
if (efuse_read_result == ESP_ERR_NOT_SUPPORTED) {
LOG_W(TAG, "Calibration scheme not supported, skip software calibration");
} else if (efuse_read_result == ESP_ERR_INVALID_VERSION) {
LOG_W(TAG, "eFuse not burnt, skip software calibration");
} else if (efuse_read_result == ESP_OK) {
calibrated = true;
LOG_I(TAG, "Calibration success");
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, static_cast<adc_bits_width_t>(ADC_WIDTH_BIT_DEFAULT), 0, &adcCharacteristics);
} else {
LOG_W(TAG, "eFuse read failed, skipping calibration");
}
return calibrated;
}
uint32_t CardputerPower::adcReadValue() const {
int adc_raw = adc1_get_raw(ADC1_CHANNEL_9);
LOG_D(TAG, "Raw data: %d", adc_raw);
float voltage;
if (calibrated) {
voltage = esp_adc_cal_raw_to_voltage(adc_raw, &adcCharacteristics);
LOG_D(TAG, "Calibrated data: %f mV", voltage);
} else {
voltage = 0.0f;
}
return voltage;
}
bool CardputerPower::ensureInitialized() {
if (!initialized) {
calibrated = adcInitCalibration();
if (adc1_config_width(static_cast<adc_bits_width_t>(ADC_WIDTH_BIT_DEFAULT)) != ESP_OK) {
LOG_E(TAG, "ADC1 config width failed");
return false;
}
if (adc1_config_channel_atten(ADC1_CHANNEL_9, ADC_ATTEN_DB_11) != ESP_OK) {
LOG_E(TAG, "ADC1 config attenuation failed");
return false;
}
initialized = true;
}
return true;
}
bool CardputerPower::supportsMetric(MetricType type) const {
switch (type) {
using enum MetricType;
case BatteryVoltage:
case ChargeLevel:
return true;
default:
return false;
}
}
bool CardputerPower::getMetric(MetricType type, MetricData& data) {
if (!ensureInitialized()) {
return false;
}
switch (type) {
case MetricType::BatteryVoltage:
data.valueAsUint32 = adcReadValue() * 2;
return true;
case MetricType::ChargeLevel:
data.valueAsUint8 = chargeFromAdcVoltage.estimateCharge(adcReadValue() * 2);
return true;
default:
return false;
}
}
@@ -1,29 +0,0 @@
#pragma once
#include <Tactility/hal/power/PowerDevice.h>
#include <ChargeFromVoltage.h>
#include <esp_adc_cal.h>
#include <string>
using tt::hal::power::PowerDevice;
class CardputerPower final : public PowerDevice {
ChargeFromVoltage chargeFromAdcVoltage = ChargeFromVoltage(3.3f, 4.2f);
bool initialized = false;
esp_adc_cal_characteristics_t adcCharacteristics;
bool calibrated = false;
bool adcInitCalibration();
uint32_t adcReadValue() const;
bool ensureInitialized();
public:
std::string getName() const override { return "Cardputer Power"; }
std::string getDescription() const override { return "Power measurement via ADC"; }
bool supportsMetric(MetricType type) const override;
bool getMetric(MetricType type, MetricData& data) override;
};
@@ -1,32 +0,0 @@
#include "Display.h"
#include <PwmBacklight.h>
#include <St7789Display.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
St7789Display::Configuration panel_configuration = {
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
.verticalResolution = LCD_VERTICAL_RESOLUTION,
.gapX = 53, // Should be 52 according to https://github.com/m5stack/M5GFX/blob/master/src/M5GFX.cpp but this leaves a gap at the bottom
.gapY = 40,
.swapXY = true,
.mirrorX = true,
.mirrorY = false,
.invertColor = true,
.bufferSize = LCD_BUFFER_SIZE,
.touch = nullptr,
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
.resetPin = LCD_PIN_RESET,
.lvglSwapBytes = false
};
auto spi_configuration = std::make_shared<St7789Display::SpiConfiguration>(St7789Display::SpiConfiguration {
.spiHostDevice = LCD_SPI_HOST,
.csPin = LCD_PIN_CS,
.dcPin = LCD_PIN_DC,
.pixelClockFrequency = 62'500'000,
.transactionQueueDepth = 10
});
return std::make_shared<St7789Display>(panel_configuration, spi_configuration);
}
@@ -1,19 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <memory>
#include <driver/gpio.h>
#include <driver/spi_common.h>
constexpr auto LCD_SPI_HOST = SPI2_HOST;
constexpr auto LCD_PIN_CS = GPIO_NUM_37;
constexpr auto LCD_PIN_DC = GPIO_NUM_34; // RS
constexpr auto LCD_PIN_RESET = GPIO_NUM_33;
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_38;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 240;
constexpr auto LCD_VERTICAL_RESOLUTION = 135;
constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10;
constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT;
constexpr auto LCD_SPI_TRANSFER_SIZE_LIMIT = LCD_BUFFER_SIZE * LV_COLOR_DEPTH / 8;
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
@@ -10,6 +10,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD
display.size=1.14"
@@ -1,4 +1,6 @@
dependencies:
- Platforms/platform-esp32
- Drivers/bmi270-module
- Drivers/st7789-module
- Drivers/m5stack-module
dts: m5stack,cardputer-adv.dts
@@ -1,6 +1,8 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/battery_sense.h>
#include <tactility/bindings/esp32_adc_oneshot.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h>
@@ -11,7 +13,10 @@
#include <tactility/bindings/esp32_uart.h>
#include <bindings/bmi270.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <bindings/st7789.h>
#include <bindings/cardputer_adv_keyboard.h>
// Reference: https://docs.m5stack.com/en/core/Cardputer-Adv
/ {
@@ -33,6 +38,21 @@
gpio-count = <49>;
};
adc0 {
compatible = "espressif,esp32-adc-oneshot";
unit-id = <ADC_UNIT_1>;
clk-src = <ADC_RTC_CLK_SRC_DEFAULT>;
// GPIO10 (ADC1_CHANNEL_9 on ESP32-S3): battery voltage sense, behind a 1:2 divider.
channels = <ADC_CHANNEL_9 ADC_ATTEN_DB_12 ADC_BITWIDTH_DEFAULT>;
};
battery-sense {
compatible = "battery-sense";
io-channel = <&adc0 0>;
reference-voltage-mv = <3300>;
multiplier = <2000>;
};
i2c_internal {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_0>;
@@ -44,6 +64,11 @@
compatible = "bosch,bmi270";
reg = <0x69>;
};
keyboard {
compatible = "m5stack,cardputer-adv-keyboard";
reg = <0x34>;
};
};
port_a: grove0 {
@@ -56,15 +81,36 @@
i2cClockFrequency = <400000>;
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pin-backlight = <&gpio0 38 GPIO_FLAG_NONE>;
frequency-hz = <512>;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
cs-gpios = <&gpio0 37 GPIO_FLAG_NONE>;
pin-mosi = <&gpio0 35 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 36 GPIO_FLAG_NONE>;
display {
compatible = "display-placeholder";
display@0 {
compatible = "sitronix,st7789";
horizontal-resolution = <240>;
vertical-resolution = <135>;
// Should be 52 according to https://github.com/m5stack/M5GFX/blob/master/src/M5GFX.cpp but this leaves a gap at the bottom
gap-x = <53>;
gap-y = <40>;
swap-xy;
mirror-x;
invert-color;
pixel-clock-hz = <62500000>;
pin-dc = <&gpio0 34 GPIO_FLAG_NONE>;
pin-reset = <&gpio0 33 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
};