unPhone implementation and more (#169)

- Implemented [unPhone](https://unphone.net/) v9 board
- Updated `.clang-format` to better reflect the intended code style
- Fix SD card compatibility issues for all boards (frequency wasn't set well)
- Moved `I2cDevice` class from CoreS3 board project to TactilityHeadless project
- Tactility configuration now has default empty lists for apps and services fields
- Fix for Launcher app: we don't need padding when showing it vertically
- Fix for I2cDevice read/write calls that checked for `esp_err_t` instead of `bool`
- Fix for TinyUSB init that checked for `esp_err_t` instead of `bool`
This commit is contained in:
Ken Van Hoeylandt 2025-01-19 16:57:00 +01:00 committed by GitHub
parent 3ea02d912f
commit 72230129bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 1890 additions and 33 deletions

View File

@ -5,6 +5,7 @@ AccessModifierOffset: -4
AlignAfterOpenBracket: BlockIndent AlignAfterOpenBracket: BlockIndent
AlignConsecutiveAssignments: None AlignConsecutiveAssignments: None
AlignOperands: DontAlign AlignOperands: DontAlign
AlignTrailingComments: false
AllowAllArgumentsOnNextLine: false AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false
@ -37,6 +38,8 @@ BreakInheritanceList: BeforeColon
ColumnLimit: 0 ColumnLimit: 0
CompactNamespaces: false CompactNamespaces: false
ContinuationIndentWidth: 4 ContinuationIndentWidth: 4
EmptyLineBeforeAccessModifier: Always
EmptyLineAfterAccessModifier: Always
IndentCaseLabels: true IndentCaseLabels: true
IndentPPDirectives: None IndentPPDirectives: None
IndentWidth: 4 IndentWidth: 4

View File

@ -40,3 +40,12 @@ jobs:
with: with:
board_id: m5stack-cores3 board_id: m5stack-cores3
arch: esp32s3 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

View File

@ -14,6 +14,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
list(APPEND BOARD_COMPONENTS list(APPEND BOARD_COMPONENTS
LilygoTdeck LilygoTdeck
M5stackCoreS3 M5stackCoreS3
UnPhone
) )
endif() endif()

View File

@ -14,6 +14,8 @@ menu "Tactility App"
bool "M5Stack Core2" bool "M5Stack Core2"
config TT_BOARD_M5STACK_CORES3 config TT_BOARD_M5STACK_CORES3
bool "M5Stack CoreS3" bool "M5Stack CoreS3"
config TT_BOARD_UNPHONE
bool "unPhone"
help help
Select a board/hardware configuration. Select a board/hardware configuration.
Use TT_BOARD_CUSTOM if you will manually configure the board in your project. Use TT_BOARD_CUSTOM if you will manually configure the board in your project.

View File

@ -16,6 +16,9 @@
#elif defined(CONFIG_TT_BOARD_M5STACK_CORES3) #elif defined(CONFIG_TT_BOARD_M5STACK_CORES3)
#include "M5stackCoreS3.h" #include "M5stackCoreS3.h"
#define TT_BOARD_HARDWARE &m5stack_cores3 #define TT_BOARD_HARDWARE &m5stack_cores3
#elif defined(CONFIG_TT_BOARD_UNPHONE)
#include "UnPhone.h"
#define TT_BOARD_HARDWARE &unPhone
#else #else
#define TT_BOARD_HARDWARE NULL #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. #error Replace TT_BOARD_HARDWARE in main.c with your own. Or copy one of the ./sdkconfig.board.* files into ./sdkconfig.

View File

@ -1,9 +1,12 @@
dependencies: dependencies:
espressif/esp_lcd_ili9341: "2.0.0" 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_cst816s: "1.0.3"
espressif/esp_lcd_touch_gt911: "1.1.1~2" espressif/esp_lcd_touch_gt911: "1.1.1~2"
espressif/esp_lcd_touch_ft5x06: "1.0.6~1" 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: espressif/esp_tinyusb:
version: "1.5.0" version: "1.5.0"
rules: rules:

View File

@ -6,7 +6,7 @@
#include <esp_vfs_fat.h> #include <esp_vfs_fat.h>
#include <sdmmc_cmd.h> #include <sdmmc_cmd.h>
#define TDECK_SDCARD_SPI_FREQUENCY 800000U #define TDECK_SDCARD_SPI_FREQUENCY 20000000U
#define TDECK_SDCARD_PIN_CS GPIO_NUM_39 #define TDECK_SDCARD_PIN_CS GPIO_NUM_39
#define TDECK_LCD_PIN_CS GPIO_NUM_12 #define TDECK_LCD_PIN_CS GPIO_NUM_12
#define TDECK_RADIO_PIN_CS GPIO_NUM_9 #define TDECK_RADIO_PIN_CS GPIO_NUM_9

View File

@ -5,7 +5,7 @@
#include <esp_vfs_fat.h> #include <esp_vfs_fat.h>
#define CORE2_SDCARD_SPI_FREQUENCY 800000U #define CORE2_SDCARD_SPI_FREQUENCY 20000000U
#define CORE2_SDCARD_PIN_CS GPIO_NUM_4 #define CORE2_SDCARD_PIN_CS GPIO_NUM_4
#define CORE2_LCD_PIN_CS GPIO_NUM_5 #define CORE2_LCD_PIN_CS GPIO_NUM_5

View File

@ -1,5 +1,5 @@
idf_component_register( 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" INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port esp_lcd esp_lcd_ili9341 esp_lcd_touch_ft5x06 driver vfs fatfs REQUIRES Tactility esp_lvgl_port esp_lcd esp_lcd_ili9341 esp_lcd_touch_ft5x06 driver vfs fatfs
) )

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "I2cDevice/I2cDevice.h" #include "hal/i2c/I2cDevice.h"
#define AW9523_ADDRESS 0x58 #define AW9523_ADDRESS 0x58

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "hal/i2c/I2c.h" #include "hal/i2c/I2cDevice.h"
#include "I2cDevice/I2cDevice.h"
#define AXP2101_ADDRESS 0x34 #define AXP2101_ADDRESS 0x34

View File

@ -5,7 +5,7 @@
#include <esp_vfs_fat.h> #include <esp_vfs_fat.h>
#define CORES3_SDCARD_SPI_FREQUENCY 800000U #define CORES3_SDCARD_SPI_FREQUENCY 20000000U
#define CORES3_SDCARD_PIN_CS GPIO_NUM_4 #define CORES3_SDCARD_PIN_CS GPIO_NUM_4
#define CORES3_LCD_PIN_CS GPIO_NUM_3 #define CORES3_LCD_PIN_CS GPIO_NUM_3

View File

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

View File

@ -0,0 +1,46 @@
#include "TactilityCore.h"
#include "hal/UnPhoneDisplayConstants.h"
#include "hx8357/disp_spi.h"
#include <driver/spi_common.h>
#include <soc/gpio_num.h>
#include <lvgl.h>
#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();
}

View File

@ -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<UBaseType_t>(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;
}

View File

@ -0,0 +1,97 @@
#include "TactilityCore.h"
#include "UnPhoneFeatures.h"
#include <esp_sleep.h>
#define TAG "unphone"
extern UnPhoneFeatures unPhoneFeatures;
static std::unique_ptr<tt::Thread> 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<tt::Thread>(
"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;
}

View File

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

View File

@ -0,0 +1,5 @@
#pragma once
#include <hal/Configuration.h>
extern const tt::hal::Configuration unPhone;

View File

@ -0,0 +1,279 @@
#include "UnPhoneFeatures.h"
#include "FreeRTOS-Kernel/include/FreeRTOS.h"
#include "Log.h"
#include "service/loader/Loader.h"
#include <driver/gpio.h>
#include <driver/rtc_io.h>
#include <esp_sleep.h>
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;
}
}

View File

@ -0,0 +1,51 @@
#pragma once
#include "Thread.h"
#include "bq24295/Bq24295.h"
#include <esp_io_expander_tca95xx_16bit.h>
/**
* 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;
};

View File

@ -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

View File

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

View File

@ -0,0 +1,73 @@
#include "UnPhoneDisplay.h"
#include "UnPhoneDisplayConstants.h"
#include "UnPhoneTouch.h"
#include "Log.h"
#include <TactilityCore.h>
#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<tt::hal::Touch*>(new UnPhoneTouch());
}
tt::hal::Display* createDisplay() {
return static_cast<tt::hal::Display*>(new UnPhoneDisplay());
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <esp_lcd_types.h>
#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();

View File

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

View File

@ -0,0 +1,35 @@
#include "UnPhoneSdCard.h"
#include "lvgl/LvglSync.h"
#include "hal/SpiSdCard.h"
#include <esp_vfs_fat.h>
#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<SdCard> 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<SpiSdCard::Config>(configuration)
);
return std::shared_ptr<SdCard>(sdcard);
}

View File

@ -0,0 +1,7 @@
#pragma once
#include "hal/SdCard.h"
using namespace tt::hal;
std::shared_ptr<SdCard> createUnPhoneSdCard();

View File

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

View File

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

View File

@ -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.

View File

@ -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 <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#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);
}
}

View File

@ -0,0 +1,81 @@
/**
* @file disp_spi.h
*
*/
#ifndef DISP_SPI_H
#define DISP_SPI_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include <stdint.h>
#include <stdbool.h>
#include <driver/spi_master.h>
/*********************
* 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*/

View File

@ -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 <esp_log.h>
#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);
}

View File

@ -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 <stdbool.h>
#include <stdint.h>
#include <lvgl.h>
#include <soc/gpio_num.h>
#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*/

View File

@ -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

View File

@ -7,7 +7,7 @@
#define SDCARD_SPI_HOST SPI3_HOST #define SDCARD_SPI_HOST SPI3_HOST
#define SDCARD_PIN_CS GPIO_NUM_5 #define SDCARD_PIN_CS GPIO_NUM_5
#define SDCARD_SPI_FREQUENCY 800000U #define SDCARD_SPI_FREQUENCY 20000000U
std::shared_ptr<SdCard> createYellowSdCard() { std::shared_ptr<SdCard> createYellowSdCard() {
auto* configuration = new tt::hal::SpiSdCard::Config( auto* configuration = new tt::hal::SpiSdCard::Config(

View File

@ -42,6 +42,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
if(NOT "${IDF_TARGET}" STREQUAL "esp32s3") if(NOT "${IDF_TARGET}" STREQUAL "esp32s3")
set(EXCLUDE_COMPONENTS "LilygoTdeck") set(EXCLUDE_COMPONENTS "LilygoTdeck")
set(EXCLUDE_COMPONENTS "M5stackCoreS3") set(EXCLUDE_COMPONENTS "M5stackCoreS3")
set(EXCLUDE_COMPONENTS "UnPhone")
endif() endif()
# LVGL # LVGL

View File

@ -2,7 +2,9 @@
The basic formatting rules are set in `.clang-format`. Use auto-formatting in your editor. 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 ## Naming
@ -81,3 +83,7 @@ enum class {
Error Error
}; };
``` ```
### Exceptions
Don't ever throw them. Make a return type that wraps all the error and success scenarios that are relevant.

View File

@ -15,8 +15,12 @@
- tt_check() failure during app argument bundle nullptr check seems to trigger SIGSEGV - 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. - 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 - 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 # 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 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<const Bundle> parameters) { - 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<const Bundle> parameters) {
- Boot hooks instead of a single boot method in config. Define different boot phases/levels in enum. - Boot hooks instead of a single boot method in config. Define different boot phases/levels in enum.

View File

@ -14,9 +14,9 @@ struct Configuration {
/** HAL configuration (drivers) */ /** HAL configuration (drivers) */
const hal::Configuration* hardware; const hal::Configuration* hardware;
/** List of user applications */ /** List of user applications */
const std::vector<const app::AppManifest*> apps; const std::vector<const app::AppManifest*> apps = {};
/** List of user services */ /** List of user services */
const std::vector<const service::ServiceManifest*> services; const std::vector<const service::ServiceManifest*> services = {};
/** Optional app to start automatically after the splash screen. */ /** Optional app to start automatically after the splash screen. */
const char* _Nullable autoStartAppId = nullptr; const char* _Nullable autoStartAppId = nullptr;
}; };

View File

@ -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 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 paths = app.getPaths();
auto apps_icon_path = paths->getSystemPathLvgl("icon_apps.png"); auto apps_icon_path = paths->getSystemPathLvgl("icon_apps.png");

View File

@ -74,7 +74,7 @@ bool SpiSdCard::mountInternal(const char* mountPoint) {
// The following value is from T-Deck repo's UnitTest.ino project: // 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 // https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/UnitTest/UnitTest.ino
// Observation: Using this automatically sets the bus to 20MHz // 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; host.slot = config->spiHost;
esp_err_t result = esp_vfs_fat_sdspi_mount(mountPoint, &host, &slot_config, &mount_config, &card); esp_err_t result = esp_vfs_fat_sdspi_mount(mountPoint, &host, &slot_config, &mount_config, &card);

View File

@ -2,7 +2,7 @@
bool I2cDevice::readRegister12(uint8_t reg, float& out) const { bool I2cDevice::readRegister12(uint8_t reg, float& out) const {
std::uint8_t data[2] = {0}; 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]; out = (data[0] & 0x0F) << 8 | data[1];
return true; return true;
} else { } else {
@ -12,7 +12,7 @@ bool I2cDevice::readRegister12(uint8_t reg, float& out) const {
bool I2cDevice::readRegister14(uint8_t reg, float& out) const { bool I2cDevice::readRegister14(uint8_t reg, float& out) const {
std::uint8_t data[2] = {0}; 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]; out = (data[0] & 0x3F) << 8 | data[1];
return true; return true;
} else { } else {
@ -22,7 +22,7 @@ bool I2cDevice::readRegister14(uint8_t reg, float& out) const {
bool I2cDevice::readRegister16(uint8_t reg, uint16_t& out) const { bool I2cDevice::readRegister16(uint8_t reg, uint16_t& out) const {
std::uint8_t data[2] = {0}; 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]; out = data[0] << 8 | data[1];
return true; return true;
} else { } else {

View File

@ -1,7 +1,13 @@
#pragma once #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 { class I2cDevice {
protected: protected:
@ -18,7 +24,8 @@ protected:
bool readRegister16(uint8_t reg, uint16_t& out) const; bool readRegister16(uint8_t reg, uint16_t& out) const;
bool bitOn(uint8_t reg, uint8_t bitmask) const; bool bitOn(uint8_t reg, uint8_t bitmask) const;
bool bitOff(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: public:
explicit I2cDevice(i2c_port_t port, uint32_t address) : port(port), address(address) {} explicit I2cDevice(i2c_port_t port, uint32_t address) : port(port), address(address) {}

View File

@ -80,14 +80,6 @@ bool isStarted(i2c_port_t port) {
return started; 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) { bool lock(i2c_port_t port, TickType_t timeout) {
return dataArray[port].mutex.lock(timeout); return dataArray[port].mutex.lock(timeout);
} }
@ -96,6 +88,30 @@ bool unlock(i2c_port_t port) {
return dataArray[port].mutex.unlock(); 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) { bool masterHasDeviceAtAddress(i2c_port_t port, uint8_t address, TickType_t timeout) {
return (rand()) % 25 == 0; return (rand()) % 25 == 0;
} }

View File

@ -60,13 +60,12 @@ bool startMassStorageWithSdmmc() {
return false; return false;
} }
auto result = tusbStartMassStorageWithSdmmc(); if (tusbStartMassStorageWithSdmmc()) {
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to init mass storage: %s", esp_err_to_name(result));
return false;
} else {
currentMode = Mode::MassStorageSdmmc; currentMode = Mode::MassStorageSdmmc;
return true; return true;
} else {
TT_LOG_E(TAG, "Failed to init mass storage");
return false;
} }
} }

View File

@ -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() { void tusbStop() {

52
sdkconfig.board.unphone Normal file
View File

@ -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"