Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .clangd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CompileFlags:
Remove: [-f*, -m*]
139 changes: 139 additions & 0 deletions .opencode/plans/lvgl9-desync-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# LVGL 9 Animation Desync Fix & Framerate Optimization

## Root Cause

The BSP demo uses `lvgl_port_add_disp_rgb()` which sets `disp_type = LVGL_PORT_DISP_TYPE_RGB`, causing `lv_disp_flush_ready()` to be called **synchronously** in the flush callback. This keeps LVGL's animation timer in lockstep with real time.

Tactility uses `lvgl_port_add_disp()` which sets `disp_type = LVGL_PORT_DISP_TYPE_OTHER`, skipping the synchronous `lv_disp_flush_ready()` and waiting for the **async** `on_color_trans_done` IO callback instead. This gap between frames causes animation desync.

---

## File 1: `Devices/waveshare-s3-touch-amoled-206/Source/devices/Display.cpp`

### Edit 1: Replace TE pin code with rounder callback (lines 19-29)
Remove the unused TE pin ISR/semaphore and add the rounder callback from the BSP:

```cpp
// REMOVE:
#define TE_GPIO GPIO_NUM_13
static SemaphoreHandle_t te_semaphore = NULL;
static void IRAM_ATTR te_isr_handler(void* arg) { ... }

// ADD:
static void rounder_event_cb(lv_event_t* e) {
lv_area_t* area = (lv_area_t*)lv_event_get_param(e);
area->x1 = (area->x1 >> 1) << 1;
area->y1 = (area->y1 >> 1) << 1;
area->x2 = ((area->x2 >> 1) << 1) + 1;
area->y2 = ((area->y2 >> 1) << 1) + 1;
}
```

### Edit 2: Remove `setup_te_pin()` declaration from class (line 65)
Remove `void setup_te_pin();` from the `Co5300Display` class definition.

### Edit 3: Replace `startLvgl()` body (lines 121-165)
- Remove `setup_te_pin();` call
- Change `lvgl_port_add_disp(&disp_cfg)` → `lvgl_port_add_disp_rgb(&disp_cfg, &rgb_cfg)`
- Add rounder callback registration

```cpp
bool Co5300Display::startLvgl() {
ESP_LOGI(TAG, "Registering display with LVGL port");
if (panelHandle == nullptr || ioHandle == nullptr) {
ESP_LOGE(TAG, "Display hardware not initialized");
return false;
}

constexpr int buffer_rows = 52;
constexpr int buffer_size = BSP_LCD_H_RES * buffer_rows;
lvgl_port_display_cfg_t disp_cfg = {};
disp_cfg.io_handle = ioHandle;
disp_cfg.panel_handle = panelHandle;
disp_cfg.buffer_size = (uint32_t)buffer_size;
disp_cfg.double_buffer = false;
disp_cfg.hres = BSP_LCD_H_RES;
disp_cfg.vres = BSP_LCD_V_RES;
disp_cfg.monochrome = false;
#if LVGL_VERSION_MAJOR >= 9
disp_cfg.color_format = LV_COLOR_FORMAT_RGB565;
#endif
disp_cfg.flags.sw_rotate = true;
disp_cfg.flags.buff_dma = true;
disp_cfg.flags.buff_spiram = false;
#if LVGL_VERSION_MAJOR >= 9
disp_cfg.flags.swap_bytes = true;
#endif

const lvgl_port_display_rgb_cfg_t rgb_cfg = {
.flags = {
.bb_mode = 0,
.avoid_tearing = 0,
}
};
lvglDisplay = lvgl_port_add_disp_rgb(&disp_cfg, &rgb_cfg);
if (lvglDisplay == nullptr) {
ESP_LOGE(TAG, "Failed to add LVGL display");
return false;
}

lv_display_add_event_cb(lvglDisplay, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL);
ESP_LOGI(TAG, "Display registered with LVGL, lvglDisplay=%p", lvglDisplay);

if (touch != nullptr && touch->supportsLvgl()) {
touch->startLvgl(lvglDisplay);
}
return true;
}
```

### Edit 4: Remove TE cleanup from `stopLvgl()` (lines 170-174)
Remove the `if (te_semaphore != NULL) { ... }` block.

### Edit 5: Remove `setup_te_pin()` implementation (lines 95-119)
Delete the entire `Co5300Display::setup_te_pin()` method body.

---

## File 2: `sdkconfig` — LVGL Config Tuning

Three settings to change (via Kconfig menu or direct edit):

| Setting | Current | New | Rationale |
|---|---|---|---|
| `CONFIG_LV_DEF_REFR_PERIOD` | 33 | 15 | Match demo; 66.7 Hz target |
| `CONFIG_LV_DRAW_SW_DRAW_UNIT_CNT` | 1 | 2 | Parallel rendering for throughput |
| `CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM` | not set | y | Draw functions in IRAM for speed |

To apply via sdkconfig editor:
```bash
idf.py menuconfig
# Navigate to: Component config → LVGL → Rendering
```

Or direct sdkconfig edits:
```ini
# Change line ~2670
CONFIG_LV_DEF_REFR_PERIOD=15

# Change line ~2680
CONFIG_LV_DRAW_SW_DRAW_UNIT_CNT=2

# Add near LV_ATTRIBUTE_FAST_MEM section
CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y
```

Also recommend enabling for profiling:
```ini
CONFIG_LV_USE_SYSMON=y
CONFIG_LV_USE_PERF_MONITOR=y
```

---

## Verification

After changes:
1. Build: `idf.py build`
2. Flash and verify animations are smooth (no desync)
3. Check FPS counter (if Sysmon enabled) — should show steady ~60-67 fps
6 changes: 5 additions & 1 deletion Buildscripts/sdkconfig/default.properties
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,8 @@ CONFIG_FATFS_SECTOR_512=y
CONFIG_WL_SECTOR_SIZE_512=y
CONFIG_WL_SECTOR_SIZE=512
CONFIG_WL_SECTOR_MODE_SAFE=y
CONFIG_WL_SECTOR_MODE=1
CONFIG_WL_SECTOR_MODE=1
# Bluetooth
CONFIG_BT_ENABLED=y
CONFIG_BT_CONTROLLER_ENABLED=y
CONFIG_BLUEDROID_ENABLED=y
7 changes: 7 additions & 0 deletions Data/user/app/ImuBall/manifest.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[manifest]
version = 1.0

[app]
id = ImuBall
name = IMU Ball
versionName = 1.0
10 changes: 10 additions & 0 deletions Devices/waveshare-s3-touch-amoled-18/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)

file(GLOB_RECURSE SOURCE_FILES "Source/*.cpp")

idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility ButtonControl esp_lvgl_port esp32_s3_touch_amoled_2_06 PwmBacklight driver esp_driver_i2c esp_driver_gpio esp_lcd esp_timer spiffs esp_psram fatfs usb espressif__esp_lcd_touch_ft5x06
PRIV_REQUIRES espressif__esp_lcd_panel_io_additions waveshare__esp_lcd_sh8601
)
27 changes: 27 additions & 0 deletions Devices/waveshare-s3-touch-amoled-18/Source/Configuration.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include "devices/Display.h"
#include "devices/Sdcard.h"

#include <Tactility/hal/Configuration.h>

using namespace tt::hal;

static DeviceVector createDevices() {
auto devices = DeviceVector();

auto display = createDisplay();
if (display) {
devices.push_back(display);
}

auto sdcard = createSdCard();
if (sdcard) {
devices.push_back(sdcard);
}

return devices;
}

extern const Configuration hardwareConfiguration = {
.initBoot = nullptr,
.createDevices = createDevices
};
166 changes: 166 additions & 0 deletions Devices/waveshare-s3-touch-amoled-18/Source/devices/Display.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#include "Display.h"
#include "Sdcard.h"

#include <Tactility/hal/display/DisplayDevice.h>
#include <bsp/esp32_s3_touch_amoled_2_06.h>
#include <bsp/display.h>
#include <bsp/touch.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lvgl_port.h>
#include <esp_log.h>
#include <lvgl.h>
#include <string>
#include <memory>

static const char* TAG = "Display";

#ifndef BSP_LCD_H_RES
#define BSP_LCD_H_RES 410
#endif
#ifndef BSP_LCD_V_RES
#define BSP_LCD_V_RES 502
#endif

class Co5300Display final : public tt::hal::display::DisplayDevice {
public:
explicit Co5300Display() = default;
~Co5300Display() override = default;

std::string getName() const override { return "CO5300"; }
std::string getDescription() const override { return "CO5300 AMOLED Display (SH8601)"; }

bool start() override;
bool stop() override;

std::shared_ptr<tt::hal::touch::TouchDevice> getTouchDevice() override { return nullptr; }

void setBacklightDuty(uint8_t backlightDuty) override;
bool supportsBacklightDuty() const override { return true; }

bool supportsLvgl() const override { return true; }
bool startLvgl() override;
bool stopLvgl() override { return true; }

lv_display_t* getLvglDisplay() const override { return lvglDisplay; }

bool supportsDisplayDriver() const override { return false; }
std::shared_ptr<tt::hal::display::DisplayDriver> getDisplayDriver() override { return nullptr; }

private:
lv_display_t* lvglDisplay = nullptr;
lv_indev_t* lvglTouchIndev = nullptr;
esp_lcd_panel_handle_t panelHandle = nullptr;
esp_lcd_panel_io_handle_t ioHandle = nullptr;
esp_lcd_touch_handle_t touchHandle = nullptr;
};

bool Co5300Display::start() {
ESP_LOGI(TAG, "Starting SH8601 AMOLED display hardware");

bsp_display_config_t disp_config = {
.max_transfer_sz = BSP_LCD_H_RES * BSP_LCD_V_RES * 2,
};

esp_err_t err = bsp_display_new(&disp_config, &panelHandle, &ioHandle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to init display hardware: %s", esp_err_to_name(err));
return false;
}

ESP_LOGI(TAG, "Display hardware initialized");

esp_lcd_panel_set_gap(panelHandle, 22, 0);

err = bsp_display_backlight_on();
if (err != ESP_OK) {
ESP_LOGW(TAG, "Failed to turn on backlight: %s", esp_err_to_name(err));
}

err = bsp_touch_new(NULL, &touchHandle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Failed to init touch: %s", esp_err_to_name(err));
touchHandle = nullptr;
} else {
ESP_LOGI(TAG, "Touch initialized");
}

return true;
}

bool Co5300Display::startLvgl() {
ESP_LOGI(TAG, "Registering display with LVGL port");

if (panelHandle == nullptr || ioHandle == nullptr) {
ESP_LOGE(TAG, "Display hardware not initialized");
return false;
}

const int buffer_height = 50;
const int buffer_size = BSP_LCD_H_RES * buffer_height;

lvgl_port_display_cfg_t disp_cfg = {};
disp_cfg.io_handle = ioHandle;
disp_cfg.panel_handle = panelHandle;
disp_cfg.buffer_size = (uint32_t)buffer_size;
disp_cfg.double_buffer = false;
disp_cfg.hres = BSP_LCD_H_RES;
disp_cfg.vres = BSP_LCD_V_RES;
disp_cfg.monochrome = false;
disp_cfg.rotation.swap_xy = false;
disp_cfg.rotation.mirror_x = false;
disp_cfg.rotation.mirror_y = false;
disp_cfg.flags.sw_rotate = true;
#if LVGL_VERSION_MAJOR >= 9
disp_cfg.color_format = LV_COLOR_FORMAT_RGB565;
#endif
disp_cfg.flags.buff_dma = true;
disp_cfg.flags.buff_spiram = false;
#if LVGL_VERSION_MAJOR >= 9
disp_cfg.flags.swap_bytes = true;
#endif

lvglDisplay = lvgl_port_add_disp(&disp_cfg);

if (lvglDisplay == nullptr) {
ESP_LOGE(TAG, "Failed to add LVGL display");
return false;
}

lv_display_set_rotation(lvglDisplay, LV_DISPLAY_ROTATION_0);

ESP_LOGI(TAG, "Display registered with LVGL, lvglDisplay=%p", lvglDisplay);

if (touchHandle != nullptr) {
const lvgl_port_touch_cfg_t touch_cfg = {
.disp = lvglDisplay,
.handle = touchHandle,
};
lvglTouchIndev = lvgl_port_add_touch(&touch_cfg);
if (lvglTouchIndev != nullptr) {
ESP_LOGI(TAG, "Touch registered with LVGL");
} else {
ESP_LOGW(TAG, "Failed to add touch to LVGL");
}
}

// Mount SD card now that SPI bus is initialized by display
auto sdcard = tt::hal::findFirstDevice<tt::hal::sdcard::SdCardDevice>(tt::hal::Device::Type::SdCard);
if (sdcard && sdcard->mount("/sdcard")) {
ESP_LOGI(TAG, "SD card mounted at /sdcard");
}

return true;
}

bool Co5300Display::stop() {
return true;
}

void Co5300Display::setBacklightDuty(uint8_t backlightDuty) {
int brightness_percent = (backlightDuty * 100) / 255;
bsp_display_brightness_set(brightness_percent);
}

std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
return std::make_shared<Co5300Display>();
}
6 changes: 6 additions & 0 deletions Devices/waveshare-s3-touch-amoled-18/Source/devices/Display.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#pragma once

#include <Tactility/hal/display/DisplayDevice.h>
#include <memory>

std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
Loading