diff --git a/.clang-format b/.clang-format index 5fd46b99..ac6206d7 100644 --- a/.clang-format +++ b/.clang-format @@ -5,6 +5,7 @@ AccessModifierOffset: -4 AlignAfterOpenBracket: BlockIndent AlignConsecutiveAssignments: None AlignOperands: DontAlign +AlignTrailingComments: false AllowAllArgumentsOnNextLine: false AllowAllConstructorInitializersOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false @@ -37,6 +38,8 @@ BreakInheritanceList: BeforeColon ColumnLimit: 0 CompactNamespaces: false ContinuationIndentWidth: 4 +EmptyLineBeforeAccessModifier: Always +EmptyLineAfterAccessModifier: Always IndentCaseLabels: true IndentPPDirectives: None IndentWidth: 4 diff --git a/.github/workflows/build-firmware.yml b/.github/workflows/build-firmware.yml index 90697674..73883e53 100644 --- a/.github/workflows/build-firmware.yml +++ b/.github/workflows/build-firmware.yml @@ -40,3 +40,12 @@ jobs: with: board_id: m5stack-cores3 arch: esp32s3 + unphone: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: "Build" + uses: ./.github/actions/build-firmware + with: + board_id: unphone + arch: esp32s3 diff --git a/App/CMakeLists.txt b/App/CMakeLists.txt index 5b52a210..1b3d133d 100644 --- a/App/CMakeLists.txt +++ b/App/CMakeLists.txt @@ -14,6 +14,7 @@ if (DEFINED ENV{ESP_IDF_VERSION}) list(APPEND BOARD_COMPONENTS LilygoTdeck M5stackCoreS3 + UnPhone ) endif() diff --git a/App/Kconfig b/App/Kconfig index fef9ec43..4c773242 100644 --- a/App/Kconfig +++ b/App/Kconfig @@ -14,6 +14,8 @@ menu "Tactility App" bool "M5Stack Core2" config TT_BOARD_M5STACK_CORES3 bool "M5Stack CoreS3" + config TT_BOARD_UNPHONE + bool "unPhone" help Select a board/hardware configuration. Use TT_BOARD_CUSTOM if you will manually configure the board in your project. diff --git a/App/Source/Boards.h b/App/Source/Boards.h index 49f3b331..5273f7dc 100644 --- a/App/Source/Boards.h +++ b/App/Source/Boards.h @@ -16,6 +16,9 @@ #elif defined(CONFIG_TT_BOARD_M5STACK_CORES3) #include "M5stackCoreS3.h" #define TT_BOARD_HARDWARE &m5stack_cores3 +#elif defined(CONFIG_TT_BOARD_UNPHONE) +#include "UnPhone.h" +#define TT_BOARD_HARDWARE &unPhone #else #define TT_BOARD_HARDWARE NULL #error Replace TT_BOARD_HARDWARE in main.c with your own. Or copy one of the ./sdkconfig.board.* files into ./sdkconfig. diff --git a/App/idf_component.yml b/App/idf_component.yml index 335109d9..989ca606 100644 --- a/App/idf_component.yml +++ b/App/idf_component.yml @@ -1,9 +1,12 @@ dependencies: espressif/esp_lcd_ili9341: "2.0.0" + espressif/esp_lcd_touch: "1.1.2" + atanisoft/esp_lcd_touch_xpt2046: "1.0.5" espressif/esp_lcd_touch_cst816s: "1.0.3" espressif/esp_lcd_touch_gt911: "1.1.1~2" espressif/esp_lcd_touch_ft5x06: "1.0.6~1" - espressif/esp_lcd_touch: "1.1.2" + espressif/esp_io_expander: "1.0.1" + espressif/esp_io_expander_tca95xx_16bit: "1.0.1" espressif/esp_tinyusb: version: "1.5.0" rules: diff --git a/Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp b/Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp index 02dc0bb9..5fe269bc 100644 --- a/Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp +++ b/Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp @@ -6,7 +6,7 @@ #include #include -#define TDECK_SDCARD_SPI_FREQUENCY 800000U +#define TDECK_SDCARD_SPI_FREQUENCY 20000000U #define TDECK_SDCARD_PIN_CS GPIO_NUM_39 #define TDECK_LCD_PIN_CS GPIO_NUM_12 #define TDECK_RADIO_PIN_CS GPIO_NUM_9 diff --git a/Boards/M5stackCore2/Source/hal/Core2SdCard.cpp b/Boards/M5stackCore2/Source/hal/Core2SdCard.cpp index fac1b7c5..e59ce3ba 100644 --- a/Boards/M5stackCore2/Source/hal/Core2SdCard.cpp +++ b/Boards/M5stackCore2/Source/hal/Core2SdCard.cpp @@ -5,7 +5,7 @@ #include -#define CORE2_SDCARD_SPI_FREQUENCY 800000U +#define CORE2_SDCARD_SPI_FREQUENCY 20000000U #define CORE2_SDCARD_PIN_CS GPIO_NUM_4 #define CORE2_LCD_PIN_CS GPIO_NUM_5 diff --git a/Boards/M5stackCoreS3/CMakeLists.txt b/Boards/M5stackCoreS3/CMakeLists.txt index 4f6440a1..2248d676 100644 --- a/Boards/M5stackCoreS3/CMakeLists.txt +++ b/Boards/M5stackCoreS3/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( - SRC_DIRS "Source" "Source/hal" "Source/Axp2101" "Source/Aw9523" "Source/I2cDevice" + SRC_DIRS "Source" "Source/hal" "Source/Axp2101" "Source/Aw9523" INCLUDE_DIRS "Source" REQUIRES Tactility esp_lvgl_port esp_lcd esp_lcd_ili9341 esp_lcd_touch_ft5x06 driver vfs fatfs ) diff --git a/Boards/M5stackCoreS3/Source/Aw9523/Aw9523.h b/Boards/M5stackCoreS3/Source/Aw9523/Aw9523.h index cc3c8c20..8e643b37 100644 --- a/Boards/M5stackCoreS3/Source/Aw9523/Aw9523.h +++ b/Boards/M5stackCoreS3/Source/Aw9523/Aw9523.h @@ -1,6 +1,6 @@ #pragma once -#include "I2cDevice/I2cDevice.h" +#include "hal/i2c/I2cDevice.h" #define AW9523_ADDRESS 0x58 diff --git a/Boards/M5stackCoreS3/Source/Axp2101/Axp2101.h b/Boards/M5stackCoreS3/Source/Axp2101/Axp2101.h index 7deb0b55..bbf3c0ef 100644 --- a/Boards/M5stackCoreS3/Source/Axp2101/Axp2101.h +++ b/Boards/M5stackCoreS3/Source/Axp2101/Axp2101.h @@ -1,7 +1,6 @@ #pragma once -#include "hal/i2c/I2c.h" -#include "I2cDevice/I2cDevice.h" +#include "hal/i2c/I2cDevice.h" #define AXP2101_ADDRESS 0x34 diff --git a/Boards/M5stackCoreS3/Source/hal/CoreS3SdCard.cpp b/Boards/M5stackCoreS3/Source/hal/CoreS3SdCard.cpp index a315fead..4a5181d4 100644 --- a/Boards/M5stackCoreS3/Source/hal/CoreS3SdCard.cpp +++ b/Boards/M5stackCoreS3/Source/hal/CoreS3SdCard.cpp @@ -5,7 +5,7 @@ #include -#define CORES3_SDCARD_SPI_FREQUENCY 800000U +#define CORES3_SDCARD_SPI_FREQUENCY 20000000U #define CORES3_SDCARD_PIN_CS GPIO_NUM_4 #define CORES3_LCD_PIN_CS GPIO_NUM_3 diff --git a/Boards/UnPhone/CMakeLists.txt b/Boards/UnPhone/CMakeLists.txt new file mode 100644 index 00000000..b119021a --- /dev/null +++ b/Boards/UnPhone/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "Source" "Source/hal" "Source/hx8357" "Source/bq24295" + INCLUDE_DIRS "Source" + REQUIRES Tactility esp_lvgl_port esp_io_expander esp_io_expander_tca95xx_16bit esp_lcd_touch esp_lcd_touch_xpt2046 +) diff --git a/Boards/UnPhone/Source/InitHardware.cpp b/Boards/UnPhone/Source/InitHardware.cpp new file mode 100644 index 00000000..656c0459 --- /dev/null +++ b/Boards/UnPhone/Source/InitHardware.cpp @@ -0,0 +1,46 @@ +#include "TactilityCore.h" +#include "hal/UnPhoneDisplayConstants.h" +#include "hx8357/disp_spi.h" +#include +#include +#include + +#define TAG "unphone" + +// SPI +#define UNPHONE_SPI_HOST SPI2_HOST +#define UNPHONE_SPI_PIN_SCLK GPIO_NUM_39 +#define UNPHONE_SPI_PIN_MOSI GPIO_NUM_40 +#define UNPHONE_SPI_PIN_MISO GPIO_NUM_41 +#define UNPHONE_SPI_TRANSFER_SIZE_LIMIT (UNPHONE_LCD_HORIZONTAL_RESOLUTION * UNPHONE_LCD_SPI_TRANSFER_HEIGHT * LV_COLOR_DEPTH / 8) + +static bool initSpi() { + TT_LOG_I(TAG, LOG_MESSAGE_SPI_INIT_START_FMT, UNPHONE_SPI_HOST); + + spi_bus_config_t bus_config = { + .mosi_io_num = UNPHONE_SPI_PIN_MOSI, + .miso_io_num = UNPHONE_SPI_PIN_MISO, + .sclk_io_num = UNPHONE_SPI_PIN_SCLK, + .quadwp_io_num = -1, // Quad SPI LCD driver is not yet supported + .quadhd_io_num = -1, // Quad SPI LCD driver is not yet supported + .data4_io_num = 0, + .data5_io_num = 0, + .data6_io_num = 0, + .data7_io_num = 0, + .max_transfer_sz = UNPHONE_SPI_TRANSFER_SIZE_LIMIT, + .flags = 0, + .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO, + .intr_flags = 0 + }; + + if (spi_bus_initialize(UNPHONE_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO) != ESP_OK) { + TT_LOG_E(TAG, LOG_MESSAGE_SPI_INIT_FAILED_FMT, UNPHONE_SPI_HOST); + return false; + } + + return true; +} + +bool unPhoneInitHardware() { + return initSpi(); +} diff --git a/Boards/UnPhone/Source/Lvgl.cpp b/Boards/UnPhone/Source/Lvgl.cpp new file mode 100644 index 00000000..503dbf5f --- /dev/null +++ b/Boards/UnPhone/Source/Lvgl.cpp @@ -0,0 +1,33 @@ +#include "Log.h" +#include "Thread.h" +#include "lvgl/LvglSync.h" +#include "esp_lvgl_port.h" +#include "hal/UnPhoneDisplay.h" + +#define TAG "unphone_lvgl" + +// LVGL +// The minimum task stack seems to be about 3500, but that crashes the wifi app in some scenarios +// At 8192, it sometimes crashes when wifi-auto enables and is busy connecting and then you open WifiManage +#define UNPHONE_LVGL_TASK_STACK_DEPTH 9216 + +bool unPhoneInitLvgl() { + static lv_disp_t* display = nullptr; + const lvgl_port_cfg_t lvgl_cfg = { + .task_priority = static_cast(tt::THREAD_PRIORITY_RENDER), + .task_stack = UNPHONE_LVGL_TASK_STACK_DEPTH, + .task_affinity = -1, // core pinning + .task_max_sleep_ms = 500, + .timer_period_ms = 5 + }; + + TT_LOG_D(TAG, "LVGL port init"); + if (lvgl_port_init(&lvgl_cfg) != ESP_OK) { + TT_LOG_E(TAG, "LVGL port init failed"); + return false; + } + + tt::lvgl::syncSet(&lvgl_port_lock, &lvgl_port_unlock); + + return true; +} diff --git a/Boards/UnPhone/Source/PowerOn.cpp b/Boards/UnPhone/Source/PowerOn.cpp new file mode 100644 index 00000000..963cd068 --- /dev/null +++ b/Boards/UnPhone/Source/PowerOn.cpp @@ -0,0 +1,97 @@ +#include "TactilityCore.h" +#include "UnPhoneFeatures.h" +#include + +#define TAG "unphone" + +extern UnPhoneFeatures unPhoneFeatures; + +static std::unique_ptr powerThread; + +static void updatePowerSwitch() { + static bool last_on_state = true; + + if (!unPhoneFeatures.isPowerSwitchOn()) { + if (last_on_state) { + TT_LOG_W(TAG, "Power off"); + } + + unPhoneFeatures.turnPeripheralsOff(); + + if (!unPhoneFeatures.isUsbPowerConnected()) { // and usb unplugged we go into shipping mode + if (last_on_state) { + TT_LOG_W(TAG, "Shipping mode until USB connects"); + unPhoneFeatures.setShipping(true); // tell BM to stop supplying power until USB connects + } + } else { // power switch off and usb plugged in we sleep + unPhoneFeatures.wakeOnPowerSwitch(); + esp_sleep_enable_timer_wakeup(60000000); // ea min: USB? else->shipping + esp_deep_sleep_start(); // deep sleep, wait for wakeup on GPIO + } + + last_on_state = false; + } else { + if (!last_on_state) { + TT_LOG_W(TAG, "Power on"); + unPhoneFeatures.setShipping(false); + } + last_on_state = true; + } +} + +static int32_t powerSwitchMain(void*) { // check power switch every 10th of sec + while (true) { + updatePowerSwitch(); + tt::kernel::delayMillis(200); + } +} + +static void startPowerSwitchThread() { + powerThread = std::make_unique( + "unphone_power_switch", + 4096, + powerSwitchMain, + nullptr + ); + powerThread->start(); +} + +static bool unPhonePowerOn() { + if (!unPhoneFeatures.init()) { + TT_LOG_E(TAG, "UnPhoneFeatures init failed"); + return false; + } + + unPhoneFeatures.printInfo(); + + updatePowerSwitch(); + startPowerSwitchThread(); + + unPhoneFeatures.setBacklightPower(false); + + // Init touch screen GPIOs (used for vibe motor) + unPhoneFeatures.setVibePower(false); + + unPhoneFeatures.setIrPower(false); + + // This will be default LOW implicitly, but this makes it explicit + unPhoneFeatures.setExpanderPower(false); + + // Vibrate once + unPhoneFeatures.setVibePower(true); + tt::kernel::delayMillis(150); + unPhoneFeatures.setVibePower(false); + + return true; +} + +bool unPhoneInitPower() { + ESP_LOGI(TAG, LOG_MESSAGE_POWER_ON_START); + + if (!unPhonePowerOn()) { + TT_LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED); + return false; + } + + return true; +} diff --git a/Boards/UnPhone/Source/UnPhone.cpp b/Boards/UnPhone/Source/UnPhone.cpp new file mode 100644 index 00000000..eb9bf209 --- /dev/null +++ b/Boards/UnPhone/Source/UnPhone.cpp @@ -0,0 +1,57 @@ +#include "UnPhoneFeatures.h" +#include "hal/Configuration.h" +#include "hal/UnPhoneDisplay.h" +#include "hal/UnPhoneSdCard.h" + +bool unPhoneInitPower(); +bool unPhoneInitHardware(); +bool unPhoneInitLvgl(); + +// Shared object, used in PowerOn and UnPhoneDisplay +UnPhoneFeatures unPhoneFeatures; + +extern const tt::hal::Configuration unPhone = { + .initBoot = unPhoneInitPower, + .initHardware = unPhoneInitHardware, + .initLvgl = unPhoneInitLvgl, + .createDisplay = createDisplay, + .sdcard = createUnPhoneSdCard(), + .i2c = { + tt::hal::i2c::Configuration { + .name = "Internal", + .port = I2C_NUM_0, + .initMode = tt::hal::i2c::InitMode::ByTactility, + .canReinit = false, + .hasMutableConfiguration = false, + .config = (i2c_config_t) { + .mode = I2C_MODE_MASTER, + .sda_io_num = GPIO_NUM_3, + .scl_io_num = GPIO_NUM_4, + .sda_pullup_en = true, + .scl_pullup_en = true, + .master = { + .clk_speed = 400000 + }, + .clk_flags = 0 + } + }, + tt::hal::i2c::Configuration { + .name = "Unused", + .port = I2C_NUM_1, + .initMode = tt::hal::i2c::InitMode::Disabled, + .canReinit = true, + .hasMutableConfiguration = true, + .config = (i2c_config_t) { + .mode = I2C_MODE_MASTER, + .sda_io_num = GPIO_NUM_NC, + .scl_io_num = GPIO_NUM_NC, + .sda_pullup_en = false, + .scl_pullup_en = false, + .master = { + .clk_speed = 400000 + }, + .clk_flags = 0 + } + } + } +}; diff --git a/Boards/UnPhone/Source/UnPhone.h b/Boards/UnPhone/Source/UnPhone.h new file mode 100644 index 00000000..a2370b6a --- /dev/null +++ b/Boards/UnPhone/Source/UnPhone.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const tt::hal::Configuration unPhone; diff --git a/Boards/UnPhone/Source/UnPhoneFeatures.cpp b/Boards/UnPhone/Source/UnPhoneFeatures.cpp new file mode 100644 index 00000000..1f0d884c --- /dev/null +++ b/Boards/UnPhone/Source/UnPhoneFeatures.cpp @@ -0,0 +1,279 @@ +#include "UnPhoneFeatures.h" +#include "FreeRTOS-Kernel/include/FreeRTOS.h" +#include "Log.h" +#include "service/loader/Loader.h" +#include +#include +#include + +namespace pin { + static const gpio_num_t BUTTON1 = GPIO_NUM_45; // left button + static const gpio_num_t BUTTON2 = GPIO_NUM_0; // middle button + static const gpio_num_t BUTTON3 = GPIO_NUM_21; // right button + static const gpio_num_t IR_LEDS = GPIO_NUM_12; + static const gpio_num_t LED_RED = GPIO_NUM_13; + static const gpio_num_t POWER_SWITCH = GPIO_NUM_18; +} // namespace pin + +namespace expanderpin { + static const esp_io_expander_pin_num_t BACKLIGHT = IO_EXPANDER_PIN_NUM_2; + static const esp_io_expander_pin_num_t EXPANDER_POWER = IO_EXPANDER_PIN_NUM_0; // enable exp brd if high + static const esp_io_expander_pin_num_t LED_GREEN = IO_EXPANDER_PIN_NUM_9; + static const esp_io_expander_pin_num_t LED_BLUE = IO_EXPANDER_PIN_NUM_13; + static const esp_io_expander_pin_num_t USB_VSENSE = IO_EXPANDER_PIN_NUM_14; + static const esp_io_expander_pin_num_t VIBE = IO_EXPANDER_PIN_NUM_7; +} // namespace expanderpin + +#define TAG "unhpone_features" + +// TODO: Make part of a new type of UnPhoneFeatures data struct that holds all the thread-related data +QueueHandle_t interruptQueue; + +static void IRAM_ATTR navButtonInterruptHandler(void* args) { + int pinNumber = (int)args; + xQueueSendFromISR(interruptQueue, &pinNumber, NULL); +} + +static int32_t buttonHandlingThreadMain(void* context) { + auto* interrupted = (bool*)context; + int pinNumber; + while (!*interrupted) { + if (xQueueReceive(interruptQueue, &pinNumber, portMAX_DELAY)) { + TT_LOG_I(TAG, "Pressed button %d", pinNumber); + if (pinNumber == pin::BUTTON1) { + tt::service::loader::stopApp(); + } + } + } + return 0; +} + +UnPhoneFeatures::~UnPhoneFeatures() { + if (buttonHandlingThread.getState() != tt::Thread::State::Stopped) { + buttonHandlingThreadInterruptRequest = true; + buttonHandlingThread.join(); + } +} + +bool UnPhoneFeatures::initPowerSwitch() { + uint64_t power_pin_mask = BIT64(pin::POWER_SWITCH); + + gpio_config_t power_gpio_config = { + .pin_bit_mask = power_pin_mask, + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_POSEDGE, + }; + + if (gpio_config(&power_gpio_config) != ESP_OK) { + TT_LOG_E(TAG, "Power pin init failed"); + return false; + } + + if (rtc_gpio_pullup_en(pin::POWER_SWITCH) == ESP_OK && + rtc_gpio_pulldown_en(pin::POWER_SWITCH) == ESP_OK) { + return true; + } else { + TT_LOG_E(TAG, "Failed to set RTC for power switch"); + return false; + } +} + +bool UnPhoneFeatures::initNavButtons() { + interruptQueue = xQueueCreate(4, sizeof(int)); + + buttonHandlingThread.setName("unphone_buttons"); + buttonHandlingThread.setPriority(tt::Thread::Priority::High); + buttonHandlingThread.setStackSize(3072); + buttonHandlingThread.setCallback(buttonHandlingThreadMain, &buttonHandlingThreadInterruptRequest); + buttonHandlingThread.start(); + + uint64_t input_pin_mask = + BIT64(pin::BUTTON1) | + BIT64(pin::BUTTON2) | + BIT64(pin::BUTTON3); + + gpio_config_t input_gpio_config = { + .pin_bit_mask = input_pin_mask, + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_NEGEDGE, + }; + + if (gpio_config(&input_gpio_config) != ESP_OK) { + TT_LOG_E(TAG, "Nav button pin init failed"); + return false; + } + + if ( + gpio_install_isr_service(0) != ESP_OK || + gpio_isr_handler_add(pin::BUTTON1, navButtonInterruptHandler, (void*)pin::BUTTON1) != ESP_OK || + gpio_isr_handler_add(pin::BUTTON2, navButtonInterruptHandler, (void*)pin::BUTTON2) != ESP_OK || + gpio_isr_handler_add(pin::BUTTON3, navButtonInterruptHandler, (void*)pin::BUTTON3) != ESP_OK + ) { + TT_LOG_E(TAG, "Nav buttons ISR init failed"); + return false; + } + + return true; +} + +bool UnPhoneFeatures::initOutputPins() { + uint64_t output_pin_mask = + BIT64(pin::IR_LEDS) | + BIT64(pin::LED_RED); + + gpio_config_t output_gpio_config = { + .pin_bit_mask = output_pin_mask, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + + if (gpio_config(&output_gpio_config) != ESP_OK) { + TT_LOG_E(TAG, "Output pin init failed"); + return false; + } + + return true; +} + +bool UnPhoneFeatures::initGpioExpander() { + // ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_110 corresponds with 0x26 from the docs at + // https://gitlab.com/hamishcunningham/unphonelibrary/-/blob/main/unPhone.h?ref_type=heads#L206 + if (esp_io_expander_new_i2c_tca95xx_16bit(I2C_NUM_0, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_110, &ioExpander) != ESP_OK) { + TT_LOG_E(TAG, "IO expander init failed"); + return false; + } + assert(ioExpander != nullptr); + + // Output pins + esp_io_expander_set_dir(ioExpander, expanderpin::BACKLIGHT, IO_EXPANDER_OUTPUT); + esp_io_expander_set_dir(ioExpander, expanderpin::EXPANDER_POWER, IO_EXPANDER_OUTPUT); + esp_io_expander_set_dir(ioExpander, expanderpin::LED_GREEN, IO_EXPANDER_OUTPUT); + esp_io_expander_set_dir(ioExpander, expanderpin::LED_BLUE, IO_EXPANDER_OUTPUT); + esp_io_expander_set_dir(ioExpander, expanderpin::VIBE, IO_EXPANDER_OUTPUT); + // Input pins + esp_io_expander_set_dir(ioExpander, expanderpin::USB_VSENSE, IO_EXPANDER_INPUT); + + return true; +} + +bool UnPhoneFeatures::init() { + TT_LOG_I(TAG, "init"); + + if (!initNavButtons()) { + TT_LOG_E(TAG, "Input pin init failed"); + return false; + } + + if (!initOutputPins()) { + TT_LOG_E(TAG, "Output pin init failed"); + return false; + } + + if (!initPowerSwitch()) { + TT_LOG_E(TAG, "Power button init failed"); + return false; + } + + if (!initGpioExpander()) { + TT_LOG_E(TAG, "GPIO expander init failed"); + return false; + } + + return true; +} + +void UnPhoneFeatures::printInfo() const { + esp_io_expander_print_state(ioExpander); + batteryManagement.printInfo(); + bool backlight_power; + const char* backlight_power_state = getBacklightPower(backlight_power) && backlight_power ? "on" : "off"; + TT_LOG_I(TAG, "Backlight: %s", backlight_power_state); +} + +bool UnPhoneFeatures::setRgbLed(bool red, bool green, bool blue) const { + assert(ioExpander != nullptr); + return gpio_set_level(pin::LED_RED, red ? 1U : 0U) == ESP_OK && + esp_io_expander_set_level(ioExpander, expanderpin::LED_GREEN, green ? 1U : 0U) == ESP_OK && + esp_io_expander_set_level(ioExpander, expanderpin::LED_BLUE, blue ? 1U : 0U) == ESP_OK; +} + +bool UnPhoneFeatures::setBacklightPower(bool on) const { + assert(ioExpander != nullptr); + return esp_io_expander_set_level(ioExpander, expanderpin::BACKLIGHT, on ? 1U : 0U) == ESP_OK; +} + +bool UnPhoneFeatures::getBacklightPower(bool& on) const { + assert(ioExpander != nullptr); + uint32_t level_mask; + if (esp_io_expander_get_level(ioExpander, expanderpin::BACKLIGHT, &level_mask) == ESP_OK) { + on = level_mask != 0U; + return true; + } else { + return false; + } +} + +bool UnPhoneFeatures::setIrPower(bool on) const { + assert(ioExpander != nullptr); + return gpio_set_level(pin::IR_LEDS, on ? 1U : 0U) == ESP_OK; +} + +bool UnPhoneFeatures::setVibePower(bool on) const { + assert(ioExpander != nullptr); + return esp_io_expander_set_level(ioExpander, expanderpin::VIBE, on ? 1U : 0U) == ESP_OK; +} + +bool UnPhoneFeatures::setExpanderPower(bool on) const { + assert(ioExpander != nullptr); + return esp_io_expander_set_level(ioExpander, expanderpin::EXPANDER_POWER, on ? 1U : 0U) == ESP_OK; +} + +bool UnPhoneFeatures::isPowerSwitchOn() const { + return gpio_get_level(pin::POWER_SWITCH) > 0; +} + +void UnPhoneFeatures::turnPeripheralsOff() const { + setExpanderPower(false); + setBacklightPower(false); + setIrPower(false); + setRgbLed(false, false, false); + setVibePower(false); +} + +bool UnPhoneFeatures::setShipping(bool on) const { + if (on) { + TT_LOG_W(TAG, "setShipping: on"); + uint8_t mask = (1 << 4) | (1 << 5); + // REG05[5:4] = 00 + batteryManagement.setWatchDogBitOff(mask); + // Set bit 5 to disable + batteryManagement.setOperationControlBitOn(1 << 5); + } else { + TT_LOG_W(TAG, "setShipping: off"); + // REG05[5:4] = 01 + batteryManagement.setWatchDogBitOff(1 << 5); + batteryManagement.setWatchDogBitOn(1 << 4); + // Clear bit 5 to enable + batteryManagement.setOperationControlBitOff(1 << 5); + } + return true; +} + +void UnPhoneFeatures::wakeOnPowerSwitch() const { + esp_sleep_enable_ext0_wakeup(pin::POWER_SWITCH, 1); +} + +bool UnPhoneFeatures::isUsbPowerConnected() const { + uint8_t status; + if (batteryManagement.getStatus(status)) { + return (status & 4U) != 0U; + } else { + return false; + } +} diff --git a/Boards/UnPhone/Source/UnPhoneFeatures.h b/Boards/UnPhone/Source/UnPhoneFeatures.h new file mode 100644 index 00000000..cfd591c6 --- /dev/null +++ b/Boards/UnPhone/Source/UnPhoneFeatures.h @@ -0,0 +1,51 @@ +#pragma once + +#include "Thread.h" +#include "bq24295/Bq24295.h" +#include + +/** + * Easy access to GPIO pins + */ +class UnPhoneFeatures { + +private: + + esp_io_expander_handle_t ioExpander = nullptr; + Bq24295 batteryManagement = Bq24295(I2C_NUM_0); + tt::Thread buttonHandlingThread; + bool buttonHandlingThreadInterruptRequest = false; + + bool initNavButtons(); + static bool initOutputPins(); + static bool initPowerSwitch(); + bool initGpioExpander(); + +public: + + UnPhoneFeatures() = default; + ~UnPhoneFeatures(); + + bool init(); + + bool setBacklightPower(bool on) const; + bool getBacklightPower(bool& on) const; + bool setIrPower(bool on) const; + bool setVibePower(bool on) const; + bool setExpanderPower(bool on) const; + + bool isPowerSwitchOn() const; + + void turnPeripheralsOff() const; + + /** Battery management (BQ24295) will stop supplying power until USB connects */ + bool setShipping(bool on) const; + + void wakeOnPowerSwitch() const; + + bool isUsbPowerConnected() const; + + bool setRgbLed(bool red, bool green, bool blue) const; + + void printInfo() const; +}; diff --git a/Boards/UnPhone/Source/bq24295/Bq24295.cpp b/Boards/UnPhone/Source/bq24295/Bq24295.cpp new file mode 100644 index 00000000..a2ca7c5c --- /dev/null +++ b/Boards/UnPhone/Source/bq24295/Bq24295.cpp @@ -0,0 +1,64 @@ +#include "Bq24295.h" +#include "Log.h" + +#define TAG "bq24295" + +/** Reference: https://gitlab.com/hamishcunningham/unphonelibrary/-/blob/main/unPhone.h?ref_type=heads */ +namespace registers { + static const uint8_t WATCHDOG = 0x05U; // Charge end/timer cntrl + static const uint8_t OPERATION_CONTROL = 0x07U; // Misc operation control + static const uint8_t STATUS = 0x08U; // System status + static const uint8_t VERSION = 0x0AU; // Vendor/part/revision status +} // namespace registers + +// region Watchdog +bool Bq24295::getWatchDog(uint8_t value) const { + return readRegister8(registers::WATCHDOG, value); +} + +bool Bq24295::setWatchDogBitOn(uint8_t mask) const { + return bitOn(registers::WATCHDOG, mask); +} + +bool Bq24295::setWatchDogBitOff(uint8_t mask) const { + return bitOff(registers::WATCHDOG, mask); +} + +// endregoin + +// region Operation Control + +bool Bq24295::getOperationControl(uint8_t value) const { + return readRegister8(registers::OPERATION_CONTROL, value); +} + +bool Bq24295::setOperationControlBitOn(uint8_t mask) const { + return bitOn(registers::OPERATION_CONTROL, mask); +} + +bool Bq24295::setOperationControlBitOff(uint8_t mask) const { + return bitOff(registers::OPERATION_CONTROL, mask); +} + +// endregion + +// region Other + +bool Bq24295::getStatus(uint8_t& value) const { + return readRegister8(registers::STATUS, value); +} + +bool Bq24295::getVersion(uint8_t& value) const { + return readRegister8(registers::VERSION, value); +} + +void Bq24295::printInfo() const { + uint8_t version, status; + if (getStatus(status) && getVersion(version)) { + TT_LOG_I(TAG, "Version %d, status %02x", version, status); + } else { + TT_LOG_E(TAG, "Failed to retrieve version and/or status"); + } +} + +// endregion \ No newline at end of file diff --git a/Boards/UnPhone/Source/bq24295/Bq24295.h b/Boards/UnPhone/Source/bq24295/Bq24295.h new file mode 100644 index 00000000..7f825b39 --- /dev/null +++ b/Boards/UnPhone/Source/bq24295/Bq24295.h @@ -0,0 +1,25 @@ +#pragma once + +#include "hal/i2c/I2cDevice.h" + +#define BQ24295_ADDRESS 0x6BU + +class Bq24295 : I2cDevice { + +public: + + explicit Bq24295(i2c_port_t port) : I2cDevice(port, BQ24295_ADDRESS) {} + + bool getWatchDog(uint8_t value) const; + bool setWatchDogBitOn(uint8_t mask) const; + bool setWatchDogBitOff(uint8_t mask) const; + + bool getOperationControl(uint8_t value) const; + bool setOperationControlBitOn(uint8_t mask) const; + bool setOperationControlBitOff(uint8_t mask) const; + + bool getStatus(uint8_t& value) const; + bool getVersion(uint8_t& value) const; + + void printInfo() const; +}; diff --git a/Boards/UnPhone/Source/hal/UnPhoneDisplay.cpp b/Boards/UnPhone/Source/hal/UnPhoneDisplay.cpp new file mode 100644 index 00000000..eed9ca32 --- /dev/null +++ b/Boards/UnPhone/Source/hal/UnPhoneDisplay.cpp @@ -0,0 +1,73 @@ +#include "UnPhoneDisplay.h" +#include "UnPhoneDisplayConstants.h" +#include "UnPhoneTouch.h" +#include "Log.h" + +#include + +#include "UnPhoneFeatures.h" +#include "esp_err.h" +#include "hx8357/disp_spi.h" +#include "hx8357/hx8357.h" + +#define TAG "unphone_display" +#define BUFFER_SIZE (UNPHONE_LCD_HORIZONTAL_RESOLUTION * UNPHONE_LCD_DRAW_BUFFER_HEIGHT * LV_COLOR_DEPTH / 8) + +extern UnPhoneFeatures unPhoneFeatures; + +bool UnPhoneDisplay::start() { + TT_LOG_I(TAG, "Starting"); + + disp_spi_add_device(SPI2_HOST); + + hx8357_reset(GPIO_NUM_46); + hx8357_init(UNPHONE_LCD_PIN_DC); + uint8_t madctl = (1U << MADCTL_BIT_INDEX_COLUMN_ADDRESS_ORDER); + hx8357_set_madctl(madctl); + + displayHandle = lv_display_create(UNPHONE_LCD_HORIZONTAL_RESOLUTION, UNPHONE_LCD_VERTICAL_RESOLUTION); + lv_display_set_physical_resolution(displayHandle, UNPHONE_LCD_HORIZONTAL_RESOLUTION, UNPHONE_LCD_VERTICAL_RESOLUTION); + lv_display_set_color_format(displayHandle, LV_COLOR_FORMAT_NATIVE); + + // TODO malloc to use SPIRAM + static auto* buffer1 = (uint8_t*)heap_caps_malloc(BUFFER_SIZE, MALLOC_CAP_SPIRAM); + static auto* buffer2 = (uint8_t*)heap_caps_malloc(BUFFER_SIZE, MALLOC_CAP_SPIRAM); + assert(buffer1 != nullptr); + assert(buffer2 != nullptr); + + lv_display_set_buffers( + displayHandle, + buffer1, + buffer2, + BUFFER_SIZE, + LV_DISPLAY_RENDER_MODE_PARTIAL + ); + + lv_display_set_flush_cb(displayHandle, hx8357_flush); + + if (displayHandle != nullptr) { + TT_LOG_I(TAG, "Finished"); + unPhoneFeatures.setBacklightPower(true); + return true; + } else { + TT_LOG_I(TAG, "Failed"); + return false; + } +} + +bool UnPhoneDisplay::stop() { + tt_assert(displayHandle != nullptr); + + lv_display_delete(displayHandle); + displayHandle = nullptr; + + return true; +} + +tt::hal::Touch* _Nullable UnPhoneDisplay::createTouch() { + return static_cast(new UnPhoneTouch()); +} + +tt::hal::Display* createDisplay() { + return static_cast(new UnPhoneDisplay()); +} diff --git a/Boards/UnPhone/Source/hal/UnPhoneDisplay.h b/Boards/UnPhone/Source/hal/UnPhoneDisplay.h new file mode 100644 index 00000000..0ecdff24 --- /dev/null +++ b/Boards/UnPhone/Source/hal/UnPhoneDisplay.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include "lvgl.h" +#include "hal/Display.h" + +extern lv_disp_t* displayHandle; + +class UnPhoneDisplay : public tt::hal::Display { + +private: + + lv_display_t* displayHandle = nullptr; + +public: + + bool start() override; + + bool stop() override; + + tt::hal::Touch* _Nullable createTouch() override; + + lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } +}; + +tt::hal::Display* createDisplay(); diff --git a/Boards/UnPhone/Source/hal/UnPhoneDisplayConstants.h b/Boards/UnPhone/Source/hal/UnPhoneDisplayConstants.h new file mode 100644 index 00000000..3c3b34b7 --- /dev/null +++ b/Boards/UnPhone/Source/hal/UnPhoneDisplayConstants.h @@ -0,0 +1,11 @@ +#pragma once + +#define UNPHONE_LCD_SPI_HOST SPI2_HOST +#define UNPHONE_LCD_PIN_CS GPIO_NUM_48 +#define UNPHONE_LCD_PIN_DC GPIO_NUM_47 +#define UNPHONE_LCD_PIN_RESET GPIO_NUM_46 +#define UNPHONE_LCD_SPI_FREQUENCY 27000000 +#define UNPHONE_LCD_HORIZONTAL_RESOLUTION 320 +#define UNPHONE_LCD_VERTICAL_RESOLUTION 480 +#define UNPHONE_LCD_DRAW_BUFFER_HEIGHT (UNPHONE_LCD_VERTICAL_RESOLUTION / 15) +#define UNPHONE_LCD_SPI_TRANSFER_HEIGHT (UNPHONE_LCD_VERTICAL_RESOLUTION / 15) diff --git a/Boards/UnPhone/Source/hal/UnPhoneSdCard.cpp b/Boards/UnPhone/Source/hal/UnPhoneSdCard.cpp new file mode 100644 index 00000000..bbf6b82b --- /dev/null +++ b/Boards/UnPhone/Source/hal/UnPhoneSdCard.cpp @@ -0,0 +1,35 @@ +#include "UnPhoneSdCard.h" + +#include "lvgl/LvglSync.h" +#include "hal/SpiSdCard.h" + +#include + +#define UNPHONE_SDCARD_SPI_FREQUENCY 20000000U +#define UNPHONE_SDCARD_PIN_CS GPIO_NUM_43 +#define UNPHONE_LCD_PIN_CS GPIO_NUM_48 +#define UNPHONE_LORA_PIN_CS GPIO_NUM_44 +#define UNPHONE_TOUCH_PIN_CS GPIO_NUM_38 + +std::shared_ptr createUnPhoneSdCard() { + auto* configuration = new tt::hal::SpiSdCard::Config( + UNPHONE_SDCARD_SPI_FREQUENCY, + UNPHONE_SDCARD_PIN_CS, + GPIO_NUM_NC, + GPIO_NUM_NC, + GPIO_NUM_NC, + SdCard::MountBehaviour::AtBoot, + tt::lvgl::getLvglSyncLockable(), + { + UNPHONE_LORA_PIN_CS, + UNPHONE_LCD_PIN_CS, + UNPHONE_TOUCH_PIN_CS + } + ); + + auto* sdcard = (SdCard*) new SpiSdCard( + std::unique_ptr(configuration) + ); + + return std::shared_ptr(sdcard); +} diff --git a/Boards/UnPhone/Source/hal/UnPhoneSdCard.h b/Boards/UnPhone/Source/hal/UnPhoneSdCard.h new file mode 100644 index 00000000..372888c0 --- /dev/null +++ b/Boards/UnPhone/Source/hal/UnPhoneSdCard.h @@ -0,0 +1,7 @@ +#pragma once + +#include "hal/SdCard.h" + +using namespace tt::hal; + +std::shared_ptr createUnPhoneSdCard(); diff --git a/Boards/UnPhone/Source/hal/UnPhoneTouch.cpp b/Boards/UnPhone/Source/hal/UnPhoneTouch.cpp new file mode 100644 index 00000000..3a6c1f54 --- /dev/null +++ b/Boards/UnPhone/Source/hal/UnPhoneTouch.cpp @@ -0,0 +1,83 @@ +#include "UnPhoneTouch.h" + +#include "esp_err.h" +#include "Log.h" +#include "esp_lvgl_port.h" +#include "esp_lcd_touch_xpt2046.h" + +#define TAG "unphone_touch" + +#define UNPHONE_TOUCH_X_MAX 320 +#define UNPHONE_TOUCH_Y_MAX 480 + +bool UnPhoneTouch::start(lv_display_t* display) { + const esp_lcd_panel_io_spi_config_t io_config = ESP_LCD_TOUCH_IO_SPI_XPT2046_CONFIG(GPIO_NUM_38); + + if (esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &ioHandle) != ESP_OK) { + TT_LOG_E(TAG, "Touch IO SPI creation failed"); + return false; + } + + esp_lcd_touch_config_t config = { + .x_max = UNPHONE_TOUCH_X_MAX, + .y_max = UNPHONE_TOUCH_Y_MAX, + .rst_gpio_num = GPIO_NUM_NC, + .int_gpio_num = GPIO_NUM_NC, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 0, + .mirror_x = 0, + .mirror_y = 0, + }, + .process_coordinates = nullptr, + .interrupt_callback = nullptr, + .user_data = nullptr, + .driver_data = nullptr + }; + + if (esp_lcd_touch_new_spi_xpt2046(ioHandle, &config, &touchHandle) != ESP_OK) { + TT_LOG_E(TAG, "XPT2046 driver init failed"); + cleanup(); + return false; + } + + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = display, + .handle = touchHandle, + }; + + TT_LOG_I(TAG, "Adding touch to LVGL"); + deviceHandle = lvgl_port_add_touch(&touch_cfg); + if (deviceHandle == nullptr) { + TT_LOG_E(TAG, "Adding touch failed"); + cleanup(); + return false; + } + + return true; +} + +bool UnPhoneTouch::stop() { + cleanup(); + return true; +} + +void UnPhoneTouch::cleanup() { + if (deviceHandle != nullptr) { + lv_indev_delete(deviceHandle); + deviceHandle = nullptr; + } + + if (touchHandle != nullptr) { + esp_lcd_touch_del(touchHandle); + touchHandle = nullptr; + } + + if (ioHandle != nullptr) { + esp_lcd_panel_io_del(ioHandle); + ioHandle = nullptr; + } +} diff --git a/Boards/UnPhone/Source/hal/UnPhoneTouch.h b/Boards/UnPhone/Source/hal/UnPhoneTouch.h new file mode 100644 index 00000000..3fa85d82 --- /dev/null +++ b/Boards/UnPhone/Source/hal/UnPhoneTouch.h @@ -0,0 +1,18 @@ +#pragma once + +#include "hal/Touch.h" +#include "TactilityCore.h" +#include "esp_lcd_panel_io_interface.h" +#include "esp_lcd_touch.h" + +class UnPhoneTouch : public tt::hal::Touch { +private: + esp_lcd_panel_io_handle_t _Nullable ioHandle = nullptr; + esp_lcd_touch_handle_t _Nullable touchHandle = nullptr; + lv_indev_t* _Nullable deviceHandle = nullptr; + void cleanup(); +public: + bool start(lv_display_t* display) override; + bool stop() override; + lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } +}; diff --git a/Boards/UnPhone/Source/hx8357/README.md b/Boards/UnPhone/Source/hx8357/README.md new file mode 100644 index 00000000..c580f6ce --- /dev/null +++ b/Boards/UnPhone/Source/hx8357/README.md @@ -0,0 +1,4 @@ +The files in this folder are from https://github.com/lvgl/lvgl_esp32_drivers +The original license is an MIT license: https://github.com/lvgl/lvgl_esp32_drivers/blob/master/LICENSE + +You may use the files in this folder under the original license, or under GPL v3 from the main Tactility project. \ No newline at end of file diff --git a/Boards/UnPhone/Source/hx8357/disp_spi.c b/Boards/UnPhone/Source/hx8357/disp_spi.c new file mode 100644 index 00000000..36c11239 --- /dev/null +++ b/Boards/UnPhone/Source/hx8357/disp_spi.c @@ -0,0 +1,315 @@ +#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration + +/** + * @file disp_spi.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "esp_system.h" +#include "driver/gpio.h" +#include "driver/spi_master.h" +#include "esp_log.h" + +#define TAG "disp_spi" + +#include + +#include +#include +#include + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#include "disp_spi.h" +//#include "disp_driver.h" + +//#include "../lvgl_helpers.h" +#include "../lvgl_spi_conf.h" + +/****************************************************************************** + * Notes about DMA spi_transaction_ext_t structure pooling + * + * An xQueue is used to hold a pool of reusable SPI spi_transaction_ext_t + * structures that get used for all DMA SPI transactions. While an xQueue may + * seem like overkill it is an already built-in RTOS feature that comes at + * little cost. xQueues are also ISR safe if it ever becomes necessary to + * access the pool in the ISR callback. + * + * When a DMA request is sent, a transaction structure is removed from the + * pool, filled out, and passed off to the esp32 SPI driver. Later, when + * servicing pending SPI transaction results, the transaction structure is + * recycled back into the pool for later reuse. This matches the DMA SPI + * transaction life cycle requirements of the esp32 SPI driver. + * + * When polling or synchronously sending SPI requests, and as required by the + * esp32 SPI driver, all pending DMA transactions are first serviced. Then the + * polling SPI request takes place. + * + * When sending an asynchronous DMA SPI request, if the pool is empty, some + * small percentage of pending transactions are first serviced before sending + * any new DMA SPI transactions. Not too many and not too few as this balance + * controls DMA transaction latency. + * + * It is therefore not the design that all pending transactions must be + * serviced and placed back into the pool with DMA SPI requests - that + * will happen eventually. The pool just needs to contain enough to float some + * number of in-flight SPI requests to speed up the overall DMA SPI data rate + * and reduce transaction latency. If however a display driver uses some + * polling SPI requests or calls disp_wait_for_pending_transactions() directly, + * the pool will reach the full state more often and speed up DMA queuing. + * + *****************************************************************************/ + +/********************* + * DEFINES + *********************/ +#define SPI_TRANSACTION_POOL_SIZE 50 /* maximum number of DMA transactions simultaneously in-flight */ + +/* DMA Transactions to reserve before queueing additional DMA transactions. A 1/10th seems to be a good balance. Too many (or all) and it will increase latency. */ +#define SPI_TRANSACTION_POOL_RESERVE_PERCENTAGE 10 +#if SPI_TRANSACTION_POOL_SIZE >= SPI_TRANSACTION_POOL_RESERVE_PERCENTAGE +#define SPI_TRANSACTION_POOL_RESERVE (SPI_TRANSACTION_POOL_SIZE / SPI_TRANSACTION_POOL_RESERVE_PERCENTAGE) +#else +#define SPI_TRANSACTION_POOL_RESERVE 1 /* defines minimum size */ +#endif + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void IRAM_ATTR spi_ready (spi_transaction_t *trans); + +/********************** + * STATIC VARIABLES + **********************/ +static spi_host_device_t spi_host; +static spi_device_handle_t spi; +static QueueHandle_t TransactionPool = NULL; +static transaction_cb_t chained_post_cb; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ +void disp_spi_add_device_config(spi_host_device_t host, spi_device_interface_config_t *devcfg) +{ + spi_host=host; + chained_post_cb=devcfg->post_cb; + devcfg->post_cb=spi_ready; + esp_err_t ret=spi_bus_add_device(host, devcfg, &spi); + assert(ret==ESP_OK); +} + +void disp_spi_add_device(spi_host_device_t host) +{ + disp_spi_add_device_with_speed(host, SPI_TFT_CLOCK_SPEED_HZ); +} + +void disp_spi_add_device_with_speed(spi_host_device_t host, int clock_speed_hz) +{ + ESP_LOGI(TAG, "Adding SPI device"); + ESP_LOGI(TAG, "Clock speed: %dHz, mode: %d, CS pin: %d", + clock_speed_hz, SPI_TFT_SPI_MODE, DISP_SPI_CS); + + spi_device_interface_config_t devcfg={ + .clock_speed_hz = clock_speed_hz, + .mode = SPI_TFT_SPI_MODE, + .spics_io_num=DISP_SPI_CS, // CS pin + .input_delay_ns=DISP_SPI_INPUT_DELAY_NS, + .queue_size=SPI_TRANSACTION_POOL_SIZE, + .pre_cb=NULL, + .post_cb=NULL, +#if defined(DISP_SPI_HALF_DUPLEX) + .flags = SPI_DEVICE_NO_DUMMY | SPI_DEVICE_HALFDUPLEX, /* dummy bits should be explicitly handled via DISP_SPI_VARIABLE_DUMMY as needed */ +#else + #if defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_FT81X) + .flags = 0, + #elif defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_RA8875) + .flags = SPI_DEVICE_NO_DUMMY, + #endif +#endif + }; + + disp_spi_add_device_config(host, &devcfg); + + /* create the transaction pool and fill it with ptrs to spi_transaction_ext_t to reuse */ + if(TransactionPool == NULL) { + TransactionPool = xQueueCreate(SPI_TRANSACTION_POOL_SIZE, sizeof(spi_transaction_ext_t*)); + assert(TransactionPool != NULL); + for (size_t i = 0; i < SPI_TRANSACTION_POOL_SIZE; i++) + { + spi_transaction_ext_t* pTransaction = (spi_transaction_ext_t*)heap_caps_malloc(sizeof(spi_transaction_ext_t), MALLOC_CAP_DMA); + assert(pTransaction != NULL); + memset(pTransaction, 0, sizeof(spi_transaction_ext_t)); + xQueueSend(TransactionPool, &pTransaction, portMAX_DELAY); + } + } +} + +void disp_spi_change_device_speed(int clock_speed_hz) +{ + if (clock_speed_hz <= 0) { + clock_speed_hz = SPI_TFT_CLOCK_SPEED_HZ; + } + ESP_LOGI(TAG, "Changing SPI device clock speed: %d", clock_speed_hz); + disp_spi_remove_device(); + disp_spi_add_device_with_speed(spi_host, clock_speed_hz); +} + +void disp_spi_remove_device() +{ + /* Wait for previous pending transaction results */ + disp_wait_for_pending_transactions(); + + esp_err_t ret=spi_bus_remove_device(spi); + assert(ret==ESP_OK); +} + +void disp_spi_transaction(const uint8_t *data, size_t length, + int flags, uint8_t *out, + uint64_t addr, uint8_t dummy_bits) +{ + if (0 == length) { + return; + } + + spi_transaction_ext_t t = {0}; + + /* transaction length is in bits */ + t.base.length = length * 8; + + if (length <= 4 && data != NULL) { + t.base.flags = SPI_TRANS_USE_TXDATA; + memcpy(t.base.tx_data, data, length); + } else { + t.base.tx_buffer = data; + } + + if (flags & DISP_SPI_RECEIVE) { + assert(out != NULL && (flags & (DISP_SPI_SEND_POLLING | DISP_SPI_SEND_SYNCHRONOUS))); + t.base.rx_buffer = out; + +#if defined(DISP_SPI_HALF_DUPLEX) + t.base.rxlength = t.base.length; + t.base.length = 0; /* no MOSI phase in half-duplex reads */ +#else + t.base.rxlength = 0; /* in full-duplex mode, zero means same as tx length */ +#endif + } + + if (flags & DISP_SPI_ADDRESS_8) { + t.address_bits = 8; + } else if (flags & DISP_SPI_ADDRESS_16) { + t.address_bits = 16; + } else if (flags & DISP_SPI_ADDRESS_24) { + t.address_bits = 24; + } else if (flags & DISP_SPI_ADDRESS_32) { + t.address_bits = 32; + } + if (t.address_bits) { + t.base.addr = addr; + t.base.flags |= SPI_TRANS_VARIABLE_ADDR; + } + +#if defined(DISP_SPI_HALF_DUPLEX) + if (flags & DISP_SPI_MODE_DIO) { + t.base.flags |= SPI_TRANS_MODE_DIO; + } else if (flags & DISP_SPI_MODE_QIO) { + t.base.flags |= SPI_TRANS_MODE_QIO; + } + + if (flags & DISP_SPI_MODE_DIOQIO_ADDR) { + t.base.flags |= SPI_TRANS_MODE_DIOQIO_ADDR; + } + + if ((flags & DISP_SPI_VARIABLE_DUMMY) && dummy_bits) { + t.dummy_bits = dummy_bits; + t.base.flags |= SPI_TRANS_VARIABLE_DUMMY; + } +#endif + + /* Save flags for pre/post transaction processing */ + t.base.user = (void *) flags; + + /* Poll/Complete/Queue transaction */ + if (flags & DISP_SPI_SEND_POLLING) { + disp_wait_for_pending_transactions(); /* before polling, all previous pending transactions need to be serviced */ + spi_device_polling_transmit(spi, (spi_transaction_t *) &t); + } else if (flags & DISP_SPI_SEND_SYNCHRONOUS) { + disp_wait_for_pending_transactions(); /* before synchronous queueing, all previous pending transactions need to be serviced */ + spi_device_transmit(spi, (spi_transaction_t *) &t); + } else { + + /* if necessary, ensure we can queue new transactions by servicing some previous transactions */ + if(uxQueueMessagesWaiting(TransactionPool) == 0) { + spi_transaction_t *presult; + while(uxQueueMessagesWaiting(TransactionPool) < SPI_TRANSACTION_POOL_RESERVE) { + if (spi_device_get_trans_result(spi, &presult, 1) == ESP_OK) { + xQueueSend(TransactionPool, &presult, portMAX_DELAY); /* back to the pool to be reused */ + } + } + } + + spi_transaction_ext_t *pTransaction = NULL; + xQueueReceive(TransactionPool, &pTransaction, portMAX_DELAY); + memcpy(pTransaction, &t, sizeof(t)); + if (spi_device_queue_trans(spi, (spi_transaction_t *) pTransaction, portMAX_DELAY) != ESP_OK) { + xQueueSend(TransactionPool, &pTransaction, portMAX_DELAY); /* send failed transaction back to the pool to be reused */ + } + } +} + + +void disp_wait_for_pending_transactions(void) +{ + spi_transaction_t *presult; + + while(uxQueueMessagesWaiting(TransactionPool) < SPI_TRANSACTION_POOL_SIZE) { /* service until the transaction reuse pool is full again */ + if (spi_device_get_trans_result(spi, &presult, 1) == ESP_OK) { + xQueueSend(TransactionPool, &presult, portMAX_DELAY); + } + } +} + +void disp_spi_acquire(void) +{ + esp_err_t ret = spi_device_acquire_bus(spi, portMAX_DELAY); + assert(ret == ESP_OK); +} + +void disp_spi_release(void) +{ + spi_device_release_bus(spi); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void IRAM_ATTR spi_ready(spi_transaction_t *trans) +{ + disp_spi_send_flag_t flags = (disp_spi_send_flag_t) trans->user; + + if (flags & DISP_SPI_SIGNAL_FLUSH) { + lv_disp_t* disp = lv_refr_get_disp_refreshing(); + lv_disp_flush_ready(disp); + } + + if (chained_post_cb) { + chained_post_cb(trans); + } +} + diff --git a/Boards/UnPhone/Source/hx8357/disp_spi.h b/Boards/UnPhone/Source/hx8357/disp_spi.h new file mode 100644 index 00000000..453946cc --- /dev/null +++ b/Boards/UnPhone/Source/hx8357/disp_spi.h @@ -0,0 +1,81 @@ +/** + * @file disp_spi.h + * + */ + +#ifndef DISP_SPI_H +#define DISP_SPI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include +#include +#include + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef enum _disp_spi_send_flag_t { + DISP_SPI_SEND_QUEUED = 0x00000000, + DISP_SPI_SEND_POLLING = 0x00000001, + DISP_SPI_SEND_SYNCHRONOUS = 0x00000002, + DISP_SPI_SIGNAL_FLUSH = 0x00000004, + DISP_SPI_RECEIVE = 0x00000008, + DISP_SPI_CMD_8 = 0x00000010, /* Reserved */ + DISP_SPI_CMD_16 = 0x00000020, /* Reserved */ + DISP_SPI_ADDRESS_8 = 0x00000040, + DISP_SPI_ADDRESS_16 = 0x00000080, + DISP_SPI_ADDRESS_24 = 0x00000100, + DISP_SPI_ADDRESS_32 = 0x00000200, + DISP_SPI_MODE_DIO = 0x00000400, + DISP_SPI_MODE_QIO = 0x00000800, + DISP_SPI_MODE_DIOQIO_ADDR = 0x00001000, + DISP_SPI_VARIABLE_DUMMY = 0x00002000, +} disp_spi_send_flag_t; + + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void disp_spi_add_device(spi_host_device_t host); +void disp_spi_add_device_config(spi_host_device_t host, spi_device_interface_config_t *devcfg); +void disp_spi_add_device_with_speed(spi_host_device_t host, int clock_speed_hz); +void disp_spi_change_device_speed(int clock_speed_hz); +void disp_spi_remove_device(); + +/* Important! + All buffers should also be 32-bit aligned and DMA capable to prevent extra allocations and copying. + When DMA reading (even in polling mode) the ESP32 always read in 4-byte chunks even if less is requested. + Extra space will be zero filled. Always ensure the out buffer is large enough to hold at least 4 bytes! +*/ +void disp_spi_transaction(const uint8_t *data, size_t length, + int flags, uint8_t *out, uint64_t addr, uint8_t dummy_bits); + +void disp_wait_for_pending_transactions(void); +void disp_spi_acquire(void); +void disp_spi_release(void); + +static inline void disp_spi_send_data(uint8_t *data, size_t length) { + disp_spi_transaction(data, length, DISP_SPI_SEND_POLLING, NULL, 0, 0); +} + +static inline void disp_spi_send_colors(uint8_t *data, size_t length) { + disp_spi_transaction(data, length, + DISP_SPI_SEND_QUEUED | DISP_SPI_SIGNAL_FLUSH, + NULL, 0, 0); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*DISP_SPI_H*/ diff --git a/Boards/UnPhone/Source/hx8357/hx8357.c b/Boards/UnPhone/Source/hx8357/hx8357.c new file mode 100644 index 00000000..51375ef3 --- /dev/null +++ b/Boards/UnPhone/Source/hx8357/hx8357.c @@ -0,0 +1,292 @@ +/** + * @file HX8357.c + * + * Roughly based on the Adafruit_HX8357_Library + * + * This library should work with: + * Adafruit 3.5" TFT 320x480 + Touchscreen Breakout + * http://www.adafruit.com/products/2050 + * + * Adafruit TFT FeatherWing - 3.5" 480x320 Touchscreen for Feathers + * https://www.adafruit.com/product/3651 + * + */ + +/********************* + * INCLUDES + *********************/ +#include "hx8357.h" +#include "disp_spi.h" +#include "driver/gpio.h" +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +/********************* + * DEFINES + *********************/ + +#define TAG "HX8357" + +/********************** + * TYPEDEFS + **********************/ + +static gpio_num_t dcPin = GPIO_NUM_NC; + +/*The LCD needs a bunch of command/argument values to be initialized. They are stored in this struct. */ +typedef struct { + uint8_t cmd; + uint8_t data[16]; + uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds. +} lcd_init_cmd_t; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void hx8357_send_cmd(uint8_t cmd); +static void hx8357_send_data(void * data, uint16_t length); +static void hx8357_send_color(void * data, uint16_t length); + + +/********************** + * INITIALIZATION ARRAYS + **********************/ +// Taken from the Adafruit driver +static const uint8_t + initb[] = { + HX8357B_SETPOWER, 3, + 0x44, 0x41, 0x06, + HX8357B_SETVCOM, 2, + 0x40, 0x10, + HX8357B_SETPWRNORMAL, 2, + 0x05, 0x12, + HX8357B_SET_PANEL_DRIVING, 5, + 0x14, 0x3b, 0x00, 0x02, 0x11, + HX8357B_SETDISPLAYFRAME, 1, + 0x0c, // 6.8mhz + HX8357B_SETPANELRELATED, 1, + 0x01, // BGR + 0xEA, 3, // seq_undefined1, 3 args + 0x03, 0x00, 0x00, + 0xEB, 4, // undef2, 4 args + 0x40, 0x54, 0x26, 0xdb, + HX8357B_SETGAMMA, 12, + 0x00, 0x15, 0x00, 0x22, 0x00, 0x08, 0x77, 0x26, 0x66, 0x22, 0x04, 0x00, + HX8357_MADCTL, 1, + 0xC0, + HX8357_COLMOD, 1, + 0x55, + HX8357_PASET, 4, + 0x00, 0x00, 0x01, 0xDF, + HX8357_CASET, 4, + 0x00, 0x00, 0x01, 0x3F, + HX8357B_SETDISPMODE, 1, + 0x00, // CPU (DBI) and internal oscillation ?? + HX8357_SLPOUT, 0x80 + 120/5, // Exit sleep, then delay 120 ms + HX8357_DISPON, 0x80 + 10/5, // Main screen turn on, delay 10 ms + 0 // END OF COMMAND LIST + }, initd[] = { + HX8357_SWRESET, 0x80 + 100/5, // Soft reset, then delay 10 ms + HX8357D_SETC, 3, + 0xFF, 0x83, 0x57, + 0xFF, 0x80 + 500/5, // No command, just delay 300 ms + HX8357_SETRGB, 4, + 0x80, 0x00, 0x06, 0x06, // 0x80 enables SDO pin (0x00 disables) + HX8357D_SETCOM, 1, + 0x25, // -1.52V + HX8357_SETOSC, 1, + 0x68, // Normal mode 70Hz, Idle mode 55 Hz + HX8357_SETPANEL, 1, + 0x05, // BGR, Gate direction swapped + HX8357_SETPWR1, 6, + 0x00, // Not deep standby + 0x15, // BT + 0x1C, // VSPR + 0x1C, // VSNR + 0x83, // AP + 0xAA, // FS + HX8357D_SETSTBA, 6, + 0x50, // OPON normal + 0x50, // OPON idle + 0x01, // STBA + 0x3C, // STBA + 0x1E, // STBA + 0x08, // GEN + HX8357D_SETCYC, 7, + 0x02, // NW 0x02 + 0x40, // RTN + 0x00, // DIV + 0x2A, // DUM + 0x2A, // DUM + 0x0D, // GDON + 0x78, // GDOFF + HX8357D_SETGAMMA, 34, + 0x02, 0x0A, 0x11, 0x1d, 0x23, 0x35, 0x41, 0x4b, 0x4b, + 0x42, 0x3A, 0x27, 0x1B, 0x08, 0x09, 0x03, 0x02, 0x0A, + 0x11, 0x1d, 0x23, 0x35, 0x41, 0x4b, 0x4b, 0x42, 0x3A, + 0x27, 0x1B, 0x08, 0x09, 0x03, 0x00, 0x01, + HX8357_COLMOD, 1, + 0x57, // 0x55 = 16 bit, 0x57 = 24bit + HX8357_MADCTL, 1, + 0xC0, + HX8357_TEON, 1, + 0x00, // TW off + HX8357_TEARLINE, 2, + 0x00, 0x02, + HX8357_SLPOUT, 0x80 + 150/5, // Exit Sleep, then delay 150 ms + HX8357_DISPON, 0x80 + 50/5, // Main screen turn on, delay 50 ms + 0, // END OF COMMAND LIST + }; + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ +static uint8_t displayType = HX8357D; + +void hx8357_reset(gpio_num_t resetPin) { + if (resetPin != GPIO_NUM_NC) { + esp_rom_gpio_pad_select_gpio(resetPin); + gpio_set_direction(resetPin, GPIO_MODE_OUTPUT); + + //Reset the display + gpio_set_level(resetPin, 0); + vTaskDelay(10 / portTICK_PERIOD_MS); + gpio_set_level(resetPin, 1); + vTaskDelay(120 / portTICK_PERIOD_MS); + } +} + +void hx8357_init(gpio_num_t newDcPin) { + ESP_LOGI(TAG, "Initialization."); + + dcPin = newDcPin; + + //Initialize non-SPI GPIOs + esp_rom_gpio_pad_select_gpio(dcPin); + gpio_set_direction(dcPin, GPIO_MODE_OUTPUT); + + //Send all the commands + const uint8_t *addr = (displayType == HX8357B) ? initb : initd; + uint8_t cmd, x, numArgs; + while((cmd = *addr++) > 0) { // '0' command ends list + x = *addr++; + numArgs = x & 0x7F; + if (cmd != 0xFF) { // '255' is ignored + if (x & 0x80) { // If high bit set, numArgs is a delay time + hx8357_send_cmd(cmd); + } else { + hx8357_send_cmd(cmd); + hx8357_send_data((void *) addr, numArgs); + addr += numArgs; + } + } + if (x & 0x80) { // If high bit set... + vTaskDelay(numArgs * 5 / portTICK_PERIOD_MS); // numArgs is actually a delay time (5ms units) + } + } + +#if HX8357_INVERT_COLORS + hx8357_send_cmd(HX8357_INVON); +#else + hx8357_send_cmd(HX8357_INVOFF); +#endif +} + +//(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map); +void hx8357_flush(lv_disp_t* drv, const lv_area_t * area, uint8_t * color_map) +{ + uint32_t size = lv_area_get_width(area) * lv_area_get_height(area); + + /* Column addresses */ + uint8_t xb[] = { + (uint8_t) (area->x1 >> 8) & 0xFF, + (uint8_t) (area->x1) & 0xFF, + (uint8_t) (area->x2 >> 8) & 0xFF, + (uint8_t) (area->x2) & 0xFF, + }; + + /* Page addresses */ + uint8_t yb[] = { + (uint8_t) (area->y1 >> 8) & 0xFF, + (uint8_t) (area->y1) & 0xFF, + (uint8_t) (area->y2 >> 8) & 0xFF, + (uint8_t) (area->y2) & 0xFF, + }; + + /*Column addresses*/ + hx8357_send_cmd(HX8357_CASET); + hx8357_send_data(xb, 4); + + /*Page addresses*/ + hx8357_send_cmd(HX8357_PASET); + hx8357_send_data(yb, 4); + + /*Memory write*/ + hx8357_send_cmd(HX8357_RAMWR); + hx8357_send_color((void*)color_map, size * (LV_COLOR_DEPTH / 8)); +} + +void hx8357_set_madctl(uint8_t value) { + hx8357_send_cmd(HX8357_MADCTL); + hx8357_send_data(&value, 1); +} +/********************** + * STATIC FUNCTIONS + **********************/ + + +static void hx8357_send_cmd(uint8_t cmd) +{ + disp_wait_for_pending_transactions(); + gpio_set_level(dcPin, 0); /*Command mode*/ + disp_spi_send_data(&cmd, 1); +} + + +static void hx8357_send_data(void * data, uint16_t length) +{ + disp_wait_for_pending_transactions(); + gpio_set_level(dcPin, 1); /*Data mode*/ + disp_spi_send_data(data, length); +} + + +static void hx8357_send_color(void * data, uint16_t length) +{ + disp_wait_for_pending_transactions(); + gpio_set_level(dcPin, 1); /*Data mode*/ + disp_spi_send_colors(data, length); +} + +uint8_t hx8357d_get_gamma_curve_count() { + return 4; +} + +void hx8357d_set_gamme_curve(uint8_t index) { + uint8_t curve = 1; + switch (index) { + case 0: + curve = 0x01; + break; + case 1: + curve = 0x02; + break; + case 2: + curve = 0x04; + break; + case 3: + curve = 0x08; + break; + } + hx8357_send_cmd(HX8357D_SETGAMMA_BY_ID); + hx8357_send_data(&curve, 1); +} diff --git a/Boards/UnPhone/Source/hx8357/hx8357.h b/Boards/UnPhone/Source/hx8357/hx8357.h new file mode 100644 index 00000000..1a4d11ed --- /dev/null +++ b/Boards/UnPhone/Source/hx8357/hx8357.h @@ -0,0 +1,133 @@ +/** + * @file hx8357.h + * + * Roughly based on the Adafruit_HX8357_Library + * + * This library should work with: + * Adafruit 3.5" TFT 320x480 + Touchscreen Breakout + * http://www.adafruit.com/products/2050 + * + * Adafruit TFT FeatherWing - 3.5" 480x320 Touchscreen for Feathers + * https://www.adafruit.com/product/3651 + * + * Datasheet: + * https://cdn-shop.adafruit.com/datasheets/HX8357-D_DS_April2012.pdf + */ + +#ifndef HX8357_H +#define HX8357_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include +#include + +#include +#include + +#define HX8357D 0xD ///< Our internal const for D type +#define HX8357B 0xB ///< Our internal const for B type + +#define HX8357_TFTWIDTH 320 ///< 320 pixels wide +#define HX8357_TFTHEIGHT 480 ///< 480 pixels tall + +#define HX8357_NOP 0x00 ///< No op +#define HX8357_SWRESET 0x01 ///< software reset +#define HX8357_RDDID 0x04 ///< Read ID +#define HX8357_RDDST 0x09 ///< (unknown) + +#define HX8357_RDPOWMODE 0x0A ///< Read power mode Read power mode +#define HX8357_RDMADCTL 0x0B ///< Read MADCTL +#define HX8357_RDCOLMOD 0x0C ///< Column entry mode +#define HX8357_RDDIM 0x0D ///< Read display image mode +#define HX8357_RDDSDR 0x0F ///< Read dosplay signal mode + +#define HX8357_SLPIN 0x10 ///< Enter sleep mode +#define HX8357_SLPOUT 0x11 ///< Exit sleep mode +#define HX8357B_PTLON 0x12 ///< Partial mode on +#define HX8357B_NORON 0x13 ///< Normal mode + +#define HX8357_INVOFF 0x20 ///< Turn off invert +#define HX8357_INVON 0x21 ///< Turn on invert +#define HX8357_DISPOFF 0x28 ///< Display on +#define HX8357_DISPON 0x29 ///< Display off + +#define HX8357_CASET 0x2A ///< Column addr set +#define HX8357_PASET 0x2B ///< Page addr set +#define HX8357_RAMWR 0x2C ///< Write VRAM +#define HX8357_RAMRD 0x2E ///< Read VRAm + +#define HX8357B_PTLAR 0x30 ///< (unknown) +#define HX8357_TEON 0x35 ///< Tear enable on +#define HX8357_TEARLINE 0x44 ///< (unknown) +#define HX8357_MADCTL 0x36 ///< Memory access control +#define HX8357_COLMOD 0x3A ///< Color mode + +#define HX8357_SETOSC 0xB0 ///< Set oscillator +#define HX8357_SETPWR1 0xB1 ///< Set power control +#define HX8357B_SETDISPLAY 0xB2 ///< Set display mode +#define HX8357_SETRGB 0xB3 ///< Set RGB interface +#define HX8357D_SETCOM 0xB6 ///< Set VCOM voltage + +#define HX8357B_SETDISPMODE 0xB4 ///< Set display mode +#define HX8357D_SETCYC 0xB4 ///< Set display cycle reg +#define HX8357B_SETOTP 0xB7 ///< Set OTP memory +#define HX8357D_SETC 0xB9 ///< Enable extension command + +#define HX8357B_SET_PANEL_DRIVING 0xC0 ///< Set panel drive mode +#define HX8357D_SETSTBA 0xC0 ///< Set source option +#define HX8357B_SETDGC 0xC1 ///< Set DGC settings +#define HX8357B_SETID 0xC3 ///< Set ID +#define HX8357B_SETDDB 0xC4 ///< Set DDB +#define HX8357B_SETDISPLAYFRAME 0xC5 ///< Set display frame +#define HX8357B_GAMMASET 0xC8 ///< Set Gamma correction +#define HX8357B_SETCABC 0xC9 ///< Set CABC +#define HX8357_SETPANEL 0xCC ///< Set Panel + +#define HX8357B_SETPOWER 0xD0 ///< Set power control +#define HX8357B_SETVCOM 0xD1 ///< Set VCOM +#define HX8357B_SETPWRNORMAL 0xD2 ///< Set power normal + +#define HX8357B_RDID1 0xDA ///< Read ID #1 +#define HX8357B_RDID2 0xDB ///< Read ID #2 +#define HX8357B_RDID3 0xDC ///< Read ID #3 +#define HX8357B_RDID4 0xDD ///< Read ID #4 + +#define HX8357D_SETGAMMA 0xE0 ///< Set Gamma curve data +#define HX8357D_SETGAMMA_BY_ID 0x26 ///< Set Gamma curve by curve identifier (0x01, 0x02, 0x04, 0x08) +#define HX8357B_SETGAMMA 0xC8 ///< Set Gamma +#define HX8357B_SETPANELRELATED 0xE9 ///< Set panel related + +// MADCTL +// See datasheet page 123: https://cdn-shop.adafruit.com/datasheets/HX8357-D_DS_April2012.pdf +#define MADCTL_BIT_INDEX_COMMON_OUTPUTS_RAM 0 // N/A - set to 0 +#define MADCTL_BIT_INDEX_SEGMENT_OUTPUTS_RAM 1 // N/A - set to 0 +#define MADCTL_BIT_INDEX_DATA_LATCH_ORDER 2 // 0 = left-to-right refresh, 1 = right-to-left +#define MADCTL_BIT_INDEX_RGB_BGR_ORDER 3 // 0 = RGB, 1 = BGR +#define MADCTL_BIT_INDEX_LINE_ADDRESS_ORDER 4 // 0 = top-to-bottom refresh, 1 = bottom-to-top +#define MADCTL_BIT_INDEX_PAGE_COLUMN_ORDER 5 // 0 = normal, 1 = reverse +#define MADCTL_BIT_INDEX_COLUMN_ADDRESS_ORDER 6 // 0 = left-to-right, 1 = right-to-left +#define MADCTL_BIT_INDEX_PAGE_ADDRESS_ORDER 7 // 0 = top-to-bottom, 1 = bottom-to-top + +void hx8357_reset(gpio_num_t resetPin); +void hx8357_init(gpio_num_t dcPin); +void hx8357_set_madctl(uint8_t value); +void hx8357_flush(lv_disp_t* drv, const lv_area_t* area, uint8_t* color_map); + +uint8_t hx8357d_get_gamma_curve_count(); +/** + * Note: this doesn't work, even though the manual says it should + * Page 141: https://cdn-shop.adafruit.com/datasheets/HX8357-D_DS_April2012.pdf + */ +void hx8357d_set_gamme_curve(uint8_t index); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*HX8357_H*/ diff --git a/Boards/UnPhone/Source/lvgl_spi_conf.h b/Boards/UnPhone/Source/lvgl_spi_conf.h new file mode 100644 index 00000000..bde9214c --- /dev/null +++ b/Boards/UnPhone/Source/lvgl_spi_conf.h @@ -0,0 +1,7 @@ +#pragma once + +#define SPI_TFT_CLOCK_SPEED_HZ (26*1000*1000) +#define SPI_TFT_SPI_MODE (0) +#define DISP_SPI_CS GPIO_NUM_48 +#define DISP_SPI_INPUT_DELAY_NS 0 + diff --git a/Boards/YellowBoard/Source/hal/YellowSdCard.cpp b/Boards/YellowBoard/Source/hal/YellowSdCard.cpp index 192cc7d0..4806d784 100644 --- a/Boards/YellowBoard/Source/hal/YellowSdCard.cpp +++ b/Boards/YellowBoard/Source/hal/YellowSdCard.cpp @@ -7,7 +7,7 @@ #define SDCARD_SPI_HOST SPI3_HOST #define SDCARD_PIN_CS GPIO_NUM_5 -#define SDCARD_SPI_FREQUENCY 800000U +#define SDCARD_SPI_FREQUENCY 20000000U std::shared_ptr createYellowSdCard() { auto* configuration = new tt::hal::SpiSdCard::Config( diff --git a/CMakeLists.txt b/CMakeLists.txt index 41f88721..f1da537c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,7 @@ if (DEFINED ENV{ESP_IDF_VERSION}) if(NOT "${IDF_TARGET}" STREQUAL "esp32s3") set(EXCLUDE_COMPONENTS "LilygoTdeck") set(EXCLUDE_COMPONENTS "M5stackCoreS3") + set(EXCLUDE_COMPONENTS "UnPhone") endif() # LVGL diff --git a/CODING_STYLE.md b/CODING_STYLE.md index baa7a019..23cb0ac1 100644 --- a/CODING_STYLE.md +++ b/CODING_STYLE.md @@ -2,7 +2,9 @@ The basic formatting rules are set in `.clang-format`. Use auto-formatting in your editor. -All code should target C++ language revision 17. +All code should target C++ language revision 20. + +If you use CLion, please enable the setting called "Enable ClangFormat" under Settings > Editor > Code Style. ## Naming @@ -81,3 +83,7 @@ enum class { Error }; ``` + +### Exceptions + +Don't ever throw them. Make a return type that wraps all the error and success scenarios that are relevant. diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 3109432d..b32d9b93 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -15,8 +15,12 @@ - tt_check() failure during app argument bundle nullptr check seems to trigger SIGSEGV - Fix bug in T-Deck/etc: esp_lvgl_port settings has a large stack size (~9kB) to fix an issue where the T-Deck would get a stackoverflow. This sometimes happens when WiFi is auto-enabled and you open the app while it is still connecting. - M5Stack Core only shows 4MB of SPIRAM in use +- Try to improve Core2 and CoreS3 performance by setting swap_bytes of display driver to false (this is a software operation on the display buffer!) and use 24 bit colour mode if needed # TODOs +- Boards' CMakeLists.txt manually declare each source folder. Update them all to do a recursive search of all folders. +- We currently make all boards for a given platform (e.g. ESP32S3), but it's better to filter all irrelevant ones based on the Kconfig board settings: + Projects will load and compile faster as it won't compile all the dependencies of all these other boards - Make a ledger for setting CPU affinity of various services and tasks - Make "blocking" argument the last one, and put it default to false (or remove it entirely?): void startApp(const std::string& id, bool blocking, std::shared_ptr parameters) { - Boot hooks instead of a single boot method in config. Define different boot phases/levels in enum. diff --git a/Tactility/Source/Tactility.h b/Tactility/Source/Tactility.h index 0bb65d63..d910baeb 100644 --- a/Tactility/Source/Tactility.h +++ b/Tactility/Source/Tactility.h @@ -14,9 +14,9 @@ struct Configuration { /** HAL configuration (drivers) */ const hal::Configuration* hardware; /** List of user applications */ - const std::vector apps; + const std::vector apps = {}; /** List of user services */ - const std::vector services; + const std::vector services = {}; /** Optional app to start automatically after the splash screen. */ const char* _Nullable autoStartAppId = nullptr; }; diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index 53a1b86d..329719f3 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -62,7 +62,7 @@ static void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) { } int32_t available_width = lv_display_get_horizontal_resolution(display) - (3 * 80); - int32_t padding = TT_MIN(available_width / 4, 64); + int32_t padding = is_landscape_display ? TT_MIN(available_width / 4, 64) : 0; auto paths = app.getPaths(); auto apps_icon_path = paths->getSystemPathLvgl("icon_apps.png"); diff --git a/TactilityHeadless/Source/hal/SpiSdCard.cpp b/TactilityHeadless/Source/hal/SpiSdCard.cpp index bb37ca26..8976c2d3 100644 --- a/TactilityHeadless/Source/hal/SpiSdCard.cpp +++ b/TactilityHeadless/Source/hal/SpiSdCard.cpp @@ -74,7 +74,7 @@ bool SpiSdCard::mountInternal(const char* mountPoint) { // The following value is from T-Deck repo's UnitTest.ino project: // https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/UnitTest/UnitTest.ino // Observation: Using this automatically sets the bus to 20MHz - host.max_freq_khz = config->spiFrequency; + host.max_freq_khz = config->spiFrequency / 1000U; host.slot = config->spiHost; esp_err_t result = esp_vfs_fat_sdspi_mount(mountPoint, &host, &slot_config, &mount_config, &card); diff --git a/Boards/M5stackCoreS3/Source/I2cDevice/I2cDevice.cpp b/TactilityHeadless/Source/hal/i2c/I2cDevice.cpp similarity index 93% rename from Boards/M5stackCoreS3/Source/I2cDevice/I2cDevice.cpp rename to TactilityHeadless/Source/hal/i2c/I2cDevice.cpp index 61302fdd..36351f30 100644 --- a/Boards/M5stackCoreS3/Source/I2cDevice/I2cDevice.cpp +++ b/TactilityHeadless/Source/hal/i2c/I2cDevice.cpp @@ -2,7 +2,7 @@ bool I2cDevice::readRegister12(uint8_t reg, float& out) const { std::uint8_t data[2] = {0}; - if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT) == ESP_OK) { + if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) { out = (data[0] & 0x0F) << 8 | data[1]; return true; } else { @@ -12,7 +12,7 @@ bool I2cDevice::readRegister12(uint8_t reg, float& out) const { bool I2cDevice::readRegister14(uint8_t reg, float& out) const { std::uint8_t data[2] = {0}; - if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT) == ESP_OK) { + if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) { out = (data[0] & 0x3F) << 8 | data[1]; return true; } else { @@ -22,7 +22,7 @@ bool I2cDevice::readRegister14(uint8_t reg, float& out) const { bool I2cDevice::readRegister16(uint8_t reg, uint16_t& out) const { std::uint8_t data[2] = {0}; - if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT) == ESP_OK) { + if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) { out = data[0] << 8 | data[1]; return true; } else { diff --git a/Boards/M5stackCoreS3/Source/I2cDevice/I2cDevice.h b/TactilityHeadless/Source/hal/i2c/I2cDevice.h similarity index 64% rename from Boards/M5stackCoreS3/Source/I2cDevice/I2cDevice.h rename to TactilityHeadless/Source/hal/i2c/I2cDevice.h index 90c5635c..6ca94064 100644 --- a/Boards/M5stackCoreS3/Source/I2cDevice/I2cDevice.h +++ b/TactilityHeadless/Source/hal/i2c/I2cDevice.h @@ -1,7 +1,13 @@ #pragma once -#include "hal/i2c/I2c.h" +#include "./I2c.h" +/** + * Represents an I2C peripheral at a specific port and address. + * It helps to read and write registers. + * + * All read and write calls are thread-safe. + */ class I2cDevice { protected: @@ -18,7 +24,8 @@ protected: bool readRegister16(uint8_t reg, uint16_t& out) const; bool bitOn(uint8_t reg, uint8_t bitmask) const; bool bitOff(uint8_t reg, uint8_t bitmask) const; - + bool bitOnByIndex(uint8_t reg, uint8_t index) const { return bitOn(reg, 1 << index); } + bool bitOffByIndex(uint8_t reg, uint8_t index) const { return bitOff(reg, 1 << index); } public: explicit I2cDevice(i2c_port_t port, uint32_t address) : port(port), address(address) {} diff --git a/TactilityHeadless/Source/hal/i2c/I2cMock.cpp b/TactilityHeadless/Source/hal/i2c/I2cMock.cpp index 76ab7b01..e122652e 100644 --- a/TactilityHeadless/Source/hal/i2c/I2cMock.cpp +++ b/TactilityHeadless/Source/hal/i2c/I2cMock.cpp @@ -80,14 +80,6 @@ bool isStarted(i2c_port_t port) { return started; } -bool read(i2c_port_t port, uint16_t address, uint32_t reg, uint8_t* buffer, uint16_t size, TickType_t timeout) { - return false; -} - -bool write(i2c_port_t port, uint16_t address, uint32_t reg, const uint8_t* buffer, uint16_t size, TickType_t timeout) { - return false; -} - bool lock(i2c_port_t port, TickType_t timeout) { return dataArray[port].mutex.lock(timeout); } @@ -96,6 +88,30 @@ bool unlock(i2c_port_t port) { return dataArray[port].mutex.unlock(); } +bool masterRead(i2c_port_t port, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout) { + return false; +} + +bool masterReadRegister(i2c_port_t port, uint8_t address, uint8_t reg, uint8_t* data, size_t dataSize, TickType_t timeout) { + return false; +} + +bool masterWrite(i2c_port_t port, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout) { + return false; +} + +bool masterWriteRegister(i2c_port_t port, uint8_t address, uint8_t reg, const uint8_t* data, uint16_t dataSize, TickType_t timeout) { + return false; +} + +bool masterWriteRegisterArray(i2c_port_t port, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout) { + return false; +} + +bool masterWriteRead(i2c_port_t port, uint8_t address, const uint8_t* writeData, size_t writeDataSize, uint8_t* readData, size_t readDataSize, TickType_t timeout) { + return false; +} + bool masterHasDeviceAtAddress(i2c_port_t port, uint8_t address, TickType_t timeout) { return (rand()) % 25 == 0; } diff --git a/TactilityHeadless/Source/hal/usb/Usb.cpp b/TactilityHeadless/Source/hal/usb/Usb.cpp index 0c896d74..96df5e35 100644 --- a/TactilityHeadless/Source/hal/usb/Usb.cpp +++ b/TactilityHeadless/Source/hal/usb/Usb.cpp @@ -60,13 +60,12 @@ bool startMassStorageWithSdmmc() { return false; } - auto result = tusbStartMassStorageWithSdmmc(); - if (result != ESP_OK) { - TT_LOG_E(TAG, "Failed to init mass storage: %s", esp_err_to_name(result)); - return false; - } else { + if (tusbStartMassStorageWithSdmmc()) { currentMode = Mode::MassStorageSdmmc; return true; + } else { + TT_LOG_E(TAG, "Failed to init mass storage"); + return false; } } diff --git a/TactilityHeadless/Source/hal/usb/UsbTusb.cpp b/TactilityHeadless/Source/hal/usb/UsbTusb.cpp index 368a6ec3..def1c6d5 100644 --- a/TactilityHeadless/Source/hal/usb/UsbTusb.cpp +++ b/TactilityHeadless/Source/hal/usb/UsbTusb.cpp @@ -149,7 +149,12 @@ bool tusbStartMassStorageWithSdmmc() { } }; - return tinyusb_msc_storage_init_sdmmc(&config_sdmmc) == ESP_OK; + auto result = tinyusb_msc_storage_init_sdmmc(&config_sdmmc); + if (result != ESP_OK) { + TT_LOG_E(TAG, "TinyUSB init failed: %s", esp_err_to_name(result)); + } + + return result == ESP_OK; } void tusbStop() { diff --git a/sdkconfig.board.unphone b/sdkconfig.board.unphone new file mode 100644 index 00000000..bb114820 --- /dev/null +++ b/sdkconfig.board.unphone @@ -0,0 +1,52 @@ +# 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=4096 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_ELF_LOADER_CUSTOMER_SYMBOLS=y +CONFIG_FATFS_LFN_HEAP=y +CONFIG_FATFS_VOLUME_COUNT=3 + +# Hardware: Main +CONFIG_TT_BOARD_UNPHONE=y +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_FLASHMODE_QIO=y +# Hardware: SPI RAM +CONFIG_ESP32S3_SPIRAM_SUPPORT=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +# LVGL +CONFIG_LV_DISP_DEF_REFR_PERIOD=17 +CONFIG_LV_INDEV_DEF_READ_PERIOD=17 +CONFIG_LV_DPI_DEF=139 +CONFIG_LV_COLOR_DEPTH_24=y +CONFIG_LV_COLOR_DEPTH=24 +# TinyUSB: Currently not working (no error in log, mounting takes minutes or more) +CONFIG_TINYUSB_MSC_ENABLED=n +CONFIG_TINYUSB_MSC_MOUNT_PATH="/sdcard"