From 1456f8f9fb5e0e87f2aa081b24901e9f62fc62ac Mon Sep 17 00:00:00 2001 From: Chris Greening Date: Fri, 6 Dec 2024 06:59:44 +0000 Subject: [PATCH] WIP - support for t-deck --- firmware/data/manic.z80 | Bin 0 -> 24590 bytes firmware/platformio.ini | 46 +++++++++++++++++ firmware/src/AudioOutput/I2SBase.cpp | 30 +----------- firmware/src/Emulator/keyboard_defs.h | 68 ++++++++++++++++++++++++++ firmware/src/Files/Files.h | 2 +- firmware/src/Files/Flash.cpp | 1 + firmware/src/Files/Flash.h | 4 +- firmware/src/Files/SDCard.cpp | 47 ++++++++++++++++++ firmware/src/Files/SDCard.h | 1 + firmware/src/Input/TDeckKeyboard.cpp | 53 ++++++++++++++++++++ firmware/src/Input/TDeckKeyboard.h | 19 +++++++ firmware/src/TFT/ST7789.cpp | 17 +++++-- firmware/src/main.cpp | 66 ++++++++++++++++--------- 13 files changed, 294 insertions(+), 60 deletions(-) create mode 100644 firmware/data/manic.z80 create mode 100644 firmware/src/Input/TDeckKeyboard.cpp create mode 100644 firmware/src/Input/TDeckKeyboard.h diff --git a/firmware/data/manic.z80 b/firmware/data/manic.z80 new file mode 100644 index 0000000000000000000000000000000000000000..5a2e5734dd14abf0a8e9ce0ab29b00a83b7d6dc0 GIT binary patch literal 24590 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 i--) {xyv[i]=xyv[i-1];} - for (b=0; b specKeyToLetter = { { SPECKEY_X, 'X' }, { SPECKEY_Y, 'Y' }, { SPECKEY_Z, 'Z' }, +}; + +// map letters to the SpecKeys enum +const std::unordered_map letterToSpecKey = { + { '0', SPECKEY_0 }, + { '1', SPECKEY_1 }, + { '2', SPECKEY_2 }, + { '3', SPECKEY_3 }, + { '4', SPECKEY_4 }, + { '5', SPECKEY_5 }, + { '6', SPECKEY_6 }, + { '7', SPECKEY_7 }, + { '8', SPECKEY_8 }, + { '9', SPECKEY_9 }, + { 'A', SPECKEY_A }, + { 'B', SPECKEY_B }, + { 'C', SPECKEY_C }, + { 'D', SPECKEY_D }, + { 'E', SPECKEY_E }, + { 'F', SPECKEY_F }, + { 'G', SPECKEY_G }, + { 'H', SPECKEY_H }, + { 'I', SPECKEY_I }, + { 'J', SPECKEY_J }, + { 'K', SPECKEY_K }, + { 'L', SPECKEY_L }, + { 'M', SPECKEY_M }, + { 'N', SPECKEY_N }, + { 'O', SPECKEY_O }, + { 'P', SPECKEY_P }, + { 'Q', SPECKEY_Q }, + { 'R', SPECKEY_R }, + { 'S', SPECKEY_S }, + { 'T', SPECKEY_T }, + { 'U', SPECKEY_U }, + { 'V', SPECKEY_V }, + { 'W', SPECKEY_W }, + { 'X', SPECKEY_X }, + { 'Y', SPECKEY_Y }, + { 'Z', SPECKEY_Z }, + { 'a', SPECKEY_A }, + { 'b', SPECKEY_B }, + { 'c', SPECKEY_C }, + { 'd', SPECKEY_D }, + { 'e', SPECKEY_E }, + { 'f', SPECKEY_F }, + { 'g', SPECKEY_G }, + { 'h', SPECKEY_H }, + { 'i', SPECKEY_I }, + { 'j', SPECKEY_J }, + { 'k', SPECKEY_K }, + { 'l', SPECKEY_L }, + { 'm', SPECKEY_M }, + { 'n', SPECKEY_N }, + { 'o', SPECKEY_O }, + { 'p', SPECKEY_P }, + { 'q', SPECKEY_Q }, + { 'r', SPECKEY_R }, + { 's', SPECKEY_S }, + { 't', SPECKEY_T }, + { 'u', SPECKEY_U }, + { 'v', SPECKEY_V }, + { 'w', SPECKEY_W }, + { 'x', SPECKEY_X }, + { 'y', SPECKEY_Y }, + { 'z', SPECKEY_Z }, + { ' ', SPECKEY_SPACE }, + { 0x0d, SPECKEY_ENTER }, }; \ No newline at end of file diff --git a/firmware/src/Files/Files.h b/firmware/src/Files/Files.h index 6698f68..e3674f4 100644 --- a/firmware/src/Files/Files.h +++ b/firmware/src/Files/Files.h @@ -329,7 +329,7 @@ class FilesImplementation: public IFiles for (DirectoryIterator it(full_path, prefix, extensions); it != DirectoryIterator(); ++it) { - files.push_back(FileInfoPtr(new FileInfo(StringUtils::upcase(it->d_name), full_path + "/" + it->d_name))); + files.push_back(FileInfoPtr(new FileInfo(StringUtils::upcase(it->d_name), full_path + it->d_name))); } // sort the files - is this needed? Maybe they are already alphabetically sorted std::sort(files.begin(), files.end(), [](FileInfoPtr a, FileInfoPtr b) diff --git a/firmware/src/Files/Flash.cpp b/firmware/src/Files/Flash.cpp index 6331485..a0e79c1 100644 --- a/firmware/src/Files/Flash.cpp +++ b/firmware/src/Files/Flash.cpp @@ -5,6 +5,7 @@ Flash::Flash(const char *mountPoint) { + m_mountPoint = mountPoint; if (SPIFFS.begin(true, mountPoint)) { _isMounted = true; diff --git a/firmware/src/Files/Flash.h b/firmware/src/Files/Flash.h index b857e1c..e923af8 100644 --- a/firmware/src/Files/Flash.h +++ b/firmware/src/Files/Flash.h @@ -4,7 +4,7 @@ class Flash { private: bool _isMounted; - const char *_mountPoint; + std::string m_mountPoint; public: Flash(const char *mountPoint); ~Flash(); @@ -12,6 +12,6 @@ class Flash return _isMounted; } const char *mountPoint() { - return _mountPoint; + return m_mountPoint.c_str(); } }; \ No newline at end of file diff --git a/firmware/src/Files/SDCard.cpp b/firmware/src/Files/SDCard.cpp index a7e219f..a4224f4 100644 --- a/firmware/src/Files/SDCard.cpp +++ b/firmware/src/Files/SDCard.cpp @@ -67,6 +67,53 @@ SDCard::SDCard(const char *mountPoint, gpio_num_t clk, gpio_num_t cmd, gpio_num_ #endif } +SDCard::SDCard(const char *mountPoint, gpio_num_t cs) { + m_mountPoint = mountPoint; + m_host.max_freq_khz = SDMMC_FREQ_52M; + esp_err_t ret; + // Options for mounting the filesystem. + // If format_if_mount_failed is set to true, SD card will be partitioned and + // formatted in case when mounting fails. + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 5, + .allocation_unit_size = 16 * 1024}; + + Serial.println("Initializing SD card"); + + // This initializes the slot without card detect (CD) and write protect (WP) signals. + // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals. + sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT(); + slot_config.gpio_cs = cs; + slot_config.host_id = spi_host_device_t(m_host.slot); + + ret = esp_vfs_fat_sdspi_mount(mountPoint, &m_host, &slot_config, &mount_config, &m_card); + if (ret != ESP_OK) + { + if (ret == ESP_FAIL) + { + Serial.println("Failed to mount filesystem. " + "If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option."); + } + else + { + Serial.printf("Failed to initialize the card (%s). " + "Make sure SD card lines have pull-up resistors in place.\n", + esp_err_to_name(ret)); + } + return; + } + Serial.printf("SDCard mounted at: %s\n", mountPoint); + // Card has been initialized, print its properties + sdmmc_card_print_info(stdout, m_card); + _isMounted = true; + + m_sector_count = m_card->csd.capacity; + m_sector_size = m_card->csd.sector_size; + + m_mutex = xSemaphoreCreateMutex(); +} + SDCard::SDCard(const char *mountPoint, gpio_num_t miso, gpio_num_t mosi, gpio_num_t clk, gpio_num_t cs) { m_mountPoint = mountPoint; diff --git a/firmware/src/Files/SDCard.h b/firmware/src/Files/SDCard.h index 07c93b4..21dc1bf 100644 --- a/firmware/src/Files/SDCard.h +++ b/firmware/src/Files/SDCard.h @@ -25,6 +25,7 @@ class SDCard return m_mountPoint.c_str(); } SDCard(const char *mountPoint, gpio_num_t miso, gpio_num_t mosi, gpio_num_t clk, gpio_num_t cs); + SDCard(const char *mountPoint, gpio_num_t cs); SDCard(const char *mountPoint, gpio_num_t clk, gpio_num_t cmd, gpio_num_t d0, gpio_num_t d1, gpio_num_t d2, gpio_num_t d3); bool writeSectors(uint8_t *src, size_t start_sector, size_t sector_count); bool readSectors(uint8_t *dst, size_t start_sector, size_t sector_count); diff --git a/firmware/src/Input/TDeckKeyboard.cpp b/firmware/src/Input/TDeckKeyboard.cpp new file mode 100644 index 0000000..e222b14 --- /dev/null +++ b/firmware/src/Input/TDeckKeyboard.cpp @@ -0,0 +1,53 @@ +#include "Wire.h" +#include "../Serial.h" +#include "TDeckKeyboard.h" + +TDeckKeyboard::TDeckKeyboard(KeyEventType keyEvent, KeyPressedEventType keyPressedEvent) : m_keyEvent(keyEvent), m_keyPressedEvent(keyPressedEvent) +{ +} + +bool TDeckKeyboard::start() { + Wire.begin(GPIO_NUM_18, GPIO_NUM_8); + Wire.requestFrom(0x55, 1); + if (Wire.read() == -1) { + Serial.println("LILYGO Keyboad not online ."); + return false; + } + // kick off a task to read the keyboard + xTaskCreate(readKeyboardTask, "readKeyboardTask", 4096, this, 5, NULL); + return true; +} + +void TDeckKeyboard::readKeyboardTask(void *pvParameters) { + TDeckKeyboard *self = (TDeckKeyboard *)pvParameters; + self->readKeyboard(); +} + +void TDeckKeyboard::readKeyboard() { + SpecKeys lastKey = SpecKeys::SPECKEY_NONE; + unsigned long lastKeyPress = 0; + while(true) { + char keyValue = 0; + Wire.requestFrom(0x55, 1); + while (Wire.available() > 0) { + keyValue = Wire.read(); + if (keyValue != (char)0x00) { + // is it a valid spec key? + // convert to uppercase + if (letterToSpecKey.find(keyValue) != letterToSpecKey.end()) { + SpecKeys key = letterToSpecKey.at(keyValue); + m_keyPressedEvent(key); + m_keyEvent(key, true); + lastKey = key; + lastKeyPress = millis(); + } + } + // release any pressed keys + if (lastKey != SpecKeys::SPECKEY_NONE && millis() - lastKeyPress > 100) { + m_keyEvent(lastKey, false); + lastKey = SpecKeys::SPECKEY_NONE; + } + } + vTaskDelay(10); + } +} \ No newline at end of file diff --git a/firmware/src/Input/TDeckKeyboard.h b/firmware/src/Input/TDeckKeyboard.h new file mode 100644 index 0000000..3575c17 --- /dev/null +++ b/firmware/src/Input/TDeckKeyboard.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include "../Emulator/keyboard_defs.h" + +class TDeckKeyboard +{ +private: + // this is used for key down and key up events + using KeyEventType = std::function; + KeyEventType m_keyEvent; + // this is used for key pressed evens and supports repeating keys + using KeyPressedEventType = std::function; + KeyPressedEventType m_keyPressedEvent; +public: + TDeckKeyboard(KeyEventType keyEvent, KeyPressedEventType keyPressedEvent); + bool start(); + static void readKeyboardTask(void *pvParameters); + void readKeyboard(); +}; diff --git a/firmware/src/TFT/ST7789.cpp b/firmware/src/TFT/ST7789.cpp index 3d4c93c..411f1a8 100644 --- a/firmware/src/TFT/ST7789.cpp +++ b/firmware/src/TFT/ST7789.cpp @@ -83,12 +83,21 @@ ST7789::ST7789(gpio_num_t cs, gpio_num_t dc, gpio_num_t rst, gpio_num_t bl, int width, int height) : TFTDisplay(cs, dc, rst, bl, width, height) { + pinMode(rst, OUTPUT); + if (bl != GPIO_NUM_NC) { + pinMode(bl, OUTPUT); + digitalWrite(bl, 0); + delay(3); + digitalWrite(bl, 1); + } // Reset the display - digitalWrite(rst, LOW); - // gpio_set_level(rst, 0); + if (rst != GPIO_NUM_NC) { + digitalWrite(rst, LOW); + } vTaskDelay(pdMS_TO_TICKS(100)); - digitalWrite(rst, HIGH); - // gpio_set_level(rst, 1); + if (rst != GPIO_NUM_NC) { + digitalWrite(rst, HIGH); + } vTaskDelay(pdMS_TO_TICKS(100)); SEND_CMD_DATA(ST7789_SLPOUT); // Sleep out diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 24ec95d..43b6de8 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -30,6 +30,7 @@ #include "Input/SerialKeyboard.h" #include "Input/Nunchuck.h" #include "Input/AdafruitSeeSaw.h" +#include "Input/TDeckKeyboard.h" #include "TFT/TFTDisplay.h" #include "TFT/ST7789.h" #include "TFT/ILI9341.h" @@ -45,22 +46,16 @@ const char *MOUNT_POINT = "/fs"; void setup(void) { - // Files -#ifdef USE_SDCARD -#ifdef USE_SDIO - SDCard *fileSystem = new SDCard(MOUNT_POINT, SD_CARD_CLK, SD_CARD_CMD, SD_CARD_D0, SD_CARD_D1, SD_CARD_D2, SD_CARD_D3); - IFiles *files = new FilesImplementation(fileSystem); - setupUSB(fileSystem); -#else - SDCard *fileSystem = new SDCard(MOUNT_POINT, SD_CARD_MISO, SD_CARD_MOSI, SD_CARD_CLK, SD_CARD_CS); - IFiles *files = new FilesImplementation(fileSystem); -#endif + #ifdef BOARD_POWERON + pinMode(BOARD_POWERON, OUTPUT); + digitalWrite(BOARD_POWERON, HIGH); + #endif Serial.begin(115200); - // for(int i = 0; i < 5; i++) { - // BusyLight bl; - // vTaskDelay(pdMS_TO_TICKS(1000)); - // Serial.println("Booting..."); - // } + for(int i = 0; i < 5; i++) { + BusyLight bl; + vTaskDelay(pdMS_TO_TICKS(1000)); + Serial.println("Booting..."); + } // print out avialable ram Serial.printf("Free heap: %d\n", ESP.getFreeHeap()); Serial.printf("Free PSRAM: %d\n", ESP.getFreePsram()); @@ -75,7 +70,7 @@ void setup(void) // Initialize SPI spi_bus_config_t buscfg = { .mosi_io_num = TFT_MOSI, - .miso_io_num = -1, + .miso_io_num = TFT_MISO, .sclk_io_num = TFT_SCLK, .quadwp_io_num = -1, .quadhd_io_num = -1, @@ -91,6 +86,25 @@ void setup(void) ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); Serial.println("SPI initialized"); #endif + // Files + #ifdef USE_SDCARD + #ifdef USE_SDIO + SDCard *fileSystem = new SDCard(MOUNT_POINT, SD_CARD_CLK, SD_CARD_CMD, SD_CARD_D0, SD_CARD_D1, SD_CARD_D2, SD_CARD_D3); + IFiles *files = new FilesImplementation(fileSystem); + setupUSB(fileSystem); + #else + #ifdef SD_CARD_MISO + SDCard *fileSystem = new SDCard(MOUNT_POINT, SD_CARD_MISO, SD_CARD_MOSI, SD_CARD_CLK, SD_CARD_CS); + #else + // SD Card shares the SPI bus with the TFT + SDCard *fileSystem = new SDCard(MOUNT_POINT, SD_CARD_CS); + #endif + IFiles *files = new FilesImplementation(fileSystem); + #endif + #else + Flash *fileSystem = new Flash(MOUNT_POINT); + IFiles *files = new FilesImplementation(fileSystem); + #endif #ifdef TFT_ST7789 Display *tft = new ST7789(TFT_CS, TFT_DC, TFT_RST, TFT_BL, TFT_WIDTH, TFT_HEIGHT); #endif @@ -101,11 +115,12 @@ void setup(void) // navigation stack NavigationStack *navigationStack = new NavigationStack(tft, hdmiDisplay); // Audio output + AudioOutput *audioOutput = nullptr; #ifdef USE_DAC_AUDIO - AudioOutput *audioOutput = new DACOutput(I2S_NUM_0); + audioOutput = new DACOutput(I2S_NUM_0); #endif #ifdef BUZZER_GPIO_NUM - AudioOutput *audioOutput = new BuzzerOutput(BUZZER_GPIO_NUM); + audioOutput = new BuzzerOutput(BUZZER_GPIO_NUM); #endif #ifdef PDM_GPIO_NUM // i2s speaker pins @@ -114,7 +129,7 @@ void setup(void) .ws_io_num = GPIO_NUM_0, .data_out_num = BUZZER_GPIO_NUM, .data_in_num = I2S_PIN_NO_CHANGE}; - AudioOutput *audioOutput = new PDMOutput(I2S_NUM_0, i2s_speaker_pins); + audioOutput = new PDMOutput(I2S_NUM_0, i2s_speaker_pins); #endif #ifdef I2S_SPEAKER_SERIAL_CLOCK #ifdef SPK_MODE @@ -127,7 +142,7 @@ void setup(void) .ws_io_num = I2S_SPEAKER_LEFT_RIGHT_CLOCK, .data_out_num = I2S_SPEAKER_SERIAL_DATA, .data_in_num = I2S_PIN_NO_CHANGE}; - AudioOutput *audioOutput = new I2SOutput(I2S_NUM_1, i2s_speaker_pins); + audioOutput = new I2SOutput(I2S_NUM_1, i2s_speaker_pins); #endif #ifdef TOUCH_KEYBOARD TouchKeyboard *touchKeyboard = new TouchKeyboard( @@ -147,14 +162,17 @@ void setup(void) [&](SpecKeys key) { navigationStack->pressKey(key); }); touchKeyboard->start(); +#endif +#ifdef LILYGO_T_KEYBOARD + TDeckKeyboard *tDeckKeyboard = new TDeckKeyboard([&](SpecKeys key, bool down) + { navigationStack->updateKey(key, down); }, + [&](SpecKeys key) + { navigationStack->pressKey(key); }); + tDeckKeyboard->start(); #endif if (audioOutput) { audioOutput->start(15625); } -#else - Flash *fileSystem = new Flash(MOUNT_POINT); - Files *files = new Files(fileSystem); -#endif // create the directory structure files->createDirectory("/snapshots"); MainMenuScreen menuPicker(*tft, hdmiDisplay, audioOutput, files);