Cardputer adv and more (#395)

- Fixed TCA8418 driver
- Updated T-Lora Pager for TCA driver fixes
- Fixed issues with T-Lora keyboard driver
- Implemented Cardputer Adv
- Cleanup of Cardputer (regular)
- Fix sdkconfig for E32R28T and E32R32P
- Disable Wi-Fi on boot (was accidentally pushed before)
This commit is contained in:
Ken Van Hoeylandt 2025-10-28 00:39:31 +01:00 committed by GitHub
parent 647678ff82
commit efd3c6041c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 742 additions and 60 deletions

View File

@ -33,6 +33,7 @@ jobs:
{ id: lilygo-tdongle-s3, arch: esp32s3 },
{ id: lilygo-tlora-pager, arch: esp32s3 },
{ id: m5stack-cardputer, arch: esp32s3 },
{ id: m5stack-cardputer-adv, arch: esp32s3 },
{ id: m5stack-core2, arch: esp32 },
{ id: m5stack-cores3, arch: esp32s3 },
{ id: m5stack-stickc-plus, arch: esp32 },

View File

@ -1,6 +1,7 @@
#pragma once
#include "Tactility/hal/sdcard/SdCardDevice.h"
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <memory>
using tt::hal::sdcard::SdCardDevice;

View File

@ -2,8 +2,7 @@
#include <Tactility/hal/i2c/I2c.h>
#include <driver/i2c.h>
#include "driver/gpio.h"
#include "freertos/queue.h"
#include <driver/gpio.h>
#include <Tactility/Log.h>
@ -12,37 +11,37 @@ constexpr auto* TAG = "TpagerKeyboard";
constexpr auto BACKLIGHT = GPIO_NUM_46;
constexpr auto KB_ROWS = 4;
constexpr auto KB_COLS = 11;
constexpr auto KB_COLS = 10;
// Lowercase Keymap
static constexpr char keymap_lc[KB_ROWS][KB_COLS] = {
{'\0', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'},
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '\n', '\0'},
{'z', 'x', 'c', 'v', 'b', 'n', 'm', '\0', LV_KEY_BACKSPACE, ' ', '\0'},
{'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'}
{'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'},
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', LV_KEY_ENTER},
{'\0', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '\0', LV_KEY_BACKSPACE},
{' ', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'}
};
// Uppercase Keymap
static constexpr char keymap_uc[KB_ROWS][KB_COLS] = {
{'\0', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'},
{'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', '\n', '\0'},
{'Z', 'X', 'C', 'V', 'B', 'N', 'M', '\0', LV_KEY_BACKSPACE, ' ', '\0'},
{'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'}
{'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'},
{'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', LV_KEY_ENTER},
{'\0', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '\0', LV_KEY_BACKSPACE},
{' ', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'}
};
// Symbol Keymap
static constexpr char keymap_sy[KB_ROWS][KB_COLS] = {
{'\0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'},
{'.', '/', '+', '-', '=', ':', '\'', '"', '@', '\t', '\0'},
{'_', '$', ';', '?', '!', ',', '.', '\0', LV_KEY_BACKSPACE, ' ', '\0'},
{'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'}
{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'},
{'.', '/', '+', '-', '=', ':', '\'', '"', '@', '\t'},
{'\0', '_', '$', ';', '?', '!', ',', '.', '\0', LV_KEY_BACKSPACE},
{' ', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'}
};
void TpagerKeyboard::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
auto keyboard = static_cast<TpagerKeyboard*>(lv_indev_get_user_data(indev));
char keypress = 0;
if (xQueueReceive(keyboard->queue, &keypress, pdMS_TO_TICKS(50)) == pdPASS) {
if (xQueueReceive(keyboard->queue, &keypress, 0) == pdPASS) {
data->key = keypress;
data->state = LV_INDEV_STATE_PRESSED;
} else {
@ -65,10 +64,10 @@ void TpagerKeyboard::processKeyboard() {
auto col = keypad->pressed_list[i].col;
auto hold = keypad->pressed_list[i].hold_time;
if ((row == 1) && (col == 10)) {
if ((row == 2) && (col == 0)) {
sym_pressed = true;
}
if ((row == 2) && (col == 7)) {
if ((row == 2) && (col == 8)) {
shift_pressed = true;
}
}
@ -91,17 +90,17 @@ void TpagerKeyboard::processKeyboard() {
chr = keymap_lc[row][col];
}
if (chr != '\0') xQueueSend(queue, &chr, portMAX_DELAY);
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 col = keypad->released_list[i].col;
if ((row == 1) && (col == 10)) {
if ((row == 2) && (col == 0)) {
sym_pressed = false;
}
if ((row == 2) && (col == 7)) {
if ((row == 2) && (col == 8)) {
shift_pressed = false;
}
}

View File

@ -1,12 +1,12 @@
#pragma once
#include <Tactility/hal/keyboard/KeyboardDevice.h>
#include <Tactility/Timer.h>
#include <Tca8418.h>
#include <driver/gpio.h>
#include <driver/ledc.h>
#include <Tactility/Timer.h>
#include <freertos/queue.h>
class TpagerKeyboard final : public tt::hal::keyboard::KeyboardDevice {
@ -16,7 +16,7 @@ class TpagerKeyboard final : public tt::hal::keyboard::KeyboardDevice {
ledc_channel_t backlightChannel;
bool backlightOkay = false;
int backlightImpulseDuty = 0;
QueueHandle_t queue;
QueueHandle_t queue = nullptr;
std::shared_ptr<Tca8418> keypad;
std::unique_ptr<tt::Timer> inputTimer;
@ -30,11 +30,11 @@ class TpagerKeyboard final : public tt::hal::keyboard::KeyboardDevice {
public:
TpagerKeyboard(const std::shared_ptr<Tca8418>& tca) : keypad(tca) {
explicit TpagerKeyboard(const std::shared_ptr<Tca8418>& tca) : keypad(tca) {
queue = xQueueCreate(20, sizeof(char));
}
~TpagerKeyboard() {
~TpagerKeyboard() override {
vQueueDelete(queue);
}

View File

@ -1,16 +1,19 @@
#include "InitBoot.h"
#include "devices/Display.h"
#include "devices/SdCard.h"
#include "devices/CardputerEncoder.h"
#include "devices/CardputerKeyboard.h"
#include "devices/CardputerPower.h"
#include <PwmBacklight.h>
#include <Tactility/hal/Configuration.h>
#include <Tactility/lvgl/LvglSync.h>
#include <lvgl.h>
using namespace tt::hal;
bool initBoot() {
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT, 512);
}
static DeviceVector createDevices() {
return {
createSdCard(),
@ -28,7 +31,7 @@ extern const Configuration hardwareConfiguration = {
.i2c {
i2c::Configuration {
.name = "Port A", // Grove
.port = I2C_NUM_1,
.port = I2C_NUM_0,
.initMode = i2c::InitMode::Disabled,
.isMutable = true,
.config = (i2c_config_t) {

View File

@ -1,10 +0,0 @@
#include <PwmBacklight.h>
#include <Tactility/Log.h>
constexpr auto* TAG = "Cardputer";
bool initBoot() {
TT_LOG_I(TAG, "initBoot");
return driver::pwmbacklight::init(GPIO_NUM_38, 512);
}

View File

@ -1,3 +0,0 @@
#pragma once
bool initBoot();

View File

@ -3,6 +3,7 @@
#include <Tactility/hal/power/PowerDevice.h>
#include <ChargeFromVoltage.h>
#include <esp_adc_cal.h>
#include <string>
using tt::hal::power::PowerDevice;

View File

@ -9,6 +9,7 @@ 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;

View File

@ -1,6 +1,7 @@
#pragma once
#include "Tactility/hal/sdcard/SdCardDevice.h"
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <memory>
using tt::hal::sdcard::SdCardDevice;

View File

@ -0,0 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight driver esp_adc EstimatedPower vfs fatfs TCA8418
)

View File

@ -0,0 +1,145 @@
#include "devices/Display.h"
#include "devices/SdCard.h"
#include "devices/CardputerKeyboard.h"
#include "devices/CardputerPower.h"
#include <Tactility/hal/Configuration.h>
#include <Tactility/lvgl/LvglSync.h>
#include <PwmBacklight.h>
#include <Tca8418.h>
using namespace tt::hal;
bool initBoot() {
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT, 512);
}
static DeviceVector createDevices() {
auto tca8418 = std::make_shared<Tca8418>(I2C_NUM_0);
return {
createSdCard(),
createDisplay(),
tca8418,
std::make_shared<CardputerKeyboard>(tca8418),
std::make_shared<CardputerPower>()
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.uiScale = UiScale::Smallest,
.createDevices = createDevices,
.i2c {
i2c::Configuration {
.name = "Main",
.port = I2C_NUM_0,
.initMode = i2c::InitMode::ByTactility,
.isMutable = false,
.config = (i2c_config_t) {
.mode = I2C_MODE_MASTER,
.sda_io_num = GPIO_NUM_8,
.scl_io_num = GPIO_NUM_9,
.sda_pullup_en = true,
.scl_pullup_en = true,
.master = {
.clk_speed = 400000
},
.clk_flags = 0
}
},
i2c::Configuration {
.name = "Port A", // Grove
.port = I2C_NUM_1,
.initMode = i2c::InitMode::Disabled,
.isMutable = true,
.config = (i2c_config_t) {
.mode = I2C_MODE_MASTER,
.sda_io_num = GPIO_NUM_2,
.scl_io_num = GPIO_NUM_1,
.sda_pullup_en = true,
.scl_pullup_en = true,
.master = {
.clk_speed = 400000
},
.clk_flags = 0
}
},
},
.spi {
// Display
spi::Configuration {
.device = SPI2_HOST,
.dma = SPI_DMA_CH_AUTO,
.config = {
.mosi_io_num = GPIO_NUM_35,
.miso_io_num = GPIO_NUM_NC,
.sclk_io_num = GPIO_NUM_36,
.quadwp_io_num = GPIO_NUM_NC,
.quadhd_io_num = GPIO_NUM_NC,
.data4_io_num = GPIO_NUM_NC,
.data5_io_num = GPIO_NUM_NC,
.data6_io_num = GPIO_NUM_NC,
.data7_io_num = GPIO_NUM_NC,
.data_io_default_level = false,
.max_transfer_sz = LCD_SPI_TRANSFER_SIZE_LIMIT,
.flags = 0,
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
.intr_flags = 0
},
.initMode = spi::InitMode::ByTactility,
.isMutable = false,
.lock = tt::lvgl::getSyncLock()
},
// SDCard
spi::Configuration {
.device = SPI3_HOST,
.dma = SPI_DMA_CH_AUTO,
.config = {
.mosi_io_num = GPIO_NUM_14,
.miso_io_num = GPIO_NUM_39,
.sclk_io_num = GPIO_NUM_40,
.quadwp_io_num = GPIO_NUM_NC,
.quadhd_io_num = GPIO_NUM_NC,
.data4_io_num = GPIO_NUM_NC,
.data5_io_num = GPIO_NUM_NC,
.data6_io_num = GPIO_NUM_NC,
.data7_io_num = GPIO_NUM_NC,
.data_io_default_level = false,
.max_transfer_sz = 0,
.flags = 0,
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
.intr_flags = 0
},
.initMode = spi::InitMode::ByTactility,
.isMutable = false,
.lock = nullptr
},
},
.uart {
uart::Configuration {
.name = "Port A",
.port = UART_NUM_1,
.rxPin = GPIO_NUM_2,
.txPin = GPIO_NUM_1,
.rtsPin = GPIO_NUM_NC,
.ctsPin = GPIO_NUM_NC,
.rxBufferSize = 1024,
.txBufferSize = 1024,
.config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.rx_flow_ctrl_thresh = 0,
.source_clk = UART_SCLK_DEFAULT,
.flags = {
.allow_pd = 0,
.backup_before_sleep = 0,
}
}
}
}
};

View File

@ -0,0 +1,155 @@
#include "CardputerKeyboard.h"
#include <Tactility/hal/i2c/I2c.h>
constexpr auto* TAG = "CardputerKeyb";
constexpr auto BACKLIGHT = GPIO_NUM_46;
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, [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(20 / portTICK_PERIOD_MS);
return true;
}
bool CardputerKeyboard::stopLvgl() {
assert(inputTimer);
inputTimer->stop();
inputTimer = nullptr;
lv_indev_delete(kbHandle);
kbHandle = nullptr;
return true;
}
bool CardputerKeyboard::isAttached() const {
return tt::hal::i2c::masterHasDeviceAtAddress(keypad->getPort(), keypad->getAddress(), 100);
}

View File

@ -0,0 +1,46 @@
#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* _Nullable 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* _Nullable getLvglIndev() override { return kbHandle; }
};

View File

@ -0,0 +1,85 @@
#include "CardputerPower.h"
#include <Tactility/Log.h>
#include <driver/adc.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) {
TT_LOG_W(TAG, "Calibration scheme not supported, skip software calibration");
} else if (efuse_read_result == ESP_ERR_INVALID_VERSION) {
TT_LOG_W(TAG, "eFuse not burnt, skip software calibration");
} else if (efuse_read_result == ESP_OK) {
calibrated = true;
TT_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 {
TT_LOG_W(TAG, "eFuse read failed, skipping calibration");
}
return calibrated;
}
uint32_t CardputerPower::adcReadValue() const {
int adc_raw = adc1_get_raw(ADC1_CHANNEL_9);
TT_LOG_D(TAG, "Raw data: %d", adc_raw);
float voltage;
if (calibrated) {
voltage = esp_adc_cal_raw_to_voltage(adc_raw, &adcCharacteristics);
TT_LOG_D(TAG, "Calibrated data: %d 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) {
TT_LOG_E(TAG, "ADC1 config width failed");
return false;
}
if (adc1_config_channel_atten(ADC1_CHANNEL_9, ADC_ATTEN_DB_11) != ESP_OK) {
TT_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;
}
}

View File

@ -0,0 +1,29 @@
#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;
};

View File

@ -0,0 +1,31 @@
#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
};
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);
}

View File

@ -0,0 +1,19 @@
#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();

View File

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

View File

@ -0,0 +1,8 @@
#pragma once
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <memory>
using tt::hal::sdcard::SdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard();

View File

@ -61,6 +61,8 @@ function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE)
set(TACTILITY_BOARD_PROJECT LilygoTLoraPager)
elseif (board_id STREQUAL "m5stack-cardputer")
set(TACTILITY_BOARD_PROJECT M5stackCardputer)
elseif (board_id STREQUAL "m5stack-cardputer-adv")
set(TACTILITY_BOARD_PROJECT M5stackCardputerAdv)
elseif (board_id STREQUAL "m5stack-core2")
set(TACTILITY_BOARD_PROJECT M5stackCore2)
elseif (board_id STREQUAL "m5stack-cores3")

View File

@ -1 +1 @@
enableOnBoot=true
enableOnBoot=false

View File

@ -6,6 +6,7 @@
## Higher Priority
- Fix Cardputer (original): use LV_KEY_NEXT and _PREV in keyboard mapping instead of encoder driver hack (and check GPIO app if it then hangs too)
- Logging with a function that uses std::format
- Calculator bugs (see GitHub issue)
- Expose http::download() and main dispatcher to TactiltyC.

View File

@ -0,0 +1,26 @@
Software License Agreement (BSD License)
Copyright (c) 2019 Limor Fried (Adafruit Industries)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holders nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -2,3 +2,4 @@
[Datasheet](https://www.ti.com/lit/ds/symlink/tca8418.pdf?ts=1751500237439)
[Original implementation](https://github.com/AnthonyDiGirolamo/i2c-thumb-keyboard/tree/master) by Anthony DiGirolamo
[Adafruit TCA8418](https://github.com/adafruit/Adafruit_TCA8418)

View File

@ -1,7 +1,7 @@
#include "Tca8418.h"
#include <Tactility/Log.h>
#define TAG "tca8418"
constexpr auto TAG = "TCA8418";
namespace registers {
static const uint8_t CFG = 0x01U;
@ -22,6 +22,38 @@ static const uint8_t KEY_EVENT_J = 0x0DU;
} // namespace registers
/** From https://github.com/adafruit/Adafruit_TCA8418/blob/main/Adafruit_TCA8418.cpp */
bool Tca8418::initMatrix(uint8_t rows, uint8_t columns) {
if ((rows > 8) || (columns > 10))
return false;
if ((rows != 0) && (columns != 0)) {
// Configure the keypad matrix.
uint8_t mask = 0x00;
for (int r = 0; r < rows; r++) {
mask <<= 1;
mask |= 1;
}
writeRegister(registers::KP_GPIO1, &mask, 1);
mask = 0x00;
for (int c = 0; c < columns && c < 8; c++) {
mask <<= 1;
mask |= 1;
}
writeRegister(registers::KP_GPIO2, &mask, 1);
if (columns > 8) {
if (columns == 9)
mask = 0x01;
else
mask = 0x03;
writeRegister(registers::KP_GPIO3, &mask, 1);
}
}
return true;
}
void Tca8418::init(uint8_t numrows, uint8_t numcols) {
/*
* | ADDRESS | REGISTER NAME | REGISTER DESCRIPTION | BIT7 | BIT6 | BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0 |
@ -34,13 +66,7 @@ void Tca8418::init(uint8_t numrows, uint8_t numcols) {
num_rows = numrows;
num_cols = numcols;
// everything enabled in key scan mode
uint8_t enabled_rows = 0x3F;
uint16_t enabled_cols = 0x3FF;
writeRegister8(registers::KP_GPIO1, enabled_rows);
writeRegister8(registers::KP_GPIO2, (uint8_t)(0xFF & enabled_cols));
writeRegister8(registers::KP_GPIO3, (uint8_t)(0x03 & (enabled_cols >> 8)));
initMatrix(num_rows, num_cols);
/*
* BIT: NAME
@ -97,7 +123,7 @@ void Tca8418::init(uint8_t numrows, uint8_t numcols) {
bool Tca8418::update() {
last_update_micros = this_update_micros;
uint8_t key_code, key_down, key_event, key_row, key_col;
uint8_t key_down, key_event, key_row, key_col;
key_event = get_key_event();
// TODO: read gpio R7/R6 status? 0x14 bits 7&6
@ -108,10 +134,12 @@ bool Tca8418::update() {
delta_micros = this_update_micros - last_update_micros;
if (key_event > 0) {
key_code = key_event & 0x7F;
key_down = (key_event & 0x80) >> 7;
key_row = key_code / num_cols;
key_col = key_code % num_cols;
key_down = (key_event & 0x80);
uint16_t buffer = key_event;
buffer &= 0x7F;
buffer--;
key_row = buffer / 10;
key_col = buffer % 10;
// always clear the released list
clear_released_list();

View File

@ -4,9 +4,12 @@
#include <Tactility/hal/i2c/I2cDevice.h>
#define TCA8418_ADDRESS 0x34U
#define KEY_EVENT_LIST_SIZE 10
constexpr auto TCA8418_ADDRESS = 0x34U;
constexpr auto KEY_EVENT_LIST_SIZE = 10;
/**
* See https://www.ti.com/lit/ds/symlink/tca8418.pdf
*/
class Tca8418 final : public tt::hal::i2c::I2cDevice {
uint8_t tca8418_address;
@ -23,6 +26,8 @@ class Tca8418 final : public tt::hal::i2c::I2cDevice {
void write(uint8_t register_address, uint8_t data);
bool read(uint8_t register_address, uint8_t* data);
bool initMatrix(uint8_t rows, uint8_t columns);
public:
struct PressedKey {

View File

@ -51,6 +51,8 @@ menu "Tactility App"
bool "LilyGo T-Lora Pager"
config TT_BOARD_M5STACK_CARDPUTER
bool "M5Stack Cardputer"
config TT_BOARD_M5STACK_CARDPUTER_ADV
bool "M5Stack Cardputer Adv"
config TT_BOARD_M5STACK_CORE2
bool "M5Stack Core2"
config TT_BOARD_M5STACK_CORES3

View File

@ -1,4 +1,5 @@
# Software defaults
# Increase stack size for WiFi (fixes crash after scan)
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072
CONFIG_ESP_MAIN_TASK_STACK_SIZE=6144
CONFIG_LV_FONT_MONTSERRAT_14=y
@ -23,6 +24,12 @@ CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
CONFIG_FATFS_LFN_HEAP=y
CONFIG_FATFS_VOLUME_COUNT=3
CONFIG_FATFS_SECTOR_512=y
CONFIG_WL_SECTOR_SIZE_512=y
CONFIG_WL_SECTOR_SIZE=512
CONFIG_WL_SECTOR_MODE_SAFE=y
CONFIG_WL_SECTOR_MODE=1
CONFIG_MBEDTLS_SSL_PROTO_TLS1_3=y
# Hardware: Main
CONFIG_PARTITION_TABLE_CUSTOM=y

View File

@ -1,5 +1,7 @@
# Software defaults
# Increase stack size for WiFi (fixes crash after scan)
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072
CONFIG_ESP_MAIN_TASK_STACK_SIZE=6144
CONFIG_LV_FONT_MONTSERRAT_14=y
CONFIG_LV_FONT_MONTSERRAT_18=y
CONFIG_LV_USE_USER_DATA=y
@ -22,6 +24,12 @@ CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
CONFIG_FATFS_LFN_HEAP=y
CONFIG_FATFS_VOLUME_COUNT=3
CONFIG_FATFS_SECTOR_512=y
CONFIG_WL_SECTOR_SIZE_512=y
CONFIG_WL_SECTOR_SIZE=512
CONFIG_WL_SECTOR_MODE_SAFE=y
CONFIG_WL_SECTOR_MODE=1
CONFIG_MBEDTLS_SSL_PROTO_TLS1_3=y
# Hardware: Main
CONFIG_PARTITION_TABLE_CUSTOM=y

View File

@ -0,0 +1,58 @@
# Software defaults
# Increase stack size for WiFi (fixes crash after scan)
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072
CONFIG_ESP_MAIN_TASK_STACK_SIZE=6144
CONFIG_LV_FONT_MONTSERRAT_14=y
CONFIG_LV_FONT_MONTSERRAT_18=y
CONFIG_LV_USE_USER_DATA=y
CONFIG_LV_USE_FS_STDIO=y
CONFIG_LV_FS_STDIO_LETTER=65
CONFIG_LV_FS_STDIO_PATH=""
CONFIG_LV_FS_STDIO_CACHE_SIZE=4096
CONFIG_LV_USE_LODEPNG=y
CONFIG_LV_USE_BUILTIN_MALLOC=n
CONFIG_LV_USE_CLIB_MALLOC=y
CONFIG_LV_USE_MSGBOX=n
CONFIG_LV_USE_SPINNER=n
CONFIG_LV_USE_WIN=n
CONFIG_LV_USE_SNAPSHOT=y
CONFIG_FREERTOS_HZ=1000
CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2
CONFIG_FREERTOS_SMP=n
CONFIG_FREERTOS_UNICORE=n
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=5120
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
CONFIG_FATFS_LFN_HEAP=y
CONFIG_FATFS_VOLUME_COUNT=3
CONFIG_FATFS_SECTOR_512=y
CONFIG_WL_SECTOR_SIZE_512=y
CONFIG_WL_SECTOR_SIZE=512
CONFIG_WL_SECTOR_MODE_SAFE=y
CONFIG_WL_SECTOR_MODE=1
CONFIG_MBEDTLS_SSL_PROTO_TLS1_3=y
# Hardware: Main
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8mb.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions-8mb.csv"
CONFIG_TT_BOARD_M5STACK_CARDPUTER_ADV=y
CONFIG_TT_BOARD_NAME="M5Stack Cardputer Adv"
CONFIG_TT_BOARD_ID="m5stack-cardputer-adv"
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
CONFIG_IDF_TARGET="esp32s3"
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
CONFIG_FLASHMODE_QIO=y
# SPI Flash (can set back to 80MHz after ESP-IDF bug is resolved)
CONFIG_ESPTOOLPY_FLASHFREQ_120M=y
# LVGL
CONFIG_LV_DPI_DEF=139
CONFIG_LV_DISP_DEF_REFR_PERIOD=10
CONFIG_LV_THEME_DEFAULT_DARK=y
# USB
CONFIG_TINYUSB_MSC_ENABLED=y
CONFIG_TINYUSB_MSC_MOUNT_PATH="/sdcard"
# Memory protection
CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n
CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=n