From 62c613477aeea5024e0ebf9733338b0e58e21bc5 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sun, 14 Sep 2025 02:25:10 +0200 Subject: [PATCH] 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 --- .github/workflows/build-firmware.yml | 9 + App/Kconfig | 2 + App/Source/Boards.h | 3 + Boards/M5stackCardputer/CMakeLists.txt | 7 + Boards/M5stackCardputer/Source/InitBoot.cpp | 10 + Boards/M5stackCardputer/Source/InitBoot.h | 3 + .../Source/M5stackCardputer.cpp | 123 ++++++ .../Source/M5stackCardputer.h | 5 + .../Source/devices/CardputerEncoder.cpp | 54 +++ .../Source/devices/CardputerEncoder.h | 29 ++ .../Source/devices/CardputerKeyboard.cpp | 90 +++++ .../Source/devices/CardputerKeyboard.h | 26 ++ .../Source/devices/Display.cpp | 28 ++ .../M5stackCardputer/Source/devices/Display.h | 15 + .../Source/devices/SdCard.cpp | 25 ++ .../M5stackCardputer/Source/devices/SdCard.h | 7 + .../Source/keyboard/README.md | 31 ++ .../Source/keyboard/keyboard.cpp | 266 +++++++++++++ .../Source/keyboard/keyboard.h | 192 +++++++++ .../M5stackCardputer/Source/keyboard/keymap.h | 366 ++++++++++++++++++ Buildscripts/board.cmake | 2 + Buildscripts/build-and-release-all.sh | 3 + Documentation/ideas.md | 4 + Drivers/ST7789/Source/St7789Display.cpp | 7 + Drivers/ST7789/Source/St7789Display.h | 8 +- Tactility/Source/app/ElfApp.cpp | 15 +- Tactility/Source/lvgl/LvglSync.cpp | 4 +- sdkconfig.board.m5stack-cardputer | 56 +++ 28 files changed, 1385 insertions(+), 5 deletions(-) create mode 100644 Boards/M5stackCardputer/CMakeLists.txt create mode 100644 Boards/M5stackCardputer/Source/InitBoot.cpp create mode 100644 Boards/M5stackCardputer/Source/InitBoot.h create mode 100644 Boards/M5stackCardputer/Source/M5stackCardputer.cpp create mode 100644 Boards/M5stackCardputer/Source/M5stackCardputer.h create mode 100644 Boards/M5stackCardputer/Source/devices/CardputerEncoder.cpp create mode 100644 Boards/M5stackCardputer/Source/devices/CardputerEncoder.h create mode 100644 Boards/M5stackCardputer/Source/devices/CardputerKeyboard.cpp create mode 100644 Boards/M5stackCardputer/Source/devices/CardputerKeyboard.h create mode 100644 Boards/M5stackCardputer/Source/devices/Display.cpp create mode 100644 Boards/M5stackCardputer/Source/devices/Display.h create mode 100644 Boards/M5stackCardputer/Source/devices/SdCard.cpp create mode 100644 Boards/M5stackCardputer/Source/devices/SdCard.h create mode 100644 Boards/M5stackCardputer/Source/keyboard/README.md create mode 100644 Boards/M5stackCardputer/Source/keyboard/keyboard.cpp create mode 100644 Boards/M5stackCardputer/Source/keyboard/keyboard.h create mode 100644 Boards/M5stackCardputer/Source/keyboard/keymap.h create mode 100644 sdkconfig.board.m5stack-cardputer diff --git a/.github/workflows/build-firmware.yml b/.github/workflows/build-firmware.yml index d0d788ba..c7625f7e 100644 --- a/.github/workflows/build-firmware.yml +++ b/.github/workflows/build-firmware.yml @@ -153,6 +153,15 @@ jobs: with: board_id: lilygo-tlora-pager arch: esp32s3 + m5stack-cardputer: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: "Build" + uses: ./.github/actions/build-firmware + with: + board_id: m5stack-cardputer + arch: esp32s3 m5stack-core2: runs-on: ubuntu-latest steps: diff --git a/App/Kconfig b/App/Kconfig index 11fcabb2..18b6ad4d 100644 --- a/App/Kconfig +++ b/App/Kconfig @@ -43,6 +43,8 @@ menu "Tactility App" bool "LilyGo T-Deck" config TT_BOARD_LILYGO_TLORA_PAGER bool "LilyGo T-Lora Pager" + config TT_BOARD_M5STACK_CARDPUTER + bool "M5Stack Cardputer" config TT_BOARD_M5STACK_CORE2 bool "M5Stack Core2" config TT_BOARD_M5STACK_CORES3 diff --git a/App/Source/Boards.h b/App/Source/Boards.h index 230961a4..fbdd4430 100644 --- a/App/Source/Boards.h +++ b/App/Source/Boards.h @@ -41,6 +41,9 @@ #elif (defined(CONFIG_TT_BOARD_ELECROW_CROWPANEL_BASIC_50)) #define TT_BOARD_HARDWARE &crowpanel_basic_50 #include "CrowPanelBasic50.h" +#elif defined(CONFIG_TT_BOARD_M5STACK_CARDPUTER) +#include "M5stackCardputer.h" +#define TT_BOARD_HARDWARE &m5stack_cardputer #elif defined(CONFIG_TT_BOARD_M5STACK_CORE2) #include "M5stackCore2.h" #define TT_BOARD_HARDWARE &m5stack_core2 diff --git a/Boards/M5stackCardputer/CMakeLists.txt b/Boards/M5stackCardputer/CMakeLists.txt new file mode 100644 index 00000000..4cd90a0d --- /dev/null +++ b/Boards/M5stackCardputer/CMakeLists.txt @@ -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 vfs fatfs +) diff --git a/Boards/M5stackCardputer/Source/InitBoot.cpp b/Boards/M5stackCardputer/Source/InitBoot.cpp new file mode 100644 index 00000000..601446d2 --- /dev/null +++ b/Boards/M5stackCardputer/Source/InitBoot.cpp @@ -0,0 +1,10 @@ +#include +#include + +constexpr auto* TAG = "Cardputer"; + +bool initBoot() { + TT_LOG_I(TAG, "initBoot"); + + return driver::pwmbacklight::init(GPIO_NUM_38, 256); +} \ No newline at end of file diff --git a/Boards/M5stackCardputer/Source/InitBoot.h b/Boards/M5stackCardputer/Source/InitBoot.h new file mode 100644 index 00000000..f3e5bf89 --- /dev/null +++ b/Boards/M5stackCardputer/Source/InitBoot.h @@ -0,0 +1,3 @@ +#pragma once + +bool initBoot(); diff --git a/Boards/M5stackCardputer/Source/M5stackCardputer.cpp b/Boards/M5stackCardputer/Source/M5stackCardputer.cpp new file mode 100644 index 00000000..145a4202 --- /dev/null +++ b/Boards/M5stackCardputer/Source/M5stackCardputer.cpp @@ -0,0 +1,123 @@ +#include "M5stackCardputer.h" +#include "InitBoot.h" +#include "devices/Display.h" +#include "devices/SdCard.h" +#include "devices/CardputerEncoder.h" +#include "devices/CardputerKeyboard.h" + +#include +#include + +#define SPI_TRANSFER_SIZE_LIMIT (LCD_DRAW_BUFFER_SIZE * LV_COLOR_DEPTH / 8) + +using namespace tt::hal; + +static DeviceVector createDevices() { + return { + createSdCard(), + createDisplay(), + std::make_shared(), + std::make_shared() + }; +} + +extern const Configuration m5stack_cardputer = { + .initBoot = initBoot, + .createDevices = createDevices, + .i2c = { + // Only available on Cardputer Adv (enabling it breaks the keyboard on a Cardputer v1.1) + i2c::Configuration { + .name = "Internal", + .port = I2C_NUM_0, + .initMode = i2c::InitMode::Disabled, + .isMutable = true, + .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 + } + } + }, + .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 = 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 = "Grove", + .port = UART_NUM_1, + .rxPin = GPIO_NUM_32, + .txPin = GPIO_NUM_33, + .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, + } + } + }, + } +}; diff --git a/Boards/M5stackCardputer/Source/M5stackCardputer.h b/Boards/M5stackCardputer/Source/M5stackCardputer.h new file mode 100644 index 00000000..94c1a675 --- /dev/null +++ b/Boards/M5stackCardputer/Source/M5stackCardputer.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const tt::hal::Configuration m5stack_cardputer; diff --git a/Boards/M5stackCardputer/Source/devices/CardputerEncoder.cpp b/Boards/M5stackCardputer/Source/devices/CardputerEncoder.cpp new file mode 100644 index 00000000..07b8a701 --- /dev/null +++ b/Boards/M5stackCardputer/Source/devices/CardputerEncoder.cpp @@ -0,0 +1,54 @@ +#include "CardputerEncoder.h" + +void CardputerEncoder::readCallback(lv_indev_t* indev, lv_indev_data_t* data) { + CardputerEncoder* self = static_cast(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; +} diff --git a/Boards/M5stackCardputer/Source/devices/CardputerEncoder.h b/Boards/M5stackCardputer/Source/devices/CardputerEncoder.h new file mode 100644 index 00000000..f97ca7cf --- /dev/null +++ b/Boards/M5stackCardputer/Source/devices/CardputerEncoder.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#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; } +}; diff --git a/Boards/M5stackCardputer/Source/devices/CardputerKeyboard.cpp b/Boards/M5stackCardputer/Source/devices/CardputerKeyboard.cpp new file mode 100644 index 00000000..6490d259 --- /dev/null +++ b/Boards/M5stackCardputer/Source/devices/CardputerKeyboard.cpp @@ -0,0 +1,90 @@ +#include "CardputerKeyboard.h" + +#include + +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(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; + } + } +} diff --git a/Boards/M5stackCardputer/Source/devices/CardputerKeyboard.h b/Boards/M5stackCardputer/Source/devices/CardputerKeyboard.h new file mode 100644 index 00000000..f57dff06 --- /dev/null +++ b/Boards/M5stackCardputer/Source/devices/CardputerKeyboard.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#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; } +}; diff --git a/Boards/M5stackCardputer/Source/devices/Display.cpp b/Boards/M5stackCardputer/Source/devices/Display.cpp new file mode 100644 index 00000000..b9bfa346 --- /dev/null +++ b/Boards/M5stackCardputer/Source/devices/Display.cpp @@ -0,0 +1,28 @@ +#include "Display.h" + +#include +#include + +std::shared_ptr createDisplay() { + auto configuration = std::make_unique( + 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(std::move(configuration)); + return std::reinterpret_pointer_cast(display); +} diff --git a/Boards/M5stackCardputer/Source/devices/Display.h b/Boards/M5stackCardputer/Source/devices/Display.h new file mode 100644 index 00000000..c466c7dc --- /dev/null +++ b/Boards/M5stackCardputer/Source/devices/Display.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Tactility/hal/display/DisplayDevice.h" +#include + +#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 createDisplay(); diff --git a/Boards/M5stackCardputer/Source/devices/SdCard.cpp b/Boards/M5stackCardputer/Source/devices/SdCard.cpp new file mode 100644 index 00000000..864daa3b --- /dev/null +++ b/Boards/M5stackCardputer/Source/devices/SdCard.cpp @@ -0,0 +1,25 @@ +#include "SdCard.h" + +#include + +constexpr auto SDCARD_PIN_CS = GPIO_NUM_12; +constexpr auto LCD_PIN_CS = GPIO_NUM_37; + +using tt::hal::sdcard::SpiSdCardDevice; + +std::shared_ptr createSdCard() { + auto configuration = std::make_unique( + 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( + std::move(configuration) + ); +} diff --git a/Boards/M5stackCardputer/Source/devices/SdCard.h b/Boards/M5stackCardputer/Source/devices/SdCard.h new file mode 100644 index 00000000..5cb65a73 --- /dev/null +++ b/Boards/M5stackCardputer/Source/devices/SdCard.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Tactility/hal/sdcard/SdCardDevice.h" + +using tt::hal::sdcard::SdCardDevice; + +std::shared_ptr createSdCard(); diff --git a/Boards/M5stackCardputer/Source/keyboard/README.md b/Boards/M5stackCardputer/Source/keyboard/README.md new file mode 100644 index 00000000..8acfc1be --- /dev/null +++ b/Boards/M5stackCardputer/Source/keyboard/README.md @@ -0,0 +1,31 @@ +# keyboard folder + +The keyboard code in this folder is from https://github.com/m5stack/M5Cardputer-UserDemo/tree/main/main/hal/keyboard + +# License + +The original license is an MIT license: + +``` +MIT License + +Copyright (c) 2023 M5Stack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` \ No newline at end of file diff --git a/Boards/M5stackCardputer/Source/keyboard/keyboard.cpp b/Boards/M5stackCardputer/Source/keyboard/keyboard.cpp new file mode 100644 index 00000000..1d28ef96 --- /dev/null +++ b/Boards/M5stackCardputer/Source/keyboard/keyboard.cpp @@ -0,0 +1,266 @@ +/** + * @file keyboard.cpp + * @author Forairaaaaa + * @brief + * @version 0.1 + * @date 2023-09-22 + * + * @copyright Copyright (c) 2023 + * + */ +#include "keyboard.h" +#include + +#define digitalWrite(pin, level) gpio_set_level((gpio_num_t)pin, level) +#define digitalRead(pin) gpio_get_level((gpio_num_t)pin) + + +using namespace KEYBOARD; + +void Keyboard::_set_output(const std::vector& pinList, uint8_t output) +{ + output = output & 0B00000111; + + digitalWrite(pinList[0], (output & 0B00000001)); + digitalWrite(pinList[1], (output & 0B00000010)); + digitalWrite(pinList[2], (output & 0B00000100)); +} + + +uint8_t Keyboard::_get_input(const std::vector& pinList) +{ + uint8_t buffer = 0x00; + uint8_t pin_value = 0x00; + + for (int i = 0; i < 7; i++) + { + pin_value = (digitalRead(pinList[i]) == 1) ? 0x00 : 0x01; + pin_value = pin_value << i; + buffer = buffer | pin_value; + } + + return buffer; +} + + +void Keyboard::init() +{ + for (auto i : output_list) + { + gpio_reset_pin((gpio_num_t)i); + gpio_set_direction((gpio_num_t)i, GPIO_MODE_OUTPUT); + gpio_set_pull_mode((gpio_num_t)i, GPIO_PULLUP_PULLDOWN); + digitalWrite(i, 0); + } + + for (auto i : input_list) + { + gpio_reset_pin((gpio_num_t)i); + gpio_set_direction((gpio_num_t)i, GPIO_MODE_INPUT); + gpio_set_pull_mode((gpio_num_t)i, GPIO_PULLUP_ONLY); + } + + _set_output(output_list, 0); +} + + +Point2D_t Keyboard::getKey() +{ + Point2D_t coor; + coor.x = -1; + coor.y = -1; + + uint8_t input_value = 0; + + for (int i = 0; i < 8; i++) { + _set_output(output_list, i); + // printf("% 3d,\t", get_input(inputList)); + + input_value = _get_input(input_list); + + /* If key pressed */ + if (input_value) { + + /* Get X */ + for (int j = 0; j < 7; j++) { + if (input_value == X_map_chart[j].value) { + coor.x = (i > 3) ? X_map_chart[j].x_1 : X_map_chart[j].x_2; + break; + } + } + + /* Get Y */ + coor.y = (i > 3) ? (i - 4) : i; + + /* Keep the same as picture */ + coor.y = -coor.y; + coor.y = coor.y + 3; + + + break; + } + } + + // printf("%d,%d\n", x, y); + return coor; +} + + +uint8_t Keyboard::getKeyNum(Point2D_t keyCoor) +{ + uint8_t ret = 0; + + if ((keyCoor.x < 0) || (keyCoor.y < 0)) { + return 0; + } + + ret = (keyCoor.y * 14) + (keyCoor.x + 1); + + return ret; +} + + +void Keyboard::updateKeyList() +{ + _key_list_buffer.clear(); + + Point2D_t coor; + + uint8_t input_value = 0; + + for (int i = 0; i < 8; i++) { + _set_output(output_list, i); + input_value = _get_input(input_list); + + /* If key pressed */ + if (input_value) { + /* Get X */ + for (int j = 0; j < 7; j++) { + if (input_value & (0x01 << j)) { + coor.x = (i > 3) ? X_map_chart[j].x_1 : X_map_chart[j].x_2; + + /* Get Y */ + coor.y = (i > 3) ? (i - 4) : i; + // printf("%d,%d\t", coor.x, coor.y); + + /* Keep the same as picture */ + coor.y = -coor.y; + coor.y = coor.y + 3; + + _key_list_buffer.push_back(coor); + } + } + } + } +} + + +bool Keyboard::isKeyPressing(int keyNum) +{ + if (_key_list_buffer.size()) + { + for (const auto& i : _key_list_buffer) + { + if (getKeyNum(i) == keyNum) + return true; + } + } + return false; +} + + +#include + +void Keyboard::updateKeysState() +{ + _keys_state_buffer.reset(); + _key_values_without_special_keys.clear(); + + // Get special keys + for (auto& i : _key_list_buffer) + { + if (strcmp(getKeyValue(i).value_first, "tab") == 0) + { + _keys_state_buffer.tab = true; + continue; + } + + if (strcmp(getKeyValue(i).value_first, "fn") == 0) + { + _keys_state_buffer.fn = true; + continue; + } + + if (strcmp(getKeyValue(i).value_first, "shift") == 0) + { + _keys_state_buffer.shift = true; + continue; + } + + if (strcmp(getKeyValue(i).value_first, "ctrl") == 0) + { + _keys_state_buffer.ctrl = true; + continue; + } + if (strcmp(getKeyValue(i).value_first, "opt") == 0) + { + _keys_state_buffer.opt = true; + continue; + } + + if (strcmp(getKeyValue(i).value_first, "alt") == 0) + { + _keys_state_buffer.alt = true; + continue; + } + + if (strcmp(getKeyValue(i).value_first, "del") == 0) + { + _keys_state_buffer.del = true; + _keys_state_buffer.hidKey.push_back(KEY_BACKSPACE); + continue; + } + + if (strcmp(getKeyValue(i).value_first, "enter") == 0) + { + _keys_state_buffer.enter = true; + _keys_state_buffer.hidKey.push_back(KEY_ENTER); + continue; + } + + if (strcmp(getKeyValue(i).value_first, "space") == 0) + { + _keys_state_buffer.space = true; + _keys_state_buffer.hidKey.push_back(KEY_SPACE); + continue; + } + + _key_values_without_special_keys.push_back(i); + } + + // Deal what left + for (auto& i : _key_values_without_special_keys) + { + if (_keys_state_buffer.ctrl || _keys_state_buffer.shift || _is_caps_locked) + { + _keys_state_buffer.values.push_back(*getKeyValue(i).value_second); + _keys_state_buffer.hidKey.push_back(getKeyValue(i).value_num_second); + } + else + { + _keys_state_buffer.values.push_back(*getKeyValue(i).value_first); + _keys_state_buffer.hidKey.push_back(getKeyValue(i).value_num_first); + } + } +} + + +bool Keyboard::isChanged() +{ + if (_last_key_size != _key_list_buffer.size()) + { + _last_key_size = _key_list_buffer.size(); + return true; + } + return false; +} diff --git a/Boards/M5stackCardputer/Source/keyboard/keyboard.h b/Boards/M5stackCardputer/Source/keyboard/keyboard.h new file mode 100644 index 00000000..81a7a530 --- /dev/null +++ b/Boards/M5stackCardputer/Source/keyboard/keyboard.h @@ -0,0 +1,192 @@ +/** + * @file keyboard.h + * @author Forairaaaaa + * @brief + * @version 0.1 + * @date 2023-09-22 + * + * @copyright Copyright (c) 2023 + * + */ +#pragma once +#include +#include +#include "keymap.h" + + +namespace KEYBOARD +{ + struct Chart_t + { + uint8_t value; + uint8_t x_1; + uint8_t x_2; + }; + + struct Point2D_t + { + int x; + int y; + }; + + const std::vector output_list = {8, 9, 11}; + const std::vector input_list = {13, 15, 3, 4, 5, 6, 7}; + // const std::vector input_list = {1, 2, 3, 4, 5, 6, 7}; + + const Chart_t X_map_chart[7] = + { + {1, 0, 1}, + {2, 2, 3}, + {4, 4, 5}, + {8, 6, 7}, + {16, 8, 9}, + {32, 10, 11}, + {64, 12, 13} + }; + + struct KeyValue_t + { + const char* value_first; + const int value_num_first; + const char* value_second; + const int value_num_second; + }; + + const KeyValue_t _key_value_map[4][14] = { + {{"`", KEY_GRAVE, "~", KEY_GRAVE}, + {"1", KEY_1, "!", KEY_1}, + {"2", KEY_2, "@", KEY_2}, + {"3", KEY_3, "#", KEY_3}, + {"4", KEY_4, "$", KEY_4}, + {"5", KEY_5, "%", KEY_5}, + {"6", KEY_6, "^", KEY_6}, + {"7", KEY_7, "&", KEY_7}, + {"8", KEY_8, "*", KEY_KPASTERISK}, + {"9", KEY_9, "(", KEY_KPLEFTPAREN}, + {"0", KEY_0, ")", KEY_KPRIGHTPAREN}, + {"-", KEY_MINUS, "_", KEY_KPMINUS}, + {"=", KEY_EQUAL, "+", KEY_KPPLUS}, + {"del", KEY_BACKSPACE, "del", KEY_BACKSPACE}}, + {{"tab", KEY_TAB, "tab", KEY_TAB}, + {"q", KEY_Q, "Q", KEY_Q}, + {"w", KEY_W, "W", KEY_W}, + {"e", KEY_E, "E", KEY_E}, + {"r", KEY_R, "R", KEY_R}, + {"t", KEY_T, "T", KEY_T}, + {"y", KEY_Y, "Y", KEY_Y}, + {"u", KEY_U, "U", KEY_U}, + {"i", KEY_I, "I", KEY_I}, + {"o", KEY_O, "O", KEY_O}, + {"p", KEY_P, "P", KEY_P}, + {"[", KEY_LEFTBRACE, "{", KEY_LEFTBRACE}, + {"]", KEY_RIGHTBRACE, "}", KEY_RIGHTBRACE}, + {"\\", KEY_BACKSLASH, "|", KEY_BACKSLASH}}, + {{"fn", 0, "fn", 0}, + {"shift", 0, "shift", 0}, + {"a", KEY_A, "A", KEY_A}, + {"s", KEY_S, "S", KEY_S}, + {"d", KEY_D, "D", KEY_D}, + {"f", KEY_F, "F", KEY_F}, + {"g", KEY_G, "G", KEY_G}, + {"h", KEY_H, "H", KEY_H}, + {"j", KEY_J, "J", KEY_J}, + {"k", KEY_K, "K", KEY_K}, + {"l", KEY_L, "L", KEY_L}, + {";", KEY_SEMICOLON, ":", KEY_SEMICOLON}, + {"'", KEY_APOSTROPHE, "\"", KEY_APOSTROPHE}, + {"enter", KEY_ENTER, "enter", KEY_ENTER}}, + {{"ctrl", KEY_LEFTCTRL, "ctrl", KEY_LEFTCTRL}, + {"opt", 0, "opt", 0}, + {"alt", KEY_LEFTALT, "alt", KEY_LEFTALT}, + {"z", KEY_Z, "Z", KEY_Z}, + {"x", KEY_X, "X", KEY_X}, + {"c", KEY_C, "C", KEY_C}, + {"v", KEY_V, "V", KEY_V}, + {"b", KEY_B, "B", KEY_B}, + {"n", KEY_N, "N", KEY_N}, + {"m", KEY_M, "M", KEY_M}, + {",", KEY_COMMA, "<", KEY_COMMA}, + {".", KEY_DOT, ">", KEY_DOT}, + {"/", KEY_KPSLASH, "?", KEY_KPSLASH}, + {"space", KEY_SPACE, "space", KEY_SPACE}}}; + + class Keyboard + { + public: + + struct KeysState + { + bool tab = false; + bool fn = false; + bool shift = false; + bool ctrl = false; + bool opt = false; + bool alt = false; + bool del = false; + bool enter = false; + bool space = false; + + std::vector values; + std::vector hidKey; + + void reset() + { + tab = false; + fn = false; + shift = false; + ctrl = false; + opt = false; + alt = false; + del = false; + enter = false; + space = false; + + values.clear(); + hidKey.clear(); + } + }; + + + private: + std::vector _key_list_buffer; + + std::vector _key_values_without_special_keys; + KeysState _keys_state_buffer; + + bool _is_caps_locked; + uint8_t _last_key_size; + + + void _set_output(const std::vector& pinList, uint8_t output); + uint8_t _get_input(const std::vector& pinList); + + + public: + Keyboard() : + _is_caps_locked(false), + _last_key_size(0) + {} + + void init(); + + Point2D_t getKey(); + + uint8_t getKeyNum(Point2D_t keyCoor); + + void updateKeyList(); + inline std::vector& keyList() { return _key_list_buffer; } + + inline KeyValue_t getKeyValue(const Point2D_t& keyCoor) { return _key_value_map[keyCoor.y][keyCoor.x]; } + + bool isKeyPressing(int keyNum); + + void updateKeysState(); + inline KeysState& keysState() { return _keys_state_buffer; } + + inline bool capslocked(void) { return _is_caps_locked; } + inline void setCapsLocked(bool isLocked) { _is_caps_locked = isLocked; } + + bool isChanged(); + inline uint8_t isPressed() { return _key_list_buffer.size(); } + }; +} diff --git a/Boards/M5stackCardputer/Source/keyboard/keymap.h b/Boards/M5stackCardputer/Source/keyboard/keymap.h new file mode 100644 index 00000000..14dafc63 --- /dev/null +++ b/Boards/M5stackCardputer/Source/keyboard/keymap.h @@ -0,0 +1,366 @@ +/** + * This file is part of esp32s3-keyboard. + * + * Copyright (C) 2020-2021 Yuquan He + * (Institute of Computing Technology, Chinese Academy of Sciences) + * + * esp32s3-keyboard is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * esp32s3-keyboard is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with esp32s3-keyboard. If not, see . + */ + +/** + * USB HID format: 8 bytes + * Byte 0: Keyboard modifier bits (SHIFT, ALT, CTRL etc) + * Byte 1: reserved + * Byte 2-7: Up to six keyboard usage indexes representing the keys that are + * currently "pressed". Order is not important, a key is either + * pressed (present in the buffer) or not pressed. + */ + +#ifndef MY_KEYMAP_H +#define MY_KEYMAP_H + +#include + +/** + * Modifier masks - used for the first byte in the HID report. + * NOTE: The second byte in the report is reserved, 0x00 + */ +#define KEY_MOD_LCTRL 0x01 +#define KEY_MOD_LSHIFT 0x02 +#define KEY_MOD_LALT 0x04 +#define KEY_MOD_LMETA 0x08 +#define KEY_MOD_RCTRL 0x10 +#define KEY_MOD_RSHIFT 0x20 +#define KEY_MOD_RALT 0x40 +#define KEY_MOD_RMETA 0x80 + +/** + * Scan codes - last N slots in the HID report (usually 6). + * 0x00 if no key pressed. + * + * If more than N keys are pressed, the HID reports + * KEY_ERR_OVF in all slots to indicate this condition. + */ + +#define KEY_NONE 0x00 // No key pressed +#define KEY_ERR_OVF \ + 0x01 // Keyboard Error Roll Over - used for all slots if too many keys are + // pressed ("Phantom key") +// 0x02 // Keyboard POST Fail +// 0x03 // Keyboard Error Undefined +#define KEY_A 0x04 // Keyboard a and A +#define KEY_B 0x05 // Keyboard b and B +#define KEY_C 0x06 // Keyboard c and C +#define KEY_D 0x07 // Keyboard d and D +#define KEY_E 0x08 // Keyboard e and E +#define KEY_F 0x09 // Keyboard f and F +#define KEY_G 0x0a // Keyboard g and G +#define KEY_H 0x0b // Keyboard h and H +#define KEY_I 0x0c // Keyboard i and I +#define KEY_J 0x0d // Keyboard j and J +#define KEY_K 0x0e // Keyboard k and K +#define KEY_L 0x0f // Keyboard l and L +#define KEY_M 0x10 // Keyboard m and M +#define KEY_N 0x11 // Keyboard n and N +#define KEY_O 0x12 // Keyboard o and O +#define KEY_P 0x13 // Keyboard p and P +#define KEY_Q 0x14 // Keyboard q and Q +#define KEY_R 0x15 // Keyboard r and R +#define KEY_S 0x16 // Keyboard s and S +#define KEY_T 0x17 // Keyboard t and T +#define KEY_U 0x18 // Keyboard u and U +#define KEY_V 0x19 // Keyboard v and V +#define KEY_W 0x1a // Keyboard w and W +#define KEY_X 0x1b // Keyboard x and X +#define KEY_Y 0x1c // Keyboard y and Y +#define KEY_Z 0x1d // Keyboard z and Z + +#define KEY_1 0x1e // Keyboard 1 and ! +#define KEY_2 0x1f // Keyboard 2 and @ +#define KEY_3 0x20 // Keyboard 3 and # +#define KEY_4 0x21 // Keyboard 4 and $ +#define KEY_5 0x22 // Keyboard 5 and % +#define KEY_6 0x23 // Keyboard 6 and ^ +#define KEY_7 0x24 // Keyboard 7 and & +#define KEY_8 0x25 // Keyboard 8 and * +#define KEY_9 0x26 // Keyboard 9 and ( +#define KEY_0 0x27 // Keyboard 0 and ) + +#define KEY_ENTER 0x28 // Keyboard Return (ENTER) +#define KEY_ESC 0x29 // Keyboard ESCAPE +#define KEY_BACKSPACE 0x2a // Keyboard DELETE (Backspace) +#define KEY_TAB 0x2b // Keyboard Tab +#define KEY_SPACE 0x2c // Keyboard Spacebar +#define KEY_MINUS 0x2d // Keyboard - and _ +#define KEY_EQUAL 0x2e // Keyboard = and + +#define KEY_LEFTBRACE 0x2f // Keyboard [ and { +#define KEY_RIGHTBRACE 0x30 // Keyboard ] and } +#define KEY_BACKSLASH 0x31 // Keyboard \ and | +#define KEY_HASHTILDE 0x32 // Keyboard Non-US # and ~ +#define KEY_SEMICOLON 0x33 // Keyboard ; and : +#define KEY_APOSTROPHE 0x34 // Keyboard ' and " +#define KEY_GRAVE 0x35 // Keyboard ` and ~ +#define KEY_COMMA 0x36 // Keyboard , and < +#define KEY_DOT 0x37 // Keyboard . and > +#define KEY_SLASH 0x38 // Keyboard / and ? +#define KEY_CAPSLOCK 0x39 // Keyboard Caps Lock + +#define KEY_F1 0x3a // Keyboard F1 +#define KEY_F2 0x3b // Keyboard F2 +#define KEY_F3 0x3c // Keyboard F3 +#define KEY_F4 0x3d // Keyboard F4 +#define KEY_F5 0x3e // Keyboard F5 +#define KEY_F6 0x3f // Keyboard F6 +#define KEY_F7 0x40 // Keyboard F7 +#define KEY_F8 0x41 // Keyboard F8 +#define KEY_F9 0x42 // Keyboard F9 +#define KEY_F10 0x43 // Keyboard F10 +#define KEY_F11 0x44 // Keyboard F11 +#define KEY_F12 0x45 // Keyboard F12 + +#define KEY_PRTSC 0x46 // Keyboard Print Screen +#define KEY_SCROLLLOCK 0x47 // Keyboard Scroll Lock +#define KEY_PAUSE 0x48 // Keyboard Pause +#define KEY_INSERT 0x49 // Keyboard Insert +#define KEY_HOME 0x4a // Keyboard Home +#define KEY_PAGEUP 0x4b // Keyboard Page Up +// #define KEY_DELETE 0x4c // Keyboard Delete Forward +#define KEY_DELETE 0xD4 // Keyboard Delete Forward +#define KEY_END 0x4d // Keyboard End +#define KEY_PAGEDOWN 0x4e // Keyboard Page Down +#define KEY_RIGHT 0x4f // Keyboard Right Arrow +#define KEY_LEFT 0x50 // Keyboard Left Arrow +#define KEY_DOWN 0x51 // Keyboard Down Arrow +#define KEY_UP 0x52 // Keyboard Up Arrow + +#define KEY_NUMLOCK 0x53 // Keyboard Num Lock and Clear +#define KEY_KPSLASH 0x54 // Keypad / +#define KEY_KPASTERISK 0x55 // Keypad * +#define KEY_KPMINUS 0x56 // Keypad - +#define KEY_KPPLUS 0x57 // Keypad + +#define KEY_KPENTER 0x58 // Keypad ENTER +#define KEY_KP1 0x59 // Keypad 1 and End +#define KEY_KP2 0x5a // Keypad 2 and Down Arrow +#define KEY_KP3 0x5b // Keypad 3 and PageDn +#define KEY_KP4 0x5c // Keypad 4 and Left Arrow +#define KEY_KP5 0x5d // Keypad 5 +#define KEY_KP6 0x5e // Keypad 6 and Right Arrow +#define KEY_KP7 0x5f // Keypad 7 and Home +#define KEY_KP8 0x60 // Keypad 8 and Up Arrow +#define KEY_KP9 0x61 // Keypad 9 and Page Up +#define KEY_KP0 0x62 // Keypad 0 and Insert +#define KEY_KPDOT 0x63 // Keypad . and Delete + +#define KEY_102ND 0x64 // Keyboard Non-US \ and | +#define KEY_COMPOSE 0x65 // Keyboard Application +#define KEY_POWER 0x66 // Keyboard Power +#define KEY_KPEQUAL 0x67 // Keypad = + +#define KEY_F13 0x68 // Keyboard F13 +#define KEY_F14 0x69 // Keyboard F14 +#define KEY_F15 0x6a // Keyboard F15 +#define KEY_F16 0x6b // Keyboard F16 +#define KEY_F17 0x6c // Keyboard F17 +#define KEY_F18 0x6d // Keyboard F18 +#define KEY_F19 0x6e // Keyboard F19 +#define KEY_F20 0x6f // Keyboard F20 +#define KEY_F21 0x70 // Keyboard F21 +#define KEY_F22 0x71 // Keyboard F22 +#define KEY_F23 0x72 // Keyboard F23 +#define KEY_F24 0x73 // Keyboard F24 + +#define KEY_OPEN 0x74 // Keyboard Execute +#define KEY_HELP 0x75 // Keyboard Help +#define KEY_PROPS 0x76 // Keyboard Menu +#define KEY_FRONT 0x77 // Keyboard Select +#define KEY_STOP 0x78 // Keyboard Stop +#define KEY_AGAIN 0x79 // Keyboard Again +#define KEY_UNDO 0x7a // Keyboard Undo +#define KEY_CUT 0x7b // Keyboard Cut +#define KEY_COPY 0x7c // Keyboard Copy +#define KEY_PASTE 0x7d // Keyboard Paste +#define KEY_FIND 0x7e // Keyboard Find +#define KEY_MUTE 0x7f // Keyboard Mute +#define KEY_VOLUMEUP 0x80 // Keyboard Volume Up +#define KEY_VOLUMEDOWN 0x81 // Keyboard Volume Down +// 0x82 Keyboard Locking Caps Lock +// 0x83 Keyboard Locking Num Lock +// 0x84 Keyboard Locking Scroll Lock +#define KEY_KPCOMMA 0x85 // Keypad Comma +// 0x86 Keypad Equal Sign +#define KEY_RO 0x87 // Keyboard International1 +#define KEY_KATAKANAHIRAGANA 0x88 // Keyboard International2 +#define KEY_YEN 0x89 // Keyboard International3 +#define KEY_HENKAN 0x8a // Keyboard International4 +#define KEY_MUHENKAN 0x8b // Keyboard International5 +#define KEY_KPJPCOMMA 0x8c // Keyboard International6 +// 0x8d Keyboard International7 +// 0x8e Keyboard International8 +// 0x8f Keyboard International9 +#define KEY_HANGEUL 0x90 // Keyboard LANG1 +#define KEY_HANJA 0x91 // Keyboard LANG2 +#define KEY_KATAKANA 0x92 // Keyboard LANG3 +#define KEY_HIRAGANA 0x93 // Keyboard LANG4 +#define KEY_ZENKAKUHANKAKU 0x94 // Keyboard LANG5 +// 0x95 Keyboard LANG6 +// 0x96 Keyboard LANG7 +// 0x97 Keyboard LANG8 +// 0x98 Keyboard LANG9 +// 0x99 Keyboard Alternate Erase +// 0x9a Keyboard SysReq/Attention +// 0x9b Keyboard Cancel +// 0x9c Keyboard Clear +// 0x9d Keyboard Prior +// 0x9e Keyboard Return +// 0x9f Keyboard Separator +// 0xa0 Keyboard Out +// 0xa1 Keyboard Oper +// 0xa2 Keyboard Clear/Again +// 0xa3 Keyboard CrSel/Props +// 0xa4 Keyboard ExSel + +// 0xb0 Keypad 00 +// 0xb1 Keypad 000 +// 0xb2 Thousands Separator +// 0xb3 Decimal Separator +// 0xb4 Currency Unit +// 0xb5 Currency Sub-unit +#define KEY_KPLEFTPAREN 0xb6 // Keypad ( +#define KEY_KPRIGHTPAREN 0xb7 // Keypad ) +// 0xb8 Keypad { +// 0xb9 Keypad } +// 0xba Keypad Tab +#define KEY_KPBACKSPACE 0xbb // Keypad Backspace +// 0xbc Keypad A +// 0xbd Keypad B +// 0xbe Keypad C +// 0xbf Keypad D +// 0xc0 Keypad E +// 0xc1 Keypad F +// 0xc2 Keypad XOR +// 0xc3 Keypad ^ +// 0xc4 Keypad % +// 0xc5 Keypad < +// 0xc6 Keypad > +// 0xc7 Keypad & +// 0xc8 Keypad && +// 0xc9 Keypad | +// 0xca Keypad || +// 0xcb Keypad : +// 0xcc Keypad # +// 0xcd Keypad Space +// 0xce Keypad @ +// 0xcf Keypad ! +// 0xd0 Keypad Memory Store +// 0xd1 Keypad Memory Recall +// 0xd2 Keypad Memory Clear +// 0xd3 Keypad Memory Add +// 0xd4 Keypad Memory Subtract +// 0xd5 Keypad Memory Multiply +// 0xd6 Keypad Memory Divide +// 0xd7 Keypad +/- +// 0xd8 Keypad Clear +// 0xd9 Keypad Clear Entry +// 0xda Keypad Binary +// 0xdb Keypad Octal +// 0xdc Keypad Decimal +// 0xdd Keypad Hexadecimal + +#define KEY_LEFTCTRL 0xe0 // Keyboard Left Control +#define KEY_LEFTSHIFT 0xe1 // Keyboard Left Shift +#define KEY_LEFTALT 0xe2 // Keyboard Left Alt +#define KEY_LEFTMETA 0xe3 // Keyboard Left GUI +#define KEY_RIGHTCTRL 0xe4 // Keyboard Right Control +#define KEY_RIGHTSHIFT 0xe5 // Keyboard Right Shift +#define KEY_RIGHTALT 0xe6 // Keyboard Right Alt +#define KEY_RIGHTMETA 0xe7 // Keyboard Right GUI + +#define KEY_MEDIA_PLAYPAUSE 0xe8 +#define KEY_MEDIA_STOPCD 0xe9 +#define KEY_MEDIA_PREVIOUSSONG 0xea +#define KEY_MEDIA_NEXTSONG 0xeb +#define KEY_MEDIA_EJECTCD 0xec +#define KEY_MEDIA_VOLUMEUP 0xed +#define KEY_MEDIA_VOLUMEDOWN 0xee +#define KEY_MEDIA_MUTE 0xef +#define KEY_MEDIA_WWW 0xf0 +#define KEY_MEDIA_BACK 0xf1 +#define KEY_MEDIA_FORWARD 0xf2 +#define KEY_MEDIA_STOP 0xf3 +#define KEY_MEDIA_FIND 0xf4 +#define KEY_MEDIA_SCROLLUP 0xf5 +#define KEY_MEDIA_SCROLLDOWN 0xf6 +#define KEY_MEDIA_EDIT 0xf7 +#define KEY_MEDIA_SLEEP 0xf8 +#define KEY_MEDIA_COFFEE 0xf9 +#define KEY_MEDIA_REFRESH 0xfa +#define KEY_MEDIA_CALC 0xfb + +enum { + // Generic Control + KEY_CONSUMER_CONTROL = 0x0001, + + // Power Control + KEY_CONSUMER_POWER = 0x0030, + KEY_CONSUMER_RESET = 0x0031, + KEY_CONSUMER_SLEEP = 0x0032, + + // Screen Brightness + KEY_CONSUMER_BRIGHTNESS_INCREMENT = 0x006F, + KEY_CONSUMER_BRIGHTNESS_DECREMENT = 0x0070, + + // These HID usages operate only on mobile systems (battery powered) and + // require Windows 8 (build 8302 or greater). + KEY_CONSUMER_WIRELESS_RADIO_CONTROLS = 0x000C, + KEY_CONSUMER_WIRELESS_RADIO_BUTTONS = 0x00C6, + KEY_CONSUMER_WIRELESS_RADIO_LED = 0x00C7, + KEY_CONSUMER_WIRELESS_RADIO_SLIDER_SWITCH = 0x00C8, + + // Media Control + KEY_CONSUMER_PLAY_PAUSE = 0x00CD, + KEY_CONSUMER_SCAN_NEXT = 0x00B5, + KEY_CONSUMER_SCAN_PREVIOUS = 0x00B6, + KEY_CONSUMER_STOP = 0x00B7, + KEY_CONSUMER_VOLUME = 0x00E0, + KEY_CONSUMER_MUTE = 0x00E2, + KEY_CONSUMER_BASS = 0x00E3, + KEY_CONSUMER_TREBLE = 0x00E4, + KEY_CONSUMER_BASS_BOOST = 0x00E5, + KEY_CONSUMER_VOLUME_INCREMENT = 0x00E9, + KEY_CONSUMER_VOLUME_DECREMENT = 0x00EA, + KEY_CONSUMER_BASS_INCREMENT = 0x0152, + KEY_CONSUMER_BASS_DECREMENT = 0x0153, + KEY_CONSUMER_TREBLE_INCREMENT = 0x0154, + KEY_CONSUMER_TREBLE_DECREMENT = 0x0155, + + // Application Launcher + KEY_CONSUMER_AL_CONSUMER_CONTROL_CONFIGURATION = 0x0183, + KEY_CONSUMER_AL_EMAIL_READER = 0x018A, + KEY_CONSUMER_AL_CALCULATOR = 0x0192, + KEY_CONSUMER_AL_LOCAL_BROWSER = 0x0194, + + // Browser/Explorer Specific + KEY_CONSUMER_AC_SEARCH = 0x0221, + KEY_CONSUMER_AC_HOME = 0x0223, + KEY_CONSUMER_AC_BACK = 0x0224, + KEY_CONSUMER_AC_FORWARD = 0x0225, + KEY_CONSUMER_AC_STOP = 0x0226, + KEY_CONSUMER_AC_REFRESH = 0x0227, + KEY_CONSUMER_AC_BOOKMARKS = 0x022A, + + // Mouse Horizontal scroll + KEY_CONSUMER_AC_PAN = 0x0238, +}; + +#endif diff --git a/Buildscripts/board.cmake b/Buildscripts/board.cmake index 32a9d93c..d099f794 100644 --- a/Buildscripts/board.cmake +++ b/Buildscripts/board.cmake @@ -53,6 +53,8 @@ function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE) set(TACTILITY_BOARD_PROJECT LilygoTdeck) elseif (board_id STREQUAL "lilygo-tlora-pager") set(TACTILITY_BOARD_PROJECT LilygoTLoraPager) + elseif (board_id STREQUAL "m5stack-cardputer") + set(TACTILITY_BOARD_PROJECT M5stackCardputer) elseif (board_id STREQUAL "m5stack-core2") set(TACTILITY_BOARD_PROJECT M5stackCore2) elseif (board_id STREQUAL "m5stack-cores3") diff --git a/Buildscripts/build-and-release-all.sh b/Buildscripts/build-and-release-all.sh index 6db9c90e..6f8ca398 100755 --- a/Buildscripts/build-and-release-all.sh +++ b/Buildscripts/build-and-release-all.sh @@ -69,6 +69,9 @@ release m5stack-core2 releaseSdk release/TactilitySDK-esp32 +build m5stack-cardputer +release m5stack-cardputer + build m5stack-cores3 release m5stack-cores3 diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 300034b9..c0128538 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -20,6 +20,8 @@ ## Medium Priority +- Implement `uninstall` action in `tactility.py` +- Improve EspLcdDisplay to contain all the standard configuration options, and implement a default init function. Add a configuration class. - Statusbar icon that shows low/critical memory warnings - Make WiFi setup app that starts an access point and hosts a webpage to set up the device. This will be useful for devices without a screen, a small screen or a non-touch screen. @@ -27,6 +29,8 @@ - Try out ILI9342 https://github.com/jbrilha/esp_lcd_ili9342 - All drivers (e.g. display, touch, etc.) should call stop() in their destructor, or at least assert that they should not be running. - Bug: Turn on WiFi (when testing it wasn't connected/connecting - just active). Open chat. Observe crash. +- Toolbar: when the title doesn't fit, scroll the text instead of splitting it onto a new line (try on Waveshare 1.47") +- UI: create UI size classification (e.g. "compact" for tiny screens without touch) ## Lower Priority diff --git a/Drivers/ST7789/Source/St7789Display.cpp b/Drivers/ST7789/Source/St7789Display.cpp index 976ca589..e3420cf6 100644 --- a/Drivers/ST7789/Source/St7789Display.cpp +++ b/Drivers/ST7789/Source/St7789Display.cpp @@ -92,6 +92,13 @@ bool St7789Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lc return false; } + int gap_x = configuration->swapXY ? configuration->gapY : configuration->gapX; + int gap_y = configuration->swapXY ? configuration->gapX : configuration->gapY; + if (esp_lcd_panel_set_gap(panelHandle, gap_x, gap_y) != ESP_OK) { + TT_LOG_E(TAG, "Failed to set panel gap"); + return false; + } + return true; } diff --git a/Drivers/ST7789/Source/St7789Display.h b/Drivers/ST7789/Source/St7789Display.h index 79292b94..42782192 100644 --- a/Drivers/ST7789/Source/St7789Display.h +++ b/Drivers/ST7789/Source/St7789Display.h @@ -32,12 +32,16 @@ public: bool mirrorX = false, bool mirrorY = false, bool invertColor = false, - uint32_t bufferSize = 0 // Size in pixel count. 0 means default, which is 1/10 of the screen size + uint32_t bufferSize = 0, // Size in pixel count. 0 means default, which is 1/10 of the screen size + int gapX = 0, + int gapY = 0 ) : spiHostDevice(spiHostDevice), csPin(csPin), dcPin(dcPin), horizontalResolution(horizontalResolution), verticalResolution(verticalResolution), + gapX(gapX), + gapY(gapY), swapXY(swapXY), mirrorX(mirrorX), mirrorY(mirrorY), @@ -58,6 +62,8 @@ public: size_t transactionQueueDepth = 10; unsigned int horizontalResolution; unsigned int verticalResolution; + int gapX; + int gapY; bool swapXY = false; bool mirrorX = false; bool mirrorY = false; diff --git a/Tactility/Source/app/ElfApp.cpp b/Tactility/Source/app/ElfApp.cpp index dfb2bee1..a6dc9971 100644 --- a/Tactility/Source/app/ElfApp.cpp +++ b/Tactility/Source/app/ElfApp.cpp @@ -36,6 +36,17 @@ static size_t elfManifestSetCount = 0; static ElfManifest elfManifest; static std::shared_ptr elfManifestLock = std::make_shared(); +static std::string getErrorCodeString(int error_code) { + switch (error_code) { + case ENOMEM: + return "out of memory"; + case ENOSYS: + return "missing symbol"; + default: + return std::format("code {}", error_code); + } +} + class ElfApp : public App { const std::string appPath; @@ -77,8 +88,8 @@ class ElfApp : public App { auto relocate_result = esp_elf_relocate(&elf, elfFileData.get()); if (relocate_result != 0) { // Note: the result code mapes to values from cstdlib's errno.h - lastError = std::format("Failed to load executable (error code {})", -relocate_result); - TT_LOG_E(TAG, "%s", lastError.c_str()); + lastError = getErrorCodeString(-relocate_result); + TT_LOG_E(TAG, "Application failed to load: %s", lastError.c_str()); elfFileData = nullptr; return false; } diff --git a/Tactility/Source/lvgl/LvglSync.cpp b/Tactility/Source/lvgl/LvglSync.cpp index 22bff595..d4646ead 100644 --- a/Tactility/Source/lvgl/LvglSync.cpp +++ b/Tactility/Source/lvgl/LvglSync.cpp @@ -41,11 +41,11 @@ public: ~LvglSync() override = default; bool lock(TickType_t timeoutTicks) const override { - return tt::lvgl::lock(timeoutTicks); + return lvgl::lock(timeoutTicks); } bool unlock() const override { - tt::lvgl::unlock(); + lvgl::unlock(); return true; } }; diff --git a/sdkconfig.board.m5stack-cardputer b/sdkconfig.board.m5stack-cardputer new file mode 100644 index 00000000..61b4ff03 --- /dev/null +++ b/sdkconfig.board.m5stack-cardputer @@ -0,0 +1,56 @@ +# Software defaults +# Increase stack size for WiFi (fixes crash after scan) +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072 +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 + +# Hardware: Main +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-4mb.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions-4mb.csv" +CONFIG_TT_BOARD_M5STACK_CARDPUTER=y +CONFIG_TT_BOARD_NAME="M5Stack Cardputer" +CONFIG_TT_BOARD_ID="m5stack-cardputer" +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