Compare commits

..

19 Commits

Author SHA1 Message Date
Ken Van Hoeylandt 1bb1260ea0 Update to ESP-IDF v5.4 and fix warnings (#193)
- Update to ESP-IDF v5.4
- Fixed a lot of new and existing warnings
- Fix issue with incorrect `EventFlag` usage in Dispatcher
2025-01-26 15:52:57 +01:00
Ken Van Hoeylandt fa54eaa58a TactilityC improvements for tt_app_ functions (#192)
- Renamed `tt_app_context_*` to tt_app_*`
- Renamed `AppContextHandle` to `AppHandle`
- Created `tt_app_start()` and `tt_app_start_with_bundle()`
2025-01-26 00:48:37 +01:00
Ken Van Hoeylandt f9e1f737fd Create FUNDING.yml (#191) 2025-01-25 17:37:26 +01:00
Ken Van Hoeylandt 686f7cce83 TactilityCore improvements (#187)
FreeRTOS handles were stored plainly and they were deleted in the destructor of classes.
This meant that if a class were to be copied, the destructor would be called twice on the same handles and lead to double-free.

Seha on Discord suggested to fix this by using `std::unique_ptr` with a custom deletion function.

The changes affect:
- Thread
- Semaphore
- Mutex
- StreamBuffer
- Timer
- MessageQueue
- EventFlag

Thread  changes:
- Removal of the hack with the `Data` struct
- Thread's main body is now just a private static function inside the class.
- The C functions were relocated to static class members

PubSub changes:
- Refactored pubsub into class
- Renamed files to `PubSub` instead of `Pubsub`
- `PubSubSubscription` is now a private inner struct and `PubSub` only exposes `SubscriptionHandle`

Lockable, ScopedLockable, Mutex:
- Added `lock()` method that locks indefinitely
- Remove deprecated `acquire()` and `release()` methods
- Removed `TtWaitForever` in favour of `portMAX_DELAY`
2025-01-25 17:29:11 +01:00
Ken Van Hoeylandt c2edbad0fb Update CONTRIBUTING.md (#186) 2025-01-25 12:35:28 +01:00
Ken Van Hoeylandt d86dc40472 Fixes and improvements (#185)
- unPhone improvements related to power and boot (add boot count logging)
- Cleanup of Mutex acquire/release
- Removed `tt_assert()` in favour of `assert()`
- Fix sim build (likely failed due to migration of GitHub Actions to Ubuntu 24.04)
2025-01-24 22:49:29 +01:00
Ken Van Hoeylandt 3be251d8fb Refactor services into classes (#183) 2025-01-24 18:21:47 +01:00
Ken Van Hoeylandt bb7e79886f Various fixes and improvements (#182)
- Fix for `logMutex` bug where the `Mutex` constructor is not called when doing early boot logging (with `DEBUG` level logging) . The only way to make it work is to explicitly call the constructor in the logging wrapper function.
- Fix for unPhone power states
2025-01-23 21:17:33 +01:00
Miguel Magno 9fb8d45b2e Fix duplicate include (#181) 2025-01-23 15:27:19 +01:00
Ken Van Hoeylandt b13502170e Update issue templates (#180) 2025-01-22 23:30:14 +01:00
Ken Van Hoeylandt aa85f7d19d Create SECURITY.md (#179) 2025-01-22 23:21:42 +01:00
Ken Van Hoeylandt dc20ed4874 Improvements & fixes (#178)
- Fix for unPhone sleep: it would wake up every minute briefly (draining the battery over the course of 1-3 days)
- Minor WiFi improvements (mainly added logging and improved filtering on connect .. the latter probably doesn't matter)
2025-01-22 23:10:28 +01:00
Ken Van Hoeylandt 12a9839420 Various fixes and improvements (#177)
- Remove custom `ESP_TARGET` and use `ESP_PLATFORM` everywhere
- Add `Loader` service functionality to `tt::app::` namespace
- Make `Loader` `PubSub` usable by exposing the messages
- Add board type to crash log
- Don't show SD card in Files app when it's not mounted
- Set default SPI frequency for SD cards
- Move TT_VERSION to scope that works for sim too
- Log Tactility version and board on boot
- Rename "Yellow Board" to "CYD 2432S024C"
2025-01-21 21:55:54 +01:00
Ken Van Hoeylandt 97b8007aca Unphone battery status (#176) 2025-01-21 19:53:00 +01:00
Ken Van Hoeylandt c3bcf93698 Refactor app launching (#174)
- Refactor the way apps work: Instead of a C interface, they are now C++ classes. The main reasoning is that attaching data to an app was cumbersome. Having different implementations for different kinds of apps was cumbersome too. (3 or 4 layers of manifest nesting for the TactilityC project)
- External apps are still written in C, but they get a createData/destroyData in their manifest, so:
- External apps now have their own manifest.
- All functions in the original AppManifest are removed and replaced by a single `createApp` function
- External apps now automatically register (each app individually!) when they run the first time. As a side-effect they become visible in the `AppList` app!
- Adapted all apps for the new interface.
- Adapted all internal logic for these changes (Gui, ViewPort, Loader, AppContext, AppInstance, etc.)
- Rewrote parts of Loader to use std::shared_ptr to make the code much safer.
- Added a refcount check for the `AppInstance` and `App` at the end of their lifecycle. Show warning if refcount is too high.
2025-01-21 17:48:32 +01:00
Ken Van Hoeylandt 2bbd44a8b5 Unphone improvements (#172)
- Debounce nav button presses: This fixes multiple triggers on a single press to navigate back. Otherwise, more than 1 app would often close at the same time.
- Nav button respond to release instead of push down, because these buttons aren't super reliable in general. (I might change this in the future after more testing)
- Move single buzz earlier in boot phase, so that we can detect silent boot loops.
2025-01-19 21:40:26 +01:00
Ken Van Hoeylandt 72230129bb 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`
2025-01-19 16:57:00 +01:00
Ken Van Hoeylandt 3ea02d912f Merge develop into main (#167)
- WiFi Connect app is now hidden by default, but accessible at the bottom of the WiFi Manage app when WiFi is turned on.
- WiFi service now turns on WiFi when calling connect() and WiFi is not on.
- Removed `blocking` option for `service::loader::startApp()`. This feature was unused and complex.
- Various apps: Moved private headers into Private/ folder.
- Various apps: created start() function for easy starting.
- Added documentation to all TactilityC APIs
- Refactored various `enum` into `class enum`
- Refactor M5Stack `initBoot()` (but VBus is still 0V for some reason)
2025-01-17 19:37:42 +01:00
Ken Van Hoeylandt 3ca0f8cf97 Post-release changes (#166)
- Bump version for future release
- Update release scripts for consistent package naming
- Updated `README.md`
2025-01-14 06:53:41 +01:00
292 changed files with 7647 additions and 4711 deletions
+3
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
+15
View File
@@ -0,0 +1,15 @@
# These are supported funding model platforms
github: [ByteWelder]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+33
View File
@@ -0,0 +1,33 @@
---
name: Bug report
about: Create a report to help us improve
title: 'Bug: '
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Details (please complete the following information):**
- ESP type: [e.g. ESP32-S3]
- Device: [e.g. LilyGo T-Deck]
- Tactility version: [e.g. 0.2.0 or main branch]
**Additional context**
Add any other context about the problem here.
Add your oops.tactility.one URL if you had a crash (scanned from QR).
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+10
View File
@@ -0,0 +1,10 @@
---
name: Question
about: Ask a question
title: ''
labels: question
assignees: ''
---
@@ -0,0 +1,8 @@
### Checklist
- [ ] I read [contribution guidelines](https://github.com/ByteWelder/Tactility/blob/main/CONTRIBUTING.md)
- [ ] Code adheres to the [coding style](https://github.com/ByteWelder/Tactility/blob/main/CODING_STYLE.md)
### Description
(describe what your changes are)
+3 -3
View File
@@ -20,7 +20,7 @@ runs:
- name: 'Build' - name: 'Build'
uses: espressif/esp-idf-ci-action@main uses: espressif/esp-idf-ci-action@main
with: with:
esp_idf_version: v5.3.2 esp_idf_version: v5.4
target: ${{ inputs.arch }} target: ${{ inputs.arch }}
path: './' path: './'
- name: 'Release' - name: 'Release'
@@ -29,12 +29,12 @@ runs:
- name: 'Upload Artifact: Release' - name: 'Upload Artifact: Release'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: tactility-${{ inputs.board_id }} name: Tactility-${{ inputs.board_id }}
path: release/Tactility-${{ inputs.board_id }} path: release/Tactility-${{ inputs.board_id }}
retention-days: 30 retention-days: 30
- name: 'Upload Artifact: Release symbols' - name: 'Upload Artifact: Release symbols'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: tactility-${{ inputs.board_id }}-symbols name: Tactility-${{ inputs.board_id }}-symbols
path: release/Tactility-${{ inputs.board_id }}-symbols path: release/Tactility-${{ inputs.board_id }}-symbols
retention-days: 30 retention-days: 30
+1 -1
View File
@@ -20,7 +20,7 @@ runs:
- name: 'Build' - name: 'Build'
uses: espressif/esp-idf-ci-action@main uses: espressif/esp-idf-ci-action@main
with: with:
esp_idf_version: v5.3.2 esp_idf_version: v5.4
target: ${{ inputs.arch }} target: ${{ inputs.arch }}
path: './' path: './'
- name: 'Release' - name: 'Release'
+17 -3
View File
@@ -1,17 +1,22 @@
name: Build Firmware name: Build Firmware
on: [push] on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
permissions: read-all permissions: read-all
jobs: jobs:
yellow-board: cyd-2432S024c:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: "Build" - name: "Build"
uses: ./.github/actions/build-firmware uses: ./.github/actions/build-firmware
with: with:
board_id: yellow-board board_id: cyd-2432S024c
arch: esp32 arch: esp32
lilygo-tdeck: lilygo-tdeck:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -40,3 +45,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
+7 -2
View File
@@ -1,5 +1,10 @@
name: Build SDK name: Build SDK
on: [push] on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
permissions: read-all permissions: read-all
@@ -11,7 +16,7 @@ jobs:
- name: "Build" - name: "Build"
uses: ./.github/actions/build-sdk uses: ./.github/actions/build-sdk
with: with:
board_id: yellow-board board_id: cyd-2432S024c
arch: esp32 arch: esp32
esp32s3: esp32s3:
runs-on: ubuntu-latest runs-on: ubuntu-latest
+7 -2
View File
@@ -1,9 +1,14 @@
# Disabled because of issue: https://github.com/libsdl-org/setup-sdl/issues/23 # Disabled because of issue: https://github.com/libsdl-org/setup-sdl/issues/23
name: Build Simulator name: Build Simulator
on: [push] on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
jobs: jobs:
Build-Simulator-Linux: Build-Simulator-Linux:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: "Build" - name: "Build"
+6 -1
View File
@@ -1,5 +1,10 @@
name: Tests name: Tests
on: [push] on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
jobs: jobs:
Run: Run:
runs-on: ubuntu-latest runs-on: ubuntu-latest
+2 -1
View File
@@ -5,7 +5,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
if("${IDF_TARGET}" STREQUAL "esp32") if("${IDF_TARGET}" STREQUAL "esp32")
list(APPEND BOARD_COMPONENTS list(APPEND BOARD_COMPONENTS
YellowBoard CYD-2432S024C
M5stackCore2 M5stackCore2
) )
endif() endif()
@@ -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()
+10 -3
View File
@@ -1,19 +1,26 @@
# Kconfig file for Tactility example app # Kconfig file for Tactility example app
menu "Tactility App" menu "Tactility App"
config TT_BOARD_NAME
string "Board Name"
default ""
config TT_BOARD_ID
string "Board ID"
default ""
choice choice
prompt "Board" prompt "Board"
default TT_BOARD_CUSTOM default TT_BOARD_CUSTOM
config TT_BOARD_CUSTOM config TT_BOARD_CUSTOM
bool "Custom" bool "Custom"
config TT_BOARD_YELLOW_BOARD_24_CAP config TT_BOARD_CYD_2432S024C
bool "Yellow Board (2.4\" capacitive)" bool "CYD 2432S024C"
config TT_BOARD_LILYGO_TDECK config TT_BOARD_LILYGO_TDECK
bool "LilyGo T-Deck" bool "LilyGo T-Deck"
config TT_BOARD_M5STACK_CORE2 config TT_BOARD_M5STACK_CORE2
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.
+6 -3
View File
@@ -7,15 +7,18 @@
#if defined(CONFIG_TT_BOARD_LILYGO_TDECK) #if defined(CONFIG_TT_BOARD_LILYGO_TDECK)
#include "LilygoTdeck.h" #include "LilygoTdeck.h"
#define TT_BOARD_HARDWARE &lilygo_tdeck #define TT_BOARD_HARDWARE &lilygo_tdeck
#elif defined(CONFIG_TT_BOARD_YELLOW_BOARD_24_CAP) #elif defined(CONFIG_TT_BOARD_CYD_2432S024C)
#include "YellowBoard.h" #include "CYD2432S024C.h"
#define TT_BOARD_HARDWARE &yellow_board_24inch_cap #define TT_BOARD_HARDWARE &cyd_2432S024c_config
#elif defined(CONFIG_TT_BOARD_M5STACK_CORE2) #elif defined(CONFIG_TT_BOARD_M5STACK_CORE2)
#include "M5stackCore2.h" #include "M5stackCore2.h"
#define TT_BOARD_HARDWARE &m5stack_core2 #define TT_BOARD_HARDWARE &m5stack_core2
#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.
+15 -9
View File
@@ -1,17 +1,23 @@
#include "app/AppManifest.h"
#include "lvgl.h" #include "lvgl.h"
#include "lvgl/Toolbar.h" #include "lvgl/Toolbar.h"
static void onShow(tt::app::AppContext& context, lv_obj_t* parent) { using namespace tt::app;
lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, context);
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t* label = lv_label_create(parent); class HelloWorldApp : public App {
lv_label_set_text(label, "Hello, world!");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}
extern const tt::app::AppManifest hello_world_app = { void onShow(AppContext& context, lv_obj_t* parent) override {
lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, context);
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t* label = lv_label_create(parent);
lv_label_set_text(label, "Hello, world!");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}
};
extern const AppManifest hello_world_app = {
.id = "HelloWorld", .id = "HelloWorld",
.name = "Hello World", .name = "Hello World",
.onShow = onShow, .createApp = create<HelloWorldApp>
}; };
+5 -2
View File
@@ -1,11 +1,14 @@
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:
- if: "target == esp32s3" - if: "target == esp32s3"
idf: '5.3.2' idf: '5.4'
@@ -1,11 +1,11 @@
#include "YellowBoard.h" #include "CYD2432S024C.h"
#include "hal/YellowDisplay.h" #include "hal/YellowDisplay.h"
#include "hal/YellowSdCard.h" #include "hal/YellowSdCard.h"
bool twodotfour_lvgl_init(); bool twodotfour_lvgl_init();
bool twodotfour_boot(); bool twodotfour_boot();
const tt::hal::Configuration yellow_board_24inch_cap = { const tt::hal::Configuration cyd_2432S024c_config = {
.initBoot = &twodotfour_boot, .initBoot = &twodotfour_boot,
.initLvgl = &twodotfour_lvgl_init, .initLvgl = &twodotfour_lvgl_init,
.createDisplay = createDisplay, .createDisplay = createDisplay,
@@ -15,7 +15,7 @@ const tt::hal::Configuration yellow_board_24inch_cap = {
tt::hal::i2c::Configuration { tt::hal::i2c::Configuration {
.name = "First", .name = "First",
.port = I2C_NUM_0, .port = I2C_NUM_0,
.initMode = tt::hal::i2c::InitDisabled, .initMode = tt::hal::i2c::InitMode::Disabled,
.canReinit = true, .canReinit = true,
.hasMutableConfiguration = true, .hasMutableConfiguration = true,
.config = (i2c_config_t) { .config = (i2c_config_t) {
@@ -33,7 +33,7 @@ const tt::hal::Configuration yellow_board_24inch_cap = {
tt::hal::i2c::Configuration { tt::hal::i2c::Configuration {
.name = "Second", .name = "Second",
.port = I2C_NUM_1, .port = I2C_NUM_1,
.initMode = tt::hal::i2c::InitDisabled, .initMode = tt::hal::i2c::InitMode::Disabled,
.canReinit = true, .canReinit = true,
.hasMutableConfiguration = true, .hasMutableConfiguration = true,
.config = (i2c_config_t) { .config = (i2c_config_t) {
@@ -3,4 +3,4 @@
#include "hal/Configuration.h" #include "hal/Configuration.h"
// Capacitive touch version of the 2.4" yellow board // Capacitive touch version of the 2.4" yellow board
extern const tt::hal::Configuration yellow_board_24inch_cap; extern const tt::hal::Configuration cyd_2432S024c_config;
@@ -44,6 +44,7 @@ static bool setBacklight(uint8_t duty) {
.timer_sel = TWODOTFOUR_LCD_BACKLIGHT_LEDC_TIMER, .timer_sel = TWODOTFOUR_LCD_BACKLIGHT_LEDC_TIMER,
.duty = duty, .duty = duty,
.hpoint = 0, .hpoint = 0,
.sleep_mode = LEDC_SLEEP_MODE_NO_ALIVE_NO_PD,
.flags = { .flags = {
.output_invert = false .output_invert = false
} }
@@ -140,7 +141,7 @@ bool YellowDisplay::start() {
} }
bool YellowDisplay::stop() { bool YellowDisplay::stop() {
tt_assert(displayHandle != nullptr); assert(displayHandle != nullptr);
lvgl_port_remove_disp(displayHandle); lvgl_port_remove_disp(displayHandle);
@@ -7,16 +7,14 @@
#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
std::shared_ptr<SdCard> createYellowSdCard() { std::shared_ptr<SdCard> createYellowSdCard() {
auto* configuration = new tt::hal::SpiSdCard::Config( auto* configuration = new tt::hal::SpiSdCard::Config(
SDCARD_SPI_FREQUENCY,
SDCARD_PIN_CS, SDCARD_PIN_CS,
GPIO_NUM_NC, GPIO_NUM_NC,
GPIO_NUM_NC, GPIO_NUM_NC,
GPIO_NUM_NC, GPIO_NUM_NC,
SdCard::MountBehaviourAtBoot, SdCard::MountBehaviour::AtBoot,
nullptr, nullptr,
std::vector<gpio_num_t>(), std::vector<gpio_num_t>(),
SDCARD_SPI_HOST SDCARD_SPI_HOST
@@ -31,6 +31,7 @@ static bool init_spi() {
.data5_io_num = 0, .data5_io_num = 0,
.data6_io_num = 0, .data6_io_num = 0,
.data7_io_num = 0, .data7_io_num = 0,
.data_io_default_level = false,
.max_transfer_sz = TDECK_SPI_TRANSFER_SIZE_LIMIT, .max_transfer_sz = TDECK_SPI_TRANSFER_SIZE_LIMIT,
.flags = 0, .flags = 0,
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO, .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
+2 -2
View File
@@ -20,7 +20,7 @@ extern const tt::hal::Configuration lilygo_tdeck = {
tt::hal::i2c::Configuration { tt::hal::i2c::Configuration {
.name = "Internal", .name = "Internal",
.port = I2C_NUM_0, .port = I2C_NUM_0,
.initMode = tt::hal::i2c::InitByTactility, .initMode = tt::hal::i2c::InitMode::ByTactility,
.canReinit = false, .canReinit = false,
.hasMutableConfiguration = false, .hasMutableConfiguration = false,
.config = (i2c_config_t) { .config = (i2c_config_t) {
@@ -38,7 +38,7 @@ extern const tt::hal::Configuration lilygo_tdeck = {
tt::hal::i2c::Configuration { tt::hal::i2c::Configuration {
.name = "External", .name = "External",
.port = I2C_NUM_1, .port = I2C_NUM_1,
.initMode = tt::hal::i2c::InitByTactility, .initMode = tt::hal::i2c::InitMode::ByTactility,
.canReinit = true, .canReinit = true,
.hasMutableConfiguration = true, .hasMutableConfiguration = true,
.config = (i2c_config_t) { .config = (i2c_config_t) {
+10 -3
View File
@@ -45,6 +45,7 @@ static bool setBacklight(uint8_t duty) {
.timer_sel = TDECK_LCD_BACKLIGHT_LEDC_TIMER, .timer_sel = TDECK_LCD_BACKLIGHT_LEDC_TIMER,
.duty = duty, .duty = duty,
.hpoint = 0, .hpoint = 0,
.sleep_mode = LEDC_SLEEP_MODE_NO_ALIVE_NO_PD,
.flags = { .flags = {
.output_invert = 0 .output_invert = 0
} }
@@ -68,6 +69,8 @@ bool TdeckDisplay::start() {
.user_ctx = nullptr, .user_ctx = nullptr,
.lcd_cmd_bits = 8, .lcd_cmd_bits = 8,
.lcd_param_bits = 8, .lcd_param_bits = 8,
.cs_ena_pretrans = 0,
.cs_ena_posttrans = 0,
.flags = { .flags = {
.dc_high_on_cmd = 0, .dc_high_on_cmd = 0,
.dc_low_on_data = 0, .dc_low_on_data = 0,
@@ -134,6 +137,7 @@ bool TdeckDisplay::start() {
const lvgl_port_display_cfg_t disp_cfg = { const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = ioHandle, .io_handle = ioHandle,
.panel_handle = panelHandle, .panel_handle = panelHandle,
.control_handle = nullptr,
.buffer_size = TDECK_LCD_HORIZONTAL_RESOLUTION * TDECK_LCD_DRAW_BUFFER_HEIGHT * (TDECK_LCD_BITS_PER_PIXEL / 8), .buffer_size = TDECK_LCD_HORIZONTAL_RESOLUTION * TDECK_LCD_DRAW_BUFFER_HEIGHT * (TDECK_LCD_BITS_PER_PIXEL / 8),
.double_buffer = true, // Disable to free up SPIRAM .double_buffer = true, // Disable to free up SPIRAM
.trans_size = 0, .trans_size = 0,
@@ -145,12 +149,15 @@ bool TdeckDisplay::start() {
.mirror_x = true, .mirror_x = true,
.mirror_y = false, .mirror_y = false,
}, },
.color_format = LV_COLOR_FORMAT_RGB565,
.flags = { .flags = {
.buff_dma = false, .buff_dma = false,
.buff_spiram = true, .buff_spiram = true,
.sw_rotate = false, .sw_rotate = false,
.swap_bytes = false .swap_bytes = false,
}, .full_refresh = false,
.direct_mode = false
}
}; };
displayHandle = lvgl_port_add_disp(&disp_cfg); displayHandle = lvgl_port_add_disp(&disp_cfg);
@@ -159,7 +166,7 @@ bool TdeckDisplay::start() {
} }
bool TdeckDisplay::stop() { bool TdeckDisplay::stop() {
tt_assert(displayHandle != nullptr); assert(displayHandle != nullptr);
lvgl_port_remove_disp(displayHandle); lvgl_port_remove_disp(displayHandle);
+8 -8
View File
@@ -56,11 +56,11 @@ TdeckPower::~TdeckPower() {
bool TdeckPower::supportsMetric(MetricType type) const { bool TdeckPower::supportsMetric(MetricType type) const {
switch (type) { switch (type) {
case BATTERY_VOLTAGE: case MetricType::BatteryVoltage:
case CHARGE_LEVEL: case MetricType::ChargeLevel:
return true; return true;
case IS_CHARGING: case MetricType::IsCharging:
case CURRENT: case MetricType::Current:
return false; return false;
} }
@@ -69,17 +69,17 @@ bool TdeckPower::supportsMetric(MetricType type) const {
bool TdeckPower::getMetric(Power::MetricType type, Power::MetricData& data) { bool TdeckPower::getMetric(Power::MetricType type, Power::MetricData& data) {
switch (type) { switch (type) {
case BATTERY_VOLTAGE: case MetricType::BatteryVoltage:
return readBatteryVoltageSampled(data.valueAsUint32); return readBatteryVoltageSampled(data.valueAsUint32);
case CHARGE_LEVEL: case MetricType::ChargeLevel:
if (readBatteryVoltageSampled(data.valueAsUint32)) { if (readBatteryVoltageSampled(data.valueAsUint32)) {
data.valueAsUint32 = estimateChargeLevelFromVoltage(data.valueAsUint32); data.valueAsUint32 = estimateChargeLevelFromVoltage(data.valueAsUint32);
return true; return true;
} else { } else {
return false; return false;
} }
case IS_CHARGING: case MetricType::IsCharging:
case CURRENT: case MetricType::Current:
return false; return false;
} }
@@ -6,19 +6,17 @@
#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_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
std::shared_ptr<SdCard> createTdeckSdCard() { std::shared_ptr<SdCard> createTdeckSdCard() {
auto* configuration = new tt::hal::SpiSdCard::Config( auto* configuration = new tt::hal::SpiSdCard::Config(
TDECK_SDCARD_SPI_FREQUENCY,
TDECK_SDCARD_PIN_CS, TDECK_SDCARD_PIN_CS,
GPIO_NUM_NC, GPIO_NUM_NC,
GPIO_NUM_NC, GPIO_NUM_NC,
GPIO_NUM_NC, GPIO_NUM_NC,
SdCard::MountBehaviourAtBoot, SdCard::MountBehaviour::AtBoot,
tt::lvgl::getLvglSyncLockable(), tt::lvgl::getLvglSyncLockable(),
{ {
TDECK_RADIO_PIN_CS, TDECK_RADIO_PIN_CS,
+4 -4
View File
@@ -1,6 +1,6 @@
#include <driver/i2c.h> #include <driver/i2c.h>
#include <driver/spi_master.h> #include <driver/spi_master.h>
#include <intr_types.h> #include <esp_intr_types.h>
#include "Log.h" #include "Log.h"
#include "hal/Core2DisplayConstants.h" #include "hal/Core2DisplayConstants.h"
#include "axp192/axp192.h" #include "axp192/axp192.h"
@@ -16,11 +16,11 @@
axp192_t axpDevice; axp192_t axpDevice;
static int32_t axpI2cRead(TT_UNUSED void* handle, uint8_t address, uint8_t reg, uint8_t* buffer, uint16_t size) { static int32_t axpI2cRead(TT_UNUSED void* handle, uint8_t address, uint8_t reg, uint8_t* buffer, uint16_t size) {
return tt::hal::i2c::masterRead(I2C_NUM_0, address, reg, buffer, size, 50 / portTICK_PERIOD_MS); return tt::hal::i2c::masterReadRegister(I2C_NUM_0, address, reg, buffer, size, 50 / portTICK_PERIOD_MS);
} }
static int32_t axpI2cWrite(TT_UNUSED void* handle, uint8_t address, uint8_t reg, const uint8_t* buffer, uint16_t size) { static int32_t axpI2cWrite(TT_UNUSED void* handle, uint8_t address, uint8_t reg, const uint8_t* buffer, uint16_t size) {
return tt::hal::i2c::masterWrite(I2C_NUM_0, address, reg, buffer, size, 50 / portTICK_PERIOD_MS); return tt::hal::i2c::masterWriteRegister(I2C_NUM_0, address, reg, buffer, size, 50 / portTICK_PERIOD_MS);
} }
static bool initSpi2() { static bool initSpi2() {
@@ -37,7 +37,7 @@ static bool initSpi2() {
.data7_io_num = GPIO_NUM_NC, .data7_io_num = GPIO_NUM_NC,
.max_transfer_sz = CORE2_LCD_DRAW_BUFFER_SIZE, .max_transfer_sz = CORE2_LCD_DRAW_BUFFER_SIZE,
.flags = 0, .flags = 0,
.isr_cpu_id = INTR_CPU_ID_AUTO, .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
.intr_flags = 0 .intr_flags = 0
}; };
+2 -2
View File
@@ -15,7 +15,7 @@ extern const tt::hal::Configuration m5stack_core2 = {
tt::hal::i2c::Configuration { tt::hal::i2c::Configuration {
.name = "Internal", .name = "Internal",
.port = I2C_NUM_0, .port = I2C_NUM_0,
.initMode = tt::hal::i2c::InitByTactility, .initMode = tt::hal::i2c::InitMode::ByTactility,
.canReinit = false, // Might be set to try after trying out what it does AXP and screen .canReinit = false, // Might be set to try after trying out what it does AXP and screen
.hasMutableConfiguration = false, .hasMutableConfiguration = false,
.config = (i2c_config_t) { .config = (i2c_config_t) {
@@ -33,7 +33,7 @@ extern const tt::hal::Configuration m5stack_core2 = {
tt::hal::i2c::Configuration { tt::hal::i2c::Configuration {
.name = "External", // (Grove) .name = "External", // (Grove)
.port = I2C_NUM_1, .port = I2C_NUM_1,
.initMode = tt::hal::i2c::InitByTactility, .initMode = tt::hal::i2c::InitMode::ByTactility,
.canReinit = true, .canReinit = true,
.hasMutableConfiguration = true, .hasMutableConfiguration = true,
.config = (i2c_config_t) { .config = (i2c_config_t) {
@@ -102,7 +102,7 @@ bool Core2Display::start() {
} }
bool Core2Display::stop() { bool Core2Display::stop() {
tt_assert(displayHandle != nullptr); assert(displayHandle != nullptr);
lvgl_port_remove_disp(displayHandle); lvgl_port_remove_disp(displayHandle);
@@ -8,11 +8,11 @@ extern axp192_t axpDevice;
bool Core2Power::supportsMetric(MetricType type) const { bool Core2Power::supportsMetric(MetricType type) const {
switch (type) { switch (type) {
case BATTERY_VOLTAGE: case MetricType::BatteryVoltage:
case CHARGE_LEVEL: case MetricType::ChargeLevel:
case IS_CHARGING: case MetricType::IsCharging:
return true; return true;
case CURRENT: case MetricType::Current:
return false; return false;
} }
@@ -21,7 +21,7 @@ bool Core2Power::supportsMetric(MetricType type) const {
bool Core2Power::getMetric(Power::MetricType type, Power::MetricData& data) { bool Core2Power::getMetric(Power::MetricType type, Power::MetricData& data) {
switch (type) { switch (type) {
case BATTERY_VOLTAGE: { case MetricType::BatteryVoltage: {
float voltage; float voltage;
if (axp192_read(&axpDevice, AXP192_BATTERY_VOLTAGE, &voltage) == ESP_OK) { if (axp192_read(&axpDevice, AXP192_BATTERY_VOLTAGE, &voltage) == ESP_OK) {
data.valueAsUint32 = (uint32_t)TT_MAX((voltage * 1000.f), 0.0f); data.valueAsUint32 = (uint32_t)TT_MAX((voltage * 1000.f), 0.0f);
@@ -30,7 +30,7 @@ bool Core2Power::getMetric(Power::MetricType type, Power::MetricData& data) {
return false; return false;
} }
} }
case CHARGE_LEVEL: { case MetricType::ChargeLevel: {
float vbat, charge_current; float vbat, charge_current;
if ( if (
axp192_read(&axpDevice, AXP192_BATTERY_VOLTAGE, &vbat) == ESP_OK && axp192_read(&axpDevice, AXP192_BATTERY_VOLTAGE, &vbat) == ESP_OK &&
@@ -51,7 +51,7 @@ bool Core2Power::getMetric(Power::MetricType type, Power::MetricData& data) {
return false; return false;
} }
} }
case IS_CHARGING: { case MetricType::IsCharging: {
float charge_current; float charge_current;
if (axp192_read(&axpDevice, AXP192_CHARGE_CURRENT, &charge_current) == ESP_OK) { if (axp192_read(&axpDevice, AXP192_CHARGE_CURRENT, &charge_current) == ESP_OK) {
data.valueAsBool = charge_current > 0.001f; data.valueAsBool = charge_current > 0.001f;
@@ -60,7 +60,7 @@ bool Core2Power::getMetric(Power::MetricType type, Power::MetricData& data) {
return false; return false;
} }
} }
case CURRENT: { case MetricType::Current: {
float charge_current, discharge_current; float charge_current, discharge_current;
if ( if (
axp192_read(&axpDevice, AXP192_CHARGE_CURRENT, &charge_current) == ESP_OK && axp192_read(&axpDevice, AXP192_CHARGE_CURRENT, &charge_current) == ESP_OK &&
@@ -5,18 +5,16 @@
#include <esp_vfs_fat.h> #include <esp_vfs_fat.h>
#define CORE2_SDCARD_SPI_FREQUENCY 800000U
#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
std::shared_ptr<SdCard> createSdCard() { std::shared_ptr<SdCard> createSdCard() {
auto* configuration = new tt::hal::SpiSdCard::Config( auto* configuration = new tt::hal::SpiSdCard::Config(
CORE2_SDCARD_SPI_FREQUENCY,
CORE2_SDCARD_PIN_CS, CORE2_SDCARD_PIN_CS,
GPIO_NUM_NC, GPIO_NUM_NC,
GPIO_NUM_NC, GPIO_NUM_NC,
GPIO_NUM_NC, GPIO_NUM_NC,
SdCard::MountBehaviourAtBoot, SdCard::MountBehaviour::AtBoot,
tt::lvgl::getLvglSyncLockable(), tt::lvgl::getLvglSyncLockable(),
{ {
CORE2_LCD_PIN_CS CORE2_LCD_PIN_CS
+1 -1
View File
@@ -1,5 +1,5 @@
idf_component_register( idf_component_register(
SRC_DIRS "Source" "Source/hal" "Source/Axp2101" 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
) )
@@ -0,0 +1,24 @@
#include "Aw9523.h"
#define AW9523_REGISTER_P0 0x02
#define AW9523_REGISTER_P1 0x03
bool Aw9523::readP0(uint8_t& output) const {
return readRegister8(AW9523_REGISTER_P0, output);
}
bool Aw9523::readP1(uint8_t& output) const {
return readRegister8(AW9523_REGISTER_P1, output);
}
bool Aw9523::writeP0(uint8_t value) const {
return writeRegister8(AW9523_REGISTER_P0, value);
}
bool Aw9523::writeP1(uint8_t value) const {
return writeRegister8(AW9523_REGISTER_P1, value);
}
bool Aw9523::bitOnP1(uint8_t bitmask) const {
return bitOn(AW9523_REGISTER_P1, bitmask);
}
@@ -0,0 +1,20 @@
#pragma once
#include "hal/i2c/I2cDevice.h"
#define AW9523_ADDRESS 0x58
class Aw9523 : I2cDevice {
public:
explicit Aw9523(i2c_port_t port) : I2cDevice(port, AW9523_ADDRESS) {}
bool readP0(uint8_t& output) const;
bool readP1(uint8_t& output) const;
bool writeP0(uint8_t value) const;
bool writeP1(uint8_t value) const;
bool bitOnP1(uint8_t bitmask) const;
};
+23 -38
View File
@@ -1,44 +1,6 @@
#include "Axp2101.h" #include "Axp2101.h"
#include "Log.h" #include "Log.h"
bool Axp2101::readRegister12(uint8_t reg, float& out) const {
std::uint8_t data[2] = {0};
if (tt::hal::i2c::masterRead(port, DEFAULT_ADDRESS, reg, data, 2, DEFAULT_TIMEOUT) == ESP_OK) {
out = (data[0] & 0x0F) << 8 | data[1];
return true;
} else {
return false;
}
}
bool Axp2101::readRegister14(uint8_t reg, float& out) const {
std::uint8_t data[2] = {0};
if (tt::hal::i2c::masterRead(port, DEFAULT_ADDRESS, reg, data, 2, DEFAULT_TIMEOUT) == ESP_OK) {
out = (data[0] & 0x3F) << 8 | data[1];
return true;
} else {
return false;
}
}
bool Axp2101::readRegister16(uint8_t reg, uint16_t& out) const {
std::uint8_t data[2] = {0};
if (tt::hal::i2c::masterRead(port, DEFAULT_ADDRESS, reg, data, 2, DEFAULT_TIMEOUT) == ESP_OK) {
out = data[0] << 8 | data[1];
return true;
} else {
return false;
}
}
bool Axp2101::readRegister8(uint8_t reg, uint8_t& result) const {
return tt::hal::i2c::masterWriteRead(port, DEFAULT_ADDRESS, &reg, 1, &result, 1, DEFAULT_TIMEOUT);
}
bool Axp2101::writeRegister8(uint8_t reg, uint8_t value) const {
return tt::hal::i2c::masterWrite(port, DEFAULT_ADDRESS, reg, &value, 1, DEFAULT_TIMEOUT);
}
bool Axp2101::getBatteryVoltage(float& vbatMillis) const { bool Axp2101::getBatteryVoltage(float& vbatMillis) const {
return readRegister14(0x34, vbatMillis); return readRegister14(0x34, vbatMillis);
} }
@@ -72,3 +34,26 @@ bool Axp2101::setChargingEnabled(bool enabled) const {
return false; return false;
} }
} }
bool Axp2101::isVBus() const {
uint8_t value;
return readRegister8(0x00, value) && (value & 0x20);
}
bool Axp2101::getVBusVoltage(float& out) const {
if (!isVBus()) {
return false;
} else {
float vbus;
if (readRegister14(0x38, vbus) && vbus < 16375) {
out = vbus / 1000.0f;
return true;
} else {
return false;
}
}
}
bool Axp2101::setRegisters(uint8_t* bytePairs, size_t bytePairsSize) const {
return tt::hal::i2c::masterWriteRegisterArray(port, address, bytePairs, bytePairsSize, DEFAULT_TIMEOUT);
}
+10 -14
View File
@@ -1,24 +1,15 @@
#pragma once #pragma once
#include "hal/i2c/I2c.h" #include "hal/i2c/I2cDevice.h"
#define AXP2101_ADDRESS 0x34
/** /**
* References: * References:
* - https://github.com/m5stack/M5Unified/blob/master/src/utility/AXP2101_Class.cpp * - https://github.com/m5stack/M5Unified/blob/master/src/utility/AXP2101_Class.cpp
* - http://file.whycan.com/files/members/6736/AXP2101_Datasheet_V1.0_en_3832.pdf * - http://file.whycan.com/files/members/6736/AXP2101_Datasheet_V1.0_en_3832.pdf
*/ */
class Axp2101 { class Axp2101 : I2cDevice {
i2c_port_t port;
static constexpr uint8_t DEFAULT_ADDRESS = 0x34;
static constexpr TickType_t DEFAULT_TIMEOUT = 1000 / portTICK_PERIOD_MS;
bool readRegister8(uint8_t reg, uint8_t& result) const;
bool writeRegister8(uint8_t reg, uint8_t value) const;
bool readRegister12(uint8_t reg, float& out) const;
bool readRegister14(uint8_t reg, float& out) const;
bool readRegister16(uint8_t reg, uint16_t& out) const;
public: public:
@@ -28,10 +19,15 @@ public:
CHARGE_STATUS_STANDBY = 0b00 CHARGE_STATUS_STANDBY = 0b00
}; };
Axp2101(i2c_port_t port) : port(port) {} explicit Axp2101(i2c_port_t port) : I2cDevice(port, AXP2101_ADDRESS) {}
bool setRegisters(uint8_t* bytePairs, size_t bytePairsSize) const;
bool getBatteryVoltage(float& vbatMillis) const; bool getBatteryVoltage(float& vbatMillis) const;
bool getChargeStatus(ChargeStatus& status) const; bool getChargeStatus(ChargeStatus& status) const;
bool isChargingEnabled(bool& enabled) const; bool isChargingEnabled(bool& enabled) const;
bool setChargingEnabled(bool enabled) const; bool setChargingEnabled(bool enabled) const;
bool isVBus() const;
bool getVBusVoltage(float& out) const;
}; };
+120 -39
View File
@@ -1,10 +1,11 @@
#include <driver/i2c.h> #include <driver/i2c.h>
#include <driver/spi_master.h> #include <driver/spi_master.h>
#include <intr_types.h> #include <esp_intr_types.h>
#include "Log.h" #include "Log.h"
#include "hal/CoreS3DisplayConstants.h" #include "hal/CoreS3DisplayConstants.h"
#include "hal/i2c/I2c.h" #include "kernel/Kernel.h"
#include "CoreS3Constants.h" #include "Axp2101/Axp2101.h"
#include "Aw9523/Aw9523.h"
#define TAG "core2" #define TAG "core2"
@@ -27,9 +28,10 @@ static bool initSpi3() {
.data5_io_num = GPIO_NUM_NC, .data5_io_num = GPIO_NUM_NC,
.data6_io_num = GPIO_NUM_NC, .data6_io_num = GPIO_NUM_NC,
.data7_io_num = GPIO_NUM_NC, .data7_io_num = GPIO_NUM_NC,
.data_io_default_level = false,
.max_transfer_sz = CORES3_LCD_DRAW_BUFFER_SIZE, .max_transfer_sz = CORES3_LCD_DRAW_BUFFER_SIZE,
.flags = 0, .flags = 0,
.isr_cpu_id = INTR_CPU_ID_AUTO, .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
.intr_flags = 0 .intr_flags = 0
}; };
@@ -43,62 +45,141 @@ static bool initSpi3() {
/** /**
* For details see https://github.com/espressif/esp-bsp/blob/master/bsp/m5stack_core_s3/m5stack_core_s3.c * For details see https://github.com/espressif/esp-bsp/blob/master/bsp/m5stack_core_s3/m5stack_core_s3.c
* and schematic: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/core/K128%20CoreS3/Sch_M5_CoreS3_v1.0.pdf
*/ */
bool initGpioExpander() { bool initGpioExpander() {
TT_LOG_I(TAG, "Init AW9523 GPIO expander"); TT_LOG_I(TAG, "AW9523 init");
uint8_t aw9523_P0 = 0b10;
uint8_t aw9523_P1 = 0b10100000;
// Enable LCD /**
aw9523_P1 |= (1 << 1); * P0 pins:
* 0: Touch reset
* 1: Bus out enable
* 2: AW88298 reset (I2S)
* 3: ES7210 interrupt (Audio ADC)
* 4: SD Card SW(itch on?)
* 5: USB OTG enable
* 6: /
* 7: /
*/
/**
* P1 pins:
* 0: Cam reset
* 1: LCD reset
* 2: Touch interrupt
* 3: AW88298 interrupt (I2S)
* 4: /
* 5: /
* 6: /
* 7: Boost enable
*/
uint8_t p0_state = 0U;
uint8_t p1_state = 0U;
// Enable touch // Enable touch
aw9523_P0 |= (1); p0_state |= (1U);
// Bus out enable
p0_state |= (1U << 1U);
// I2S
p0_state |= (1U << 2U);
// SD card // SD card
aw9523_P0 |= (1 << 4); p0_state |= (1U << 4U);
if (!tt::hal::i2c::masterWrite(I2C_NUM_0, AW9523_ADDRESS, 0x02, &aw9523_P0, 1, 1000)) { // Enable LCD
TT_LOG_E(TAG, "Failed to enable LCD"); p1_state |= (1U << 1U);
// Boost enable
p1_state |= (1U << 7U);
Aw9523 aw(I2C_NUM_0);
if (!aw.writeP0(p0_state)) {
TT_LOG_E(TAG, "AW9523: Failed to set P0");
return false; return false;
} }
if (!tt::hal::i2c::masterWrite(I2C_NUM_0, AW9523_ADDRESS, 0x03, &aw9523_P1, 1, 1000)) { if (!aw.writeP1(p1_state)) {
TT_LOG_E(TAG, "Failed to enable touch"); TT_LOG_E(TAG, "AW9523: Failed to set P1");
return false; return false;
} }
Axp2101 axp(I2C_NUM_0);
if (axp.isVBus()) {
float voltage = 0.0f;
axp.getVBusVoltage(voltage);
TT_LOG_I(TAG, "AXP2101: VBus at %.2f", voltage);
} else {
TT_LOG_W(TAG, "AXP2101: VBus disabled");
}
return true; return true;
} }
bool initPowerControl() { bool initPowerControl() {
TT_LOG_I(TAG, "Init power control"); TT_LOG_I(TAG, "Init power control (AXP2101)");
uint8_t sdcard_3v3 = 0b00011100; Aw9523 aw(I2C_NUM_0);
// TODO: Refactor to use Axp2101 class with https://github.com/m5stack/M5Unified/blob/b8cfec7fed046242da7f7b8024a4e92004a51ff7/src/utility/AXP2101_Class.cpp#L62 // Source: https://github.com/m5stack/M5Unified/blob/b8cfec7fed046242da7f7b8024a4e92004a51ff7/src/utility/Power_Class.cpp#L61
if (!tt::hal::i2c::masterWrite(I2C_NUM_0, AXP2101_ADDRESS, 0x95, &sdcard_3v3, 1, 1000)) { aw.bitOnP1(0b10000000); // SY7088 boost enable
TT_LOG_E(TAG, "Failed to enable SD card");
/** AXP2101 usage
Source: https://github.com/m5stack/M5Unified/blob/b8cfec7fed046242da7f7b8024a4e92004a51ff7/README.md?plain=1#L223
| |M5Stack<BR>CoreS3<BR>CoreS3SE| |
|:---------:|:-----------------:|:---------:|
| ALDO1 |VDD 1v8 | ALDO1 |
| ALDO2 |VDDA 3v3 | ALDO2 |
| ALDO3 |CAM 3v3 | ALDO3 |
| ALDO4 |TF 3v3 | ALDO4 |
| BLDO1 |AVDD | BLDO1 |
| BLDO2 |DVDD | BLDO2 |
| DLDO1/DC1 |LCD BL | DLDO1/DC1 |
| DLDO2/DC2 | --- | DLDO2/DC2 |
| BACKUP |RTC BAT | BACKUP |
*/
/**
* 0x92 = ALD01
* 0x93 = ALD02
* 0x94 = ALD03
* 0x95 = ALD04
* 0x96 = BLD01
* 0x97 = BLD02
*
* DCDC1 : 0.7-3.5V, 25mV/step 1200mA
* DCDC2 : 0.7-2.275V,25mV/step 1600mA
* DCDC3 : 0.7-3.5V, 25mV/step 700mA
* LDOio0: 1.8-3.3V, 100mV/step 50mA
* LDO1 : 30mA always on
* LDO2 : 1.8-3.3V, 100mV/step 200mA
* LDO3 : 1.8-3.3V, 100mV/step 200mA
*/
// Source: https://github.com/m5stack/M5Unified/blob/b8cfec7fed046242da7f7b8024a4e92004a51ff7/src/utility/Power_Class.cpp#L64
static constexpr uint8_t reg_data_array[] = {
0x90U, 0xBFU, // LDOS ON/OFF control 0 (backlight)
0x92U, 18U -5U, // ALDO1 set to 1.8v // for AW88298
0x93U, 33U -5U, // ALDO2 set to 3.3v // for ES7210
0x94U, 33U -5U, // ALDO3 set to 3.3v // for camera
0x95U, 33U -5U, // ALDO3 set to 3.3v // for TF card slot
0x27, 0x00, // PowerKey Hold=1sec / PowerOff=4sec
0x69, 0x11, // CHGLED setting
0x10, 0x30, // PMU common config
0x30, 0x0F // ADC enabled (for voltage measurement)
};
Axp2101 axp(I2C_NUM_0);
if (axp.setRegisters((uint8_t*)reg_data_array, sizeof(reg_data_array))) {
TT_LOG_I(TAG, "AXP2101 initialized with %d registers", sizeof(reg_data_array) / 2);
return true;
} else {
TT_LOG_E(TAG, "AXP2101: Failed to set registers");
return false; return false;
} }
// TODO: Refactor to use Axp2102 class with https://github.com/m5stack/M5Unified/blob/b8cfec7fed046242da7f7b8024a4e92004a51ff7/src/utility/AXP2101_Class.cpp#L42
uint8_t axp_dld01_enable = 0xBF; // For backlight
if (!tt::hal::i2c::masterWrite(I2C_NUM_0, AXP2101_ADDRESS, 0x90, &axp_dld01_enable, 1, 1000)) {
TT_LOG_E(TAG, "Failed to enable display backlight");
return false;
}
// TODO: Refactor to use Axp2101 class with https://github.com/m5stack/M5Unified/blob/b8cfec7fed046242da7f7b8024a4e92004a51ff7/src/utility/AXP2101_Class.cpp#L62
uint8_t axp_dld01_voltage = 0b00011000; // For backlight
if (!tt::hal::i2c::masterWrite(I2C_NUM_0, AXP2101_ADDRESS, 0x99, &axp_dld01_voltage, 1, 1000)) {
TT_LOG_E(TAG, "Failed to set display backlight voltage");
return false;
}
return true;
} }
bool initBoot() { bool initBoot() {
TT_LOG_I(TAG, "initBoot"); TT_LOG_I(TAG, "initBoot()");
return initPowerControl() && initGpioExpander() && initSpi3(); return initPowerControl() &&
initGpioExpander() &&
initSpi3();
} }
@@ -15,7 +15,7 @@ const tt::hal::Configuration m5stack_cores3 = {
tt::hal::i2c::Configuration { tt::hal::i2c::Configuration {
.name = "Internal", .name = "Internal",
.port = I2C_NUM_0, .port = I2C_NUM_0,
.initMode = tt::hal::i2c::InitByTactility, .initMode = tt::hal::i2c::InitMode::ByTactility,
.canReinit = false, .canReinit = false,
.hasMutableConfiguration = false, .hasMutableConfiguration = false,
.config = (i2c_config_t) { .config = (i2c_config_t) {
@@ -33,7 +33,7 @@ const tt::hal::Configuration m5stack_cores3 = {
tt::hal::i2c::Configuration { tt::hal::i2c::Configuration {
.name = "External", // Grove .name = "External", // Grove
.port = I2C_NUM_1, .port = I2C_NUM_1,
.initMode = tt::hal::i2c::InitByTactility, .initMode = tt::hal::i2c::InitMode::ByTactility,
.canReinit = true, .canReinit = true,
.hasMutableConfiguration = true, .hasMutableConfiguration = true,
.config = (i2c_config_t) { .config = (i2c_config_t) {
@@ -29,6 +29,8 @@ bool CoreS3Display::start() {
.user_ctx = nullptr, .user_ctx = nullptr,
.lcd_cmd_bits = 8, .lcd_cmd_bits = 8,
.lcd_param_bits = 8, .lcd_param_bits = 8,
.cs_ena_pretrans = 0,
.cs_ena_posttrans = 0,
.flags = { .flags = {
.dc_high_on_cmd = 0, .dc_high_on_cmd = 0,
.dc_low_on_data = 0, .dc_low_on_data = 0,
@@ -119,7 +121,7 @@ bool CoreS3Display::start() {
} }
bool CoreS3Display::stop() { bool CoreS3Display::stop() {
tt_assert(displayHandle != nullptr); assert(displayHandle != nullptr);
lvgl_port_remove_disp(displayHandle); lvgl_port_remove_disp(displayHandle);
@@ -175,7 +177,7 @@ void CoreS3Display::setGammaCurve(uint8_t index) {
void CoreS3Display::setBacklightDuty(uint8_t backlightDuty) { void CoreS3Display::setBacklightDuty(uint8_t backlightDuty) {
const uint8_t voltage = 20 + ((8 * backlightDuty) / 255); // [0b00000, 0b11100] - under 20 is too dark const uint8_t voltage = 20 + ((8 * backlightDuty) / 255); // [0b00000, 0b11100] - under 20 is too dark
// TODO: Refactor to use Axp2102 class with https://github.com/m5stack/M5Unified/blob/b8cfec7fed046242da7f7b8024a4e92004a51ff7/src/utility/AXP2101_Class.cpp#L42 // TODO: Refactor to use Axp2102 class with https://github.com/m5stack/M5Unified/blob/b8cfec7fed046242da7f7b8024a4e92004a51ff7/src/utility/AXP2101_Class.cpp#L42
if (!tt::hal::i2c::masterWrite(I2C_NUM_0, AXP2101_ADDRESS, 0x99, &voltage, 1, 1000)) { if (!tt::hal::i2c::masterWriteRegister(I2C_NUM_0, AXP2101_ADDRESS, 0x99, &voltage, 1, 1000)) { // Sets DLD01
TT_LOG_E(TAG, "Failed to set display backlight voltage"); TT_LOG_E(TAG, "Failed to set display backlight voltage");
} }
} }
@@ -5,11 +5,11 @@
bool CoreS3Power::supportsMetric(MetricType type) const { bool CoreS3Power::supportsMetric(MetricType type) const {
switch (type) { switch (type) {
case BATTERY_VOLTAGE: case MetricType::BatteryVoltage:
case IS_CHARGING: case MetricType::IsCharging:
case CHARGE_LEVEL: case MetricType::ChargeLevel:
return true; return true;
case CURRENT: case MetricType::Current:
return false; return false;
} }
@@ -18,7 +18,7 @@ bool CoreS3Power::supportsMetric(MetricType type) const {
bool CoreS3Power::getMetric(Power::MetricType type, Power::MetricData& data) { bool CoreS3Power::getMetric(Power::MetricType type, Power::MetricData& data) {
switch (type) { switch (type) {
case BATTERY_VOLTAGE: { case MetricType::BatteryVoltage: {
float milliVolt; float milliVolt;
if (axpDevice.getBatteryVoltage(milliVolt)) { if (axpDevice.getBatteryVoltage(milliVolt)) {
data.valueAsUint32 = (uint32_t)milliVolt; data.valueAsUint32 = (uint32_t)milliVolt;
@@ -27,7 +27,7 @@ bool CoreS3Power::getMetric(Power::MetricType type, Power::MetricData& data) {
return false; return false;
} }
} }
case CHARGE_LEVEL: { case MetricType::ChargeLevel: {
float vbatMillis; float vbatMillis;
if (axpDevice.getBatteryVoltage(vbatMillis)) { if (axpDevice.getBatteryVoltage(vbatMillis)) {
float vbat = vbatMillis / 1000.f; float vbat = vbatMillis / 1000.f;
@@ -44,7 +44,7 @@ bool CoreS3Power::getMetric(Power::MetricType type, Power::MetricData& data) {
return false; return false;
} }
} }
case IS_CHARGING: { case MetricType::IsCharging: {
Axp2101::ChargeStatus status; Axp2101::ChargeStatus status;
if (axpDevice.getChargeStatus(status)) { if (axpDevice.getChargeStatus(status)) {
data.valueAsBool = (status == Axp2101::CHARGE_STATUS_CHARGING); data.valueAsBool = (status == Axp2101::CHARGE_STATUS_CHARGING);
@@ -53,7 +53,7 @@ bool CoreS3Power::getMetric(Power::MetricType type, Power::MetricData& data) {
return false; return false;
} }
} }
case CURRENT: case MetricType::Current:
return false; return false;
} }
@@ -5,18 +5,16 @@
#include <esp_vfs_fat.h> #include <esp_vfs_fat.h>
#define CORES3_SDCARD_SPI_FREQUENCY 800000U
#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
std::shared_ptr<SdCard> createSdCard() { std::shared_ptr<SdCard> createSdCard() {
auto* configuration = new tt::hal::SpiSdCard::Config( auto* configuration = new tt::hal::SpiSdCard::Config(
CORES3_SDCARD_SPI_FREQUENCY,
CORES3_SDCARD_PIN_CS, CORES3_SDCARD_PIN_CS,
GPIO_NUM_NC, GPIO_NUM_NC,
GPIO_NUM_NC, GPIO_NUM_NC,
GPIO_NUM_NC, GPIO_NUM_NC,
SdCard::MountBehaviourAtBoot, SdCard::MountBehaviour::AtBoot,
tt::lvgl::getLvglSyncLockable(), tt::lvgl::getLvglSyncLockable(),
{ {
CORES3_LCD_PIN_CS CORES3_LCD_PIN_CS
+9 -9
View File
@@ -10,8 +10,8 @@
#define TAG "lvgl_task" #define TAG "lvgl_task"
// Mutex for LVGL drawing // Mutex for LVGL drawing
static tt::Mutex lvgl_mutex(tt::Mutex::TypeRecursive); static tt::Mutex lvgl_mutex(tt::Mutex::Type::Recursive);
static tt::Mutex task_mutex(tt::Mutex::TypeRecursive); static tt::Mutex task_mutex(tt::Mutex::Type::Recursive);
static uint32_t task_max_sleep_ms = 10; static uint32_t task_max_sleep_ms = 10;
// Mutex for LVGL task state (to modify task_running state) // Mutex for LVGL task state (to modify task_running state)
@@ -19,12 +19,12 @@ static bool task_running = false;
static void lvgl_task(void* arg); static void lvgl_task(void* arg);
static bool task_lock(int timeout_ticks) { static bool task_lock(TickType_t timeout) {
return task_mutex.acquire(timeout_ticks) == tt::TtStatusOk; return task_mutex.lock(timeout);
} }
static void task_unlock() { static void task_unlock() {
task_mutex.release(); task_mutex.unlock();
} }
static void task_set_running(bool running) { static void task_set_running(bool running) {
@@ -41,15 +41,15 @@ bool lvgl_task_is_running() {
} }
static bool lvgl_lock(uint32_t timeoutMillis) { static bool lvgl_lock(uint32_t timeoutMillis) {
return lvgl_mutex.acquire(pdMS_TO_TICKS(timeoutMillis)) == tt::TtStatusOk; return lvgl_mutex.lock(pdMS_TO_TICKS(timeoutMillis));
} }
static void lvgl_unlock() { static void lvgl_unlock() {
lvgl_mutex.release(); lvgl_mutex.unlock();
} }
void lvgl_task_interrupt() { void lvgl_task_interrupt() {
tt_check(task_lock(tt::TtWaitForever)); tt_check(task_lock(portMAX_DELAY));
task_set_running(false); // interrupt task with boolean as flag task_set_running(false); // interrupt task with boolean as flag
task_unlock(); task_unlock();
} }
@@ -69,7 +69,7 @@ void lvgl_task_start() {
nullptr nullptr
); );
tt_assert(task_result == pdTRUE); assert(task_result == pdTRUE);
} }
lv_disp_t* displayHandle = nullptr; lv_disp_t* displayHandle = nullptr;
+1 -1
View File
@@ -33,7 +33,7 @@ void freertosMain() {
nullptr nullptr
); );
tt_assert(task_result == pdTRUE); assert(task_result == pdTRUE);
// Blocks forever // Blocks forever
vTaskStartScheduler(); vTaskStartScheduler();
@@ -36,7 +36,7 @@ extern const tt::hal::Configuration hardware = {
tt::hal::i2c::Configuration { tt::hal::i2c::Configuration {
.name = "Internal", .name = "Internal",
.port = I2C_NUM_0, .port = I2C_NUM_0,
.initMode = tt::hal::i2c::InitByTactility, .initMode = tt::hal::i2c::InitMode::ByTactility,
.canReinit = false, .canReinit = false,
.hasMutableConfiguration = false, .hasMutableConfiguration = false,
.config = (i2c_config_t) { .config = (i2c_config_t) {
@@ -54,7 +54,7 @@ extern const tt::hal::Configuration hardware = {
tt::hal::i2c::Configuration { tt::hal::i2c::Configuration {
.name = "External", .name = "External",
.port = I2C_NUM_1, .port = I2C_NUM_1,
.initMode = tt::hal::i2c::InitByTactility, .initMode = tt::hal::i2c::InitMode::ByTactility,
.canReinit = true, .canReinit = true,
.hasMutableConfiguration = true, .hasMutableConfiguration = true,
.config = (i2c_config_t) { .config = (i2c_config_t) {
@@ -4,10 +4,10 @@
bool SimulatorPower::supportsMetric(MetricType type) const { bool SimulatorPower::supportsMetric(MetricType type) const {
switch (type) { switch (type) {
case IS_CHARGING: case MetricType::IsCharging:
case CURRENT: case MetricType::Current:
case BATTERY_VOLTAGE: case MetricType::BatteryVoltage:
case CHARGE_LEVEL: case MetricType::ChargeLevel:
return true; return true;
} }
@@ -16,16 +16,16 @@ bool SimulatorPower::supportsMetric(MetricType type) const {
bool SimulatorPower::getMetric(Power::MetricType type, Power::MetricData& data) { bool SimulatorPower::getMetric(Power::MetricType type, Power::MetricData& data) {
switch (type) { switch (type) {
case IS_CHARGING: case MetricType::IsCharging:
data.valueAsBool = true; data.valueAsBool = true;
return true; return true;
case CURRENT: case MetricType::Current:
data.valueAsInt32 = 42; data.valueAsInt32 = 42;
return true; return true;
case BATTERY_VOLTAGE: case MetricType::BatteryVoltage:
data.valueAsUint32 = 4032; data.valueAsUint32 = 4032;
return true; return true;
case CHARGE_LEVEL: case MetricType::ChargeLevel:
data.valueAsUint8 = 100; data.valueAsUint8 = 100;
return true; return true;
} }
@@ -8,14 +8,14 @@ class SimulatorSdCard : public SdCard {
private: private:
State state; State state;
public: public:
SimulatorSdCard() : SdCard(MountBehaviourAtBoot), state(StateUnmounted) {} SimulatorSdCard() : SdCard(MountBehaviour::AtBoot), state(State::Unmounted) {}
bool mount(const char* mountPath) override { bool mount(const char* mountPath) override {
state = StateMounted; state = State::Mounted;
return true; return true;
} }
bool unmount() override { bool unmount() override {
state = StateUnmounted; state = State::Unmounted;
return true; return true;
} }
+5
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
)
+47
View File
@@ -0,0 +1,47 @@
#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,
.data_io_default_level = false,
.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();
}
+33
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;
}
+193
View File
@@ -0,0 +1,193 @@
#include "Preferences.h"
#include "TactilityCore.h"
#include "UnPhoneFeatures.h"
#include <esp_sleep.h>
#define TAG "unphone"
extern UnPhoneFeatures unPhoneFeatures;
static std::unique_ptr<tt::Thread> powerThread;
static const char* bootCountKey = "boot_count";
static const char* powerOffCountKey = "power_off_count";
static const char* powerSleepKey = "power_sleep_key";
class DeviceStats {
private:
tt::Preferences preferences = tt::Preferences("unphone");
int32_t getValue(const char* key) {
int32_t value = 0;
preferences.optInt32(key, value);
return value;
}
void setValue(const char* key, int32_t value) {
preferences.putInt32(key, value);
}
void increaseValue(const char* key) {
int32_t new_value = getValue(key) + 1;
setValue(key, new_value);
}
public:
void notifyBootStart() {
increaseValue(bootCountKey);
}
void notifyPowerOff() {
increaseValue(powerOffCountKey);
}
void notifyPowerSleep() {
increaseValue(powerSleepKey);
}
void printInfo() {
TT_LOG_I("TAG", "Device stats:");
TT_LOG_I("TAG", " boot: %ld", getValue(bootCountKey));
TT_LOG_I("TAG", " power off: %ld", getValue(powerOffCountKey));
TT_LOG_I("TAG", " power sleep: %ld", getValue(powerSleepKey));
}
};
DeviceStats bootStats;
enum class PowerState {
Initial,
On,
Off
};
#define DEBUG_POWER_STATES false
#if DEBUG_POWER_STATES
/** Helper method to use the buzzer to signal the different power stages */
static void powerInfoBuzz(uint8_t count) {
if (DEBUG_POWER_STATES) {
uint8_t index = 0;
while (index < count) {
unPhoneFeatures.setVibePower(true);
tt::kernel::delayMillis(50);
unPhoneFeatures.setVibePower(false);
index++;
if (index < count) {
tt::kernel::delayMillis(100);
}
}
}
}
#endif
static void updatePowerSwitch() {
static PowerState last_state = PowerState::Initial;
if (!unPhoneFeatures.isPowerSwitchOn()) {
if (last_state != PowerState::Off) {
last_state = PowerState::Off;
TT_LOG_W(TAG, "Power off");
}
if (!unPhoneFeatures.isUsbPowerConnected()) { // and usb unplugged we go into shipping mode
TT_LOG_W(TAG, "Shipping mode until USB connects");
#if DEBUG_POWER_STATES
unPhoneFeatures.setExpanderPower(true);
powerInfoBuzz(3);
unPhoneFeatures.setExpanderPower(false);
#endif
unPhoneFeatures.turnPeripheralsOff();
bootStats.notifyPowerOff();
unPhoneFeatures.setShipping(true); // tell BM to stop supplying power until USB connects
} else { // When power switch is off, but USB is plugged in, we wait (deep sleep) until USB is unplugged.
TT_LOG_W(TAG, "Waiting for USB disconnect to power off");
#if DEBUG_POWER_STATES
powerInfoBuzz(2);
#endif
unPhoneFeatures.turnPeripheralsOff();
bootStats.notifyPowerSleep();
// Deep sleep for 1 minute, then awaken to check power state again
// GPIO trigger from power switch also awakens the device
unPhoneFeatures.wakeOnPowerSwitch();
esp_sleep_enable_timer_wakeup(60000000);
esp_deep_sleep_start();
}
} else {
if (last_state != PowerState::On) {
last_state = PowerState::On;
TT_LOG_W(TAG, "Power on");
#if DEBUG_POWER_STATES
powerInfoBuzz(1);
#endif
}
}
}
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() {
// Print early, in case of early crash (info will be from previous boot)
bootStats.printInfo();
bootStats.notifyBootStart();
if (!unPhoneFeatures.init()) {
TT_LOG_E(TAG, "UnPhoneFeatures init failed");
return false;
}
unPhoneFeatures.printInfo();
unPhoneFeatures.setBacklightPower(false);
unPhoneFeatures.setVibePower(false);
unPhoneFeatures.setIrPower(false);
unPhoneFeatures.setExpanderPower(false);
// Turn off the device if power switch is on off state,
// instead of waiting for the Thread to start and continue booting
updatePowerSwitch();
startPowerSwitchThread();
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;
}
+59
View File
@@ -0,0 +1,59 @@
#include "UnPhoneFeatures.h"
#include "hal/Configuration.h"
#include "hal/UnPhoneDisplay.h"
#include "hal/UnPhonePower.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(),
.power = unPhoneGetPower,
.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
}
}
}
};
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <hal/Configuration.h>
extern const tt::hal::Configuration unPhone;
+301
View File
@@ -0,0 +1,301 @@
#include "UnPhoneFeatures.h"
#include "FreeRTOS-Kernel/include/FreeRTOS.h"
#include "Log.h"
#include "kernel/Kernel.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)) {
// The buttons might generate more than 1 click because of how they are built
TT_LOG_I(TAG, "Pressed button %d", pinNumber);
if (pinNumber == pin::BUTTON1) {
tt::service::loader::stopApp();
}
// Debounce all events for a short period of time
// This is easier than keeping track when each button was last pressed
tt::kernel::delayMillis(50);
xQueueReset(interruptQueue);
tt::kernel::delayMillis(50);
xQueueReset(interruptQueue);
}
}
return 0;
}
UnPhoneFeatures::~UnPhoneFeatures() {
if (buttonHandlingThread.getState() != tt::Thread::State::Stopped) {
buttonHandlingThreadInterruptRequest = true;
buttonHandlingThread.join();
}
}
bool UnPhoneFeatures::initPowerSwitch() {
gpio_config_t config = {
.pin_bit_mask = BIT64(pin::POWER_SWITCH),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_POSEDGE,
};
if (gpio_config(&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() {
if (!initGpioExpander()) {
TT_LOG_E(TAG, "GPIO expander init failed");
return false;
}
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 pin_mask =
BIT64(pin::BUTTON1) |
BIT64(pin::BUTTON2) |
BIT64(pin::BUTTON3);
gpio_config_t config = {
.pin_bit_mask = pin_mask,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
/**
* We have to listen to the button release (= positive signal).
* If we listen to button press, the buttons might create more than 1 signal
* when they are continuously pressed.
*/
.intr_type = GPIO_INTR_POSEDGE,
};
if (gpio_config(&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 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(&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
/**
* Important:
* If you clear the pins too late, the display or vibration motor might briefly turn on.
*/
esp_io_expander_set_dir(ioExpander, expanderpin::BACKLIGHT, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(ioExpander, expanderpin::BACKLIGHT, 0);
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_level(ioExpander, expanderpin::LED_GREEN, 0);
esp_io_expander_set_dir(ioExpander, expanderpin::LED_BLUE, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(ioExpander, expanderpin::LED_BLUE, 0);
esp_io_expander_set_dir(ioExpander, expanderpin::VIBE, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(ioExpander, expanderpin::VIBE, 0);
// 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 (!initGpioExpander()) {
TT_LOG_E(TAG, "GPIO expander init failed");
return false;
}
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;
}
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");
batteryManagement.setWatchDogTimer(Bq24295::WatchDogTimer::Disabled);
batteryManagement.setBatFetOn(false);
} else {
TT_LOG_W(TAG, "setShipping: off");
batteryManagement.setWatchDogTimer(Bq24295::WatchDogTimer::Enabled40s);
batteryManagement.setBatFetOn(true);
}
return true;
}
void UnPhoneFeatures::wakeOnPowerSwitch() const {
esp_sleep_enable_ext0_wakeup(pin::POWER_SWITCH, 1);
}
bool UnPhoneFeatures::isUsbPowerConnected() const {
return batteryManagement.isUsbPowerConnected();
}
+51
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;
};
+105
View File
@@ -0,0 +1,105 @@
#include "Bq24295.h"
#include "Log.h"
#define TAG "bq24295"
/** Reference:
* https://www.ti.com/lit/ds/symlink/bq24295.pdf
* https://gitlab.com/hamishcunningham/unphonelibrary/-/blob/main/unPhone.h?ref_type=heads
*/
namespace registers {
static const uint8_t CHARGE_TERMINATION = 0x05U; // Datasheet page 35: Charge end/timer cntrl
static const uint8_t OPERATION_CONTROL = 0x07U; // Datasheet page 37: Misc operation control
static const uint8_t STATUS = 0x08U; // Datasheet page 38: System status
static const uint8_t VERSION = 0x0AU; // Datasheet page 38: Vendor/part/revision status
} // namespace registers
bool Bq24295::readChargeTermination(uint8_t& out) const {
return readRegister8(registers::CHARGE_TERMINATION, out);
}
// region Watchdog
bool Bq24295::getWatchDogTimer(WatchDogTimer& out) const {
uint8_t value;
if (readChargeTermination(value)) {
uint8_t relevant_bits = value & (BIT(4) | BIT(5));
switch (relevant_bits) {
case 0b000000:
out = WatchDogTimer::Disabled;
return true;
case 0b010000:
out = WatchDogTimer::Enabled40s;
return true;
case 0b100000:
out = WatchDogTimer::Enabled80s;
return true;
case 0b110000:
out = WatchDogTimer::Enabled160s;
return true;
default:
return false;
}
}
return false;
}
bool Bq24295::setWatchDogTimer(WatchDogTimer in) const {
uint8_t value;
if (readChargeTermination(value)) {
uint8_t bits_to_set = 0b00110000 & static_cast<uint8_t>(in);
uint8_t value_cleared = value & 0b11001111;
uint8_t to_set = bits_to_set & value_cleared;
TT_LOG_I(TAG, "WatchDogTimer: %02x -> %02x", value, to_set);
return writeRegister8(registers::CHARGE_TERMINATION, to_set);
}
return false;
}
// endregoin
// region Operation Control (REG07)
bool Bq24295::setBatFetOn(bool on) const {
if (on) {
// bit 5 low means bat fet is on
return bitOff(registers::OPERATION_CONTROL, BIT(5));
} else {
// bit 5 high means bat fet is off
return bitOn(registers::OPERATION_CONTROL, BIT(5));
}
}
// endregion
// region Other
bool Bq24295::getStatus(uint8_t& value) const {
return readRegister8(registers::STATUS, value);
}
bool Bq24295::isUsbPowerConnected() const {
uint8_t status;
if (getStatus(status)) {
return (status & BIT(2)) != 0U;
} else {
return false;
}
}
bool Bq24295::getVersion(uint8_t& value) const {
return readRegister8(registers::VERSION, value);
}
void Bq24295::printInfo() const {
uint8_t version, status, charge_termination;
if (getStatus(status) && getVersion(version) && readChargeTermination(charge_termination)) {
TT_LOG_I(TAG, "Version %d, status %02x, charge termination %02x", version, status, charge_termination);
} else {
TT_LOG_E(TAG, "Failed to retrieve version and/or status");
}
}
// endregion
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include "hal/i2c/I2cDevice.h"
#define BQ24295_ADDRESS 0x6BU
class Bq24295 : I2cDevice {
private:
bool readChargeTermination(uint8_t& out) const;
public:
enum class WatchDogTimer {
Disabled = 0b000000,
Enabled40s = 0b010000,
Enabled80s = 0b100000,
Enabled160s = 0b110000
};
explicit Bq24295(i2c_port_t port) : I2cDevice(port, BQ24295_ADDRESS) {}
bool getWatchDogTimer(WatchDogTimer& out) const;
bool setWatchDogTimer(WatchDogTimer in) const;
bool isUsbPowerConnected() const;
bool setBatFetOn(bool on) const;
bool getStatus(uint8_t& value) const;
bool getVersion(uint8_t& value) const;
void printInfo() const;
};
@@ -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() {
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());
}
@@ -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();
@@ -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)
@@ -0,0 +1,92 @@
#include "UnPhonePower.h"
#include "UnPhoneTouch.h"
#include "Log.h"
#define TAG "unphone_power"
#define BATTERY_VOLTAGE_MIN 3.2f
#define BATTERY_VOLTAGE_MAX 4.2f
static uint8_t estimateChargeLevelFromVoltage(uint32_t milliVolt) {
float volts = TT_MIN((float)milliVolt / 1000.f, BATTERY_VOLTAGE_MAX);
float voltage_percentage = (volts - BATTERY_VOLTAGE_MIN) / (BATTERY_VOLTAGE_MAX - BATTERY_VOLTAGE_MIN);
float voltage_factor = TT_MIN(1.0f, voltage_percentage);
auto charge_level = (uint8_t) (voltage_factor * 100.f);
TT_LOG_V(TAG, "mV = %lu, scaled = %.2f, factor = %.2f, result = %d", milliVolt, volts, voltage_factor, charge_level);
return charge_level;
}
bool UnPhonePower::supportsMetric(MetricType type) const {
switch (type) {
case MetricType::BatteryVoltage:
case MetricType::ChargeLevel:
return true;
default:
return false;
}
}
bool UnPhonePower::getMetric(Power::MetricType type, Power::MetricData& data) {
switch (type) {
case MetricType::BatteryVoltage:
return readBatteryVoltageSampled(data.valueAsUint32);
case MetricType::ChargeLevel: {
uint32_t milli_volt;
if (readBatteryVoltageSampled(milli_volt)) {
data.valueAsUint8 = estimateChargeLevelFromVoltage(milli_volt);
return true;
} else {
return false;
}
}
default:
return false;
}
}
bool UnPhonePower::readBatteryVoltageOnce(uint32_t& output) const {
auto* touch = UnPhoneTouch::getInstance();
if (touch != nullptr) {
float vbat;
if (touch->getVBat(vbat)) {
// Convert to mV
output = (uint32_t)(vbat * 1000.f);
return true;
}
}
return false;
}
#define MAX_VOLTAGE_SAMPLES 15
bool UnPhonePower::readBatteryVoltageSampled(uint32_t& output) const {
size_t samples_read = 0;
uint32_t sample_accumulator = 0;
uint32_t sample_read_buffer;
for (size_t i = 0; i < MAX_VOLTAGE_SAMPLES; ++i) {
if (readBatteryVoltageOnce(sample_read_buffer)) {
sample_accumulator += sample_read_buffer;
samples_read++;
}
}
if (samples_read > 0) {
output = sample_accumulator / samples_read;
return true;
} else {
return false;
}
}
static std::shared_ptr<Power> power;
std::shared_ptr<Power> unPhoneGetPower() {
if (power == nullptr) {
power = std::make_shared<UnPhonePower>();
}
return power;
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "hal/Power.h"
#include <memory>
using namespace tt::hal;
class UnPhonePower : public Power {
public:
UnPhonePower() = default;
~UnPhonePower() = default;
bool supportsMetric(MetricType type) const override;
bool getMetric(Power::MetricType type, Power::MetricData& data) override;
private:
bool readBatteryVoltageOnce(uint32_t& output) const;
bool readBatteryVoltageSampled(uint32_t& output) const;
};
std::shared_ptr<Power> unPhoneGetPower();
@@ -0,0 +1,33 @@
#include "UnPhoneSdCard.h"
#include "lvgl/LvglSync.h"
#include "hal/SpiSdCard.h"
#include <esp_vfs_fat.h>
#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_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);
}
@@ -0,0 +1,7 @@
#pragma once
#include "hal/SdCard.h"
using namespace tt::hal;
std::shared_ptr<SdCard> createUnPhoneSdCard();
+100
View File
@@ -0,0 +1,100 @@
#include "UnPhoneTouch.h"
#include "Log.h"
#include "esp_err.h"
#include "esp_lcd_touch_xpt2046.h"
#include "esp_lvgl_port.h"
#include "lvgl/LvglSync.h"
#define TAG "unphone_touch"
#define UNPHONE_TOUCH_X_MAX 320
#define UNPHONE_TOUCH_Y_MAX 480
UnPhoneTouch* UnPhoneTouch::instance = nullptr;
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;
}
instance = this;
return true;
}
bool UnPhoneTouch::stop() {
instance = nullptr;
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;
}
}
bool UnPhoneTouch::getVBat(float& outputVbat) {
if (touchHandle != nullptr) {
// Shares the SPI bus with the display, so we have to sync/lock as this method might be called from anywhere
if (tt::lvgl::lock(50 / portTICK_PERIOD_MS)) {
esp_lcd_touch_xpt2046_read_battery_level(touchHandle, &outputVbat);
tt::lvgl::unlock();
return true;
}
}
return false;
}
+29
View File
@@ -0,0 +1,29 @@
#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:
static UnPhoneTouch* instance;
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; }
bool getVBat(float& outputVbat);
/** Used for accessing getVBat() in Power driver */
static UnPhoneTouch* getInstance() { return instance; }
};
+4
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.
+315
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 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);
}
}
+81
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*/
+292
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);
}
+133
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*/
+7
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
-2
View File
@@ -6,8 +6,6 @@ endfunction()
macro(tactility_project project_name) macro(tactility_project project_name)
set(TACTILITY_SKIP_SPIFFS 1) set(TACTILITY_SKIP_SPIFFS 1)
add_definitions(-DESP_TARGET)
add_compile_definitions(ESP_TARGET)
include("${TACTILITY_SDK_PATH}/Libraries/elf_loader/elf_loader.cmake") include("${TACTILITY_SDK_PATH}/Libraries/elf_loader/elf_loader.cmake")
project_elf($project_name) project_elf($project_name)
+7 -7
View File
@@ -8,13 +8,13 @@ set(CMAKE_ASM_COMPILE_OBJECT "${CMAKE_CXX_COMPILER_TARGET}")
include("Buildscripts/logo.cmake") include("Buildscripts/logo.cmake")
file(READ version.txt TACTILITY_VERSION)
add_compile_definitions(TT_VERSION="${TACTILITY_VERSION}")
if (DEFINED ENV{ESP_IDF_VERSION}) if (DEFINED ENV{ESP_IDF_VERSION})
message("Building with ESP-IDF v$ENV{ESP_IDF_VERSION}") message("Building with ESP-IDF v$ENV{ESP_IDF_VERSION}")
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
add_definitions(-DESP_TARGET)
add_compile_definitions(ESP_TARGET)
set(COMPONENTS App) set(COMPONENTS App)
set(EXTRA_COMPONENT_DIRS set(EXTRA_COMPONENT_DIRS
"Boards" "Boards"
@@ -34,7 +34,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
# Non-ESP32 boards # Non-ESP32 boards
if(NOT "${IDF_TARGET}" STREQUAL "esp32") if(NOT "${IDF_TARGET}" STREQUAL "esp32")
set(EXCLUDE_COMPONENTS "YellowBoard") set(EXCLUDE_COMPONENTS "CYD-2432S024C")
set(EXCLUDE_COMPONENTS "M5stackCore2") set(EXCLUDE_COMPONENTS "M5stackCore2")
endif() endif()
@@ -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
@@ -52,11 +53,10 @@ if (DEFINED ENV{ESP_IDF_VERSION})
add_compile_definitions(LV_CONF_PATH="${LVGL_CONFIG_FULL_PATH}/lv_conf_kconfig.h") add_compile_definitions(LV_CONF_PATH="${LVGL_CONFIG_FULL_PATH}/lv_conf_kconfig.h")
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_panic_handler" APPEND) idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_panic_handler" APPEND)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_log_write" APPEND) idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_log_write" APPEND)
file(READ version.txt TACTILITY_VERSION)
add_compile_definitions(TT_VERSION="${TACTILITY_VERSION}")
else() else()
message("Building for sim target") message("Building for sim target")
add_compile_definitions(CONFIG_TT_BOARD_ID="simulator")
add_compile_definitions(CONFIG_TT_BOARD_NAME="Simulator")
endif() endif()
project(Tactility) project(Tactility)
+8 -2
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
@@ -49,7 +51,7 @@ void getLimit() {
Preprocessor functions are written in snake-case and prefixed with `tt_`, for example: Preprocessor functions are written in snake-case and prefixed with `tt_`, for example:
```c++ ```c++
#define tt_assert(x) __tt_assert_internal(x) #define tt_check(x) if (!(x)) { /* .. */ }
``` ```
### Type names ### Type names
@@ -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.
+48 -7
View File
@@ -1,11 +1,52 @@
# Note # Contributing
During the pre-alpha stage, the APIs are changing rapidly. ## Accepted changes
I don't want to disappoint people with huge merge conflicts, so new features are not accepted yet.
I welcome fixes and possibly also board implementations: Before releasing version 1.0.0, the APIs are changing rapidly.
Please open an [Issue](https://github.com/ByteWelder/Tactility/issues/new) on GitHub to discuss first new boards! I want to minimize the amount of changes that I have to maintain, because it limits the amount of breaking changes that I have to deal with when the APIs change.
# Code Style ### New features
See [this document](CODING_STYLE.md). These are currently not accepted.
### Visual / interaction design changes
These are currently not accepted. I'm aware that there is a lot of room for improvement, but I want to mainly focusing on strong technical foundations before
I start building further on top of that.
Feel free to open an [issue](https://github.com/ByteWelder/Tactility/issues/new) to discuss ideas you have, if you feel like they will have a considerable impact.
### Fixing things
The general take here is that minor changes are not accepted at this stage. I don't want to spend my time reviewing and discussing these during the current stage of the project.
Only fixes for serious issues are accepted, like:
- Bugs
- Crashes
- Security issues
Some examples of non-serious issues include:
- Documentation changes
- Renaming code
- Fixes for compiler warnings that have low impact on the actual application logic (e.g. only happens on simulator when calling a logging function)
### New board implementations
I only support boards that I also own. If I don't own a board, I can't properly test the reliability of the firmware.
If you wish to send me a board, please [contact me](https://tactility.one/#/support).
Please open an [issue](https://github.com/ByteWelder/Tactility/issues/new) on GitHub to discuss new boards.
If you implemented a board yourself, I'm willing to refer to your implementation on the main website. Please [contact me](https://tactility.one/#/support).
### Anything that doesn't fall in the above categories?
Please [contact me](https://tactility.one/#/support) me first!
## Pull Requests
Pull requests should only contain a single set of changes that are related to eachother.
That way, an approved set of changes will not be blocked by an unapproved set of changes.
## Code Style
See [this document](CODING_STYLE.md) and [.clang-format](.clang-format).
+16 -27
View File
@@ -1,36 +1,24 @@
# Issues
- WiFi bug: when pressing disconnect while between `WIFI_EVENT_STA_START` and `IP_EVENT_STA_GOT_IP`, then auto-connect becomes active again.
- ESP32 (CYD) memory issues (or any device without PSRAM):
- Boot app doesn't show logo
- WiFi is on and navigating back to Desktop makes desktop icons disappear
- WiFi might fail quietly when trying to enable it: this shows no feedback (force it by increasing LVGL buffers to 100kB)
Possible mitigations:
- When no PSRAM is available, use simplified desktop buttons
- Add statusbar icon for memory pressure.
- Show error in WiFi screen (e.g. AlertDialog when SPI is not enabled and available memory is below a certain amount)
- Clean up static_cast when casting to base class.
- M5Stack CoreS3 SD card mounts, but cannot be read. There is currently a notice about it [here](https://github.com/espressif/esp-bsp/blob/master/bsp/m5stack_core_s3/README.md).
- EventFlag: Fix return value of set/get/wait (the errors are weirdly mixed in)
- Consistently use either ESP_TARGET or ESP_PLATFORM
- 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
# TODOs # TODOs
- 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.
- Clean up static_cast when casting to base class.
- Mutex: Implement give/take from ISR support (works only for non-recursive ones)
- Extend unPhone power driver: add charging status, usb connection status, etc.
- Expose app::Paths to TactilityC
- Experiment with what happens when using C++ code in an external app (without using standard library!)
- Boards' CMakeLists.txt manually declare each source folder. Update them all to do a recursive search of all folders.
- We currently build 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) {
- 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.
- Add toggle to Display app for sysmon overlay: https://docs.lvgl.io/master/API/others/sysmon/index.html - Add toggle to Display app for sysmon overlay: https://docs.lvgl.io/master/API/others/sysmon/index.html
- CrashHandler: use "corrupted" flag - CrashHandler: use "corrupted" flag
- CrashHandler: process other types of crashes (WDT?) - CrashHandler: process other types of crashes (WDT?)
- Call tt::lvgl::isSyncSet after HAL init and show error (and crash?) when it is not set. - Call tt::lvgl::isSyncSet after HAL init and show error (and crash?) when it is not set.
- Create different partitions files for different ESP flash size targets (N4, N8, N16, N32) - Create different partitions files for different ESP flash size targets (N4, N8, N16, N32)
- Attach ELF data to wrapper app (as app data) (check that app state is "running"!) so you can run more than 1 external apps at a time.
We'll need to keep track of all manifest instances, so that the wrapper can look up the relevant manifest for the relevant callbacks.
- T-Deck: Clear screen before turning on blacklight - T-Deck: Clear screen before turning on blacklight
- T-Deck: Use knob for UI selection - T-Deck: Use knob for UI selection?
- Crash monitoring: Keep track of which system phase the app crashed in (e.g. which app in which state) - Crash monitoring: Keep track of which system phase the app crashed in (e.g. which app in which state)
- AppContext's onResult should pass the app id (or launch request id!) that was started, so we can differentiate between multiple types of apps being launched - App::onResult should pass the app id (or launch request id!) that was started, so we can differentiate between multiple types of apps being launched
- Create more unit tests for `tactility-core` and `tactility` (PC-only for now) - Create more unit tests for `tactility-core` and `tactility` (PC-only for now)
- Show a warning screen if firmware encryption or secure boot are off when saving WiFi credentials. - Show a warning screen if firmware encryption or secure boot are off when saving WiFi credentials.
- Show a warning screen when a user plugs in the SD card on a device that only supports mounting at boot. - Show a warning screen when a user plugs in the SD card on a device that only supports mounting at boot.
@@ -38,19 +26,18 @@
- Explore LVGL9's FreeRTOS functionality - Explore LVGL9's FreeRTOS functionality
- External app loading: Check version of Tactility and check ESP target hardware, to check for compatibility. - External app loading: Check version of Tactility and check ESP target hardware, to check for compatibility.
- Scanning SD card for external apps and auto-register them (in a temporary register?) - Scanning SD card for external apps and auto-register them (in a temporary register?)
- tt::app::start() and similar functions as proxies for Loader app start/stop/etc.
- Support hot-plugging SD card - Support hot-plugging SD card
# Nice-to-haves # Nice-to-haves
- CoreS3 has a hardware issue that prevents mounting SD cards while using the display too: allow USB Mass Storage to use `/data` instead? Perhaps give the USB settings app a drop down to select the root filesystem to attach.
- Give external app a different icon. Allow an external app update their id, icon, type and name once they are running(, and persist that info?). Loader will need to be able to find app by (external) location.
- Audio player app - Audio player app
- Audio recording app - Audio recording app
- OTA updates - OTA updates
- Web flasher
- T-Deck Plus: Create separate board config? - T-Deck Plus: Create separate board config?
- Support for displays with different DPI. Consider the layer-based system like on Android. - Support for displays with different DPI. Consider the layer-based system like on Android.
- Make firmwares available via web serial website
- If present, use LED to show boot/wifi status - If present, use LED to show boot/wifi status
- T-Deck Power: capacity estimation uses linear voltage curve, but it should use some sort of battery discharge curve. - Capacity based on voltage: estimation for various devices uses linear voltage curve, but it should use some sort of battery discharge curve.
- Statusbar widget to show how much memory is in use? - Statusbar widget to show how much memory is in use?
- Wrapper for Slider that shows "+" and "-" buttons, and also the value in a label. - Wrapper for Slider that shows "+" and "-" buttons, and also the value in a label.
- Display app: Add toggle to display performance measurement overlay (consider showing FPS in statusbar!) - Display app: Add toggle to display performance measurement overlay (consider showing FPS in statusbar!)
@@ -58,6 +45,8 @@
- On crash, try to save current log to flash or SD card? (this is risky, though, so ask in Discord first) - On crash, try to save current log to flash or SD card? (this is risky, though, so ask in Discord first)
# App Ideas # App Ideas
- Weather app: https://lab.flipper.net/apps/flip_weather
- wget app: https://lab.flipper.net/apps/web_crawler (add profiles for known public APIs?)
- USB implementation to make device act as mass storage device. - USB implementation to make device act as mass storage device.
- System logger - System logger
- BlueTooth keyboard app - BlueTooth keyboard app
+8 -11
View File
@@ -5,8 +5,8 @@
* Note: LVGL and Tactility methods need to be exposed manually from TactilityC/Source/tt_init.cpp * Note: LVGL and Tactility methods need to be exposed manually from TactilityC/Source/tt_init.cpp
* Only C is supported for now (C++ symbols fail to link) * Only C is supported for now (C++ symbols fail to link)
*/ */
static void onShow(AppContextHandle context, lv_obj_t* parent) { static void onShow(AppHandle app, void* data, lv_obj_t* parent) {
lv_obj_t* toolbar = tt_lvgl_toolbar_create(parent, context); lv_obj_t* toolbar = tt_lvgl_toolbar_create(parent, app);
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t* label = lv_label_create(parent); lv_obj_t* label = lv_label_create(parent);
@@ -14,15 +14,12 @@ static void onShow(AppContextHandle context, lv_obj_t* parent) {
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
} }
ExternalAppManifest manifest = {
.name = "Hello World",
.onShow = onShow
};
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
tt_set_app_manifest( tt_app_register(&manifest);
"Hello World",
NULL,
NULL,
NULL,
onShow,
NULL,
NULL
);
return 0; return 0;
} }
+1 -11
View File
@@ -848,20 +848,10 @@ int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_
} }
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) { bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) {
if (x < 0 || x >= qrcode->size || y < 0 || y >= qrcode->size) { if (x >= qrcode->size || y >= qrcode->size) {
return false; return false;
} }
uint32_t offset = y * qrcode->size + x; uint32_t offset = y * qrcode->size + x;
return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
} }
/*
uint8_t qrcode_getHexLength(QRCode *qrcode) {
return ((qrcode->size * qrcode->size) + 7) / 4;
}
void qrcode_getHex(QRCode *qrcode, char *result) {
}
*/
-2
View File
@@ -4,8 +4,6 @@ Tactility is an operating system that focuses on the ESP32 microcontroller famil
See [https://tactility.one](https://tactility.one) for more information. See [https://tactility.one](https://tactility.one) for more information.
Status: pre-release
![photo of devices running Tactility](Documentation/pics/tactility-devices.webp)&nbsp;&nbsp;![screenshot of desktop app](Documentation/pics/screenshot-Desktop.png) ![photo of devices running Tactility](Documentation/pics/tactility-devices.webp)&nbsp;&nbsp;![screenshot of desktop app](Documentation/pics/screenshot-Desktop.png)
You can run built-in apps or start them from an SD card: You can run built-in apps or start them from an SD card:
+17
View File
@@ -0,0 +1,17 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| main branch | :white_check_mark: |
## Reporting a Vulnerability
We appreciate your efforts to responsibly disclose your findings, and we will make every effort to acknowledge your contributions.
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/bytewelder/tactility/security/advisories/new) tab.
You can expect a response indicating the next steps in handling your report. After the initial reply to your report, you'll be kept informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
Report security bugs in third-party dependencies to the person or team maintaining the module.
+37 -38
View File
@@ -1,9 +1,10 @@
#pragma once #pragma once
#include "app/AppContext.h"
#include "app/AppManifest.h"
#include "Bundle.h" #include "Bundle.h"
#include "Mutex.h" #include "Mutex.h"
#include "app/AppContext.h"
#include "app/AppManifest.h"
#include "app/ElfApp.h"
#include <memory> #include <memory>
#include <utility> #include <utility>
@@ -17,19 +18,6 @@ typedef enum {
StateStopped // App is not in memory StateStopped // App is not in memory
} State; } State;
struct ResultHolder {
Result result;
std::shared_ptr<const Bundle> resultData;
explicit ResultHolder(Result result) : result(result), resultData(nullptr) {}
ResultHolder(Result result, std::shared_ptr<const Bundle> resultData) :
result(result),
resultData(std::move(resultData))
{}
};
/** /**
* Thread-safe app instance. * Thread-safe app instance.
*/ */
@@ -37,8 +25,8 @@ class AppInstance : public AppContext {
private: private:
Mutex mutex = Mutex(Mutex::TypeNormal); Mutex mutex = Mutex(Mutex::Type::Normal);
const AppManifest& manifest; const std::shared_ptr<AppManifest> manifest;
State state = StateInitial; State state = StateInitial;
Flags flags = { .showStatusbar = true }; Flags flags = { .showStatusbar = true };
/** @brief Optional parameters to start the app with /** @brief Optional parameters to start the app with
@@ -46,22 +34,40 @@ private:
* Do not mutate after app creation. * Do not mutate after app creation.
*/ */
std::shared_ptr<const tt::Bundle> _Nullable parameters; std::shared_ptr<const tt::Bundle> _Nullable parameters;
/** @brief @brief Contextual data related to the running app's instance
* The app can attach its data to this. std::shared_ptr<App> app;
* The lifecycle is determined by the on_start and on_stop methods in the AppManifest.
* These manifest methods can optionally allocate/free data that is attached here. static std::shared_ptr<app::App> createApp(
*/ const std::shared_ptr<app::AppManifest>& manifest
std::shared_ptr<void> _Nullable data; ) {
std::unique_ptr<ResultHolder> _Nullable resultHolder; if (manifest->location.isInternal()) {
assert(manifest->createApp != nullptr);
return manifest->createApp();
} else if (manifest->location.isExternal()) {
if (manifest->createApp != nullptr) {
TT_LOG_W("", "Manifest specifies createApp, but this is not used with external apps");
}
#ifdef ESP_PLATFORM
return app::createElfApp(manifest);
#else
tt_crash("not supported");
#endif
} else {
tt_crash("not implemented");
}
}
public: public:
explicit AppInstance(const AppManifest& manifest) : explicit AppInstance(const std::shared_ptr<AppManifest>& manifest) :
manifest(manifest) {}
AppInstance(const AppManifest& manifest, std::shared_ptr<const Bundle> parameters) :
manifest(manifest), manifest(manifest),
parameters(std::move(parameters)) {} app(createApp(manifest))
{}
AppInstance(const std::shared_ptr<AppManifest>& manifest, std::shared_ptr<const Bundle> parameters) :
manifest(manifest),
parameters(std::move(parameters)),
app(createApp(manifest)) {}
~AppInstance() override = default; ~AppInstance() override = default;
@@ -70,22 +76,15 @@ public:
const AppManifest& getManifest() const override; const AppManifest& getManifest() const override;
Flags getFlags() const override; Flags getFlags() const;
void setFlags(Flags flags); void setFlags(Flags flags);
Flags& mutableFlags() { return flags; } // TODO: locking mechanism Flags& mutableFlags() { return flags; } // TODO: locking mechanism
std::shared_ptr<void> _Nullable getData() const override;
void setData(std::shared_ptr<void> data) override;
std::shared_ptr<const Bundle> getParameters() const override; std::shared_ptr<const Bundle> getParameters() const override;
void setResult(Result result) override;
void setResult(Result result, std::shared_ptr<const Bundle> bundle) override;
bool hasResult() const override;
std::unique_ptr<Paths> getPaths() const override; std::unique_ptr<Paths> getPaths() const override;
std::unique_ptr<ResultHolder>& getResult() { return resultHolder; } std::shared_ptr<App> getApp() const override { return app; }
}; };
} // namespace } // namespace
@@ -19,7 +19,7 @@ public:
private: private:
Mutex mutex = Mutex(Mutex::TypeRecursive); Mutex mutex = Mutex(Mutex::Type::Recursive);
std::vector<dirent> dir_entries; std::vector<dirent> dir_entries;
std::string current_path; std::string current_path;
std::string selected_child_entry; std::string selected_child_entry;
@@ -42,7 +42,7 @@ public:
bool setEntriesForPath(const std::string& path); bool setEntriesForPath(const std::string& path);
const std::vector<dirent>& lockEntries() const { const std::vector<dirent>& lockEntries() const {
mutex.lock(TtWaitForever); mutex.lock();
return dir_entries; return dir_entries;
} }
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "State.h" #include "./State.h"
#include "app/AppManifest.h" #include "app/AppManifest.h"
#include <lvgl.h> #include <lvgl.h>
#include <memory> #include <memory>
@@ -34,7 +35,7 @@ public:
void onRenamePressed(); void onRenamePressed();
void onDeletePressed(); void onDeletePressed();
void onDirEntryListScrollBegin(); void onDirEntryListScrollBegin();
void onResult(Result result, const Bundle& bundle); void onResult(Result result, std::unique_ptr<Bundle> bundle);
}; };
} }
@@ -0,0 +1,27 @@
#pragma once
#include <TactilityCore.h>
#include <Mutex.h>
#include <Thread.h>
#include "lvgl.h"
#include "hal/i2c/I2c.h"
#include "Timer.h"
#include <memory>
namespace tt::app::i2cscanner {
#define TAG "i2cscanner"
enum ScanState {
ScanStateInitial,
ScanStateScanning,
ScanStateStopped
};
struct Data {
};
void onScanTimerFinished(std::shared_ptr<Data> data);
}
@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "I2cScanner.h" #include "./I2cScannerPrivate.h"
#include <memory> #include <memory>
namespace tt::app::i2cscanner { namespace tt::app::i2cscanner {

Some files were not shown because too many files have changed in this diff Show More