Skip to content
Closed
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
19 changes: 19 additions & 0 deletions Devices/waveshare-s3-touch-amoled-18/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
file(GLOB_RECURSE SOURCE_FILES source/*.c*)

idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "source"
REQUIRES TactilityKernel Tactility TactilityFreeRtos lvgl-module lvgl-window-manager-module esp_driver_i2c esp_driver_gpio
)

# Board-local link-time hooks (see source/lvgl_scaling.cpp and source/lvgl_insets.cpp): keep the
# Material theme's widget sizes usable while tightening its spacing, and keep the UI clear of the
# top/bottom edges. No core Tactility/LVGL source is modified.
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=window_manager_configure" APPEND)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_display_create" APPEND)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_style_set_pad_top" APPEND)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_style_set_pad_bottom" APPEND)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_style_set_pad_left" APPEND)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_style_set_pad_right" APPEND)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_style_set_pad_row" APPEND)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_style_set_pad_column" APPEND)
30 changes: 30 additions & 0 deletions Devices/waveshare-s3-touch-amoled-18/device.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
general.vendor=Waveshare
general.name=S3 Touch AMOLED 1.8"
general.incubating=true

apps.launcherAppId=tactility.launcher

hardware.target=ESP32S3
hardware.flashSize=16MB
hardware.spiRam=true
hardware.spiRamMode=OCT
hardware.spiRamSpeed=120M
hardware.tinyUsbMsc=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true

storage.userDataLocation=Internal

display.size=1.8"
display.shape=rectangle
display.dpi=322

lvgl.colorDepth=16
lvgl.uiDensity=default
lvgl.fontSize=22

sdkconfig.CONFIG_TASK_WDT_TIMEOUT_S=10
sdkconfig.CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=131072
# LVGL v9 symbol (v8 was LV_DISP_DEF_REFR_PERIOD). Upstream moved this out of device.py,
# so pin it per-device: 10 ms keeps the panel above the 16 ms animation tick.
sdkconfig.CONFIG_LV_DEF_REFR_PERIOD=10
11 changes: 11 additions & 0 deletions Devices/waveshare-s3-touch-amoled-18/module.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
dependencies:
- Platforms/platform-esp32
- Drivers/sh8601-module
- Drivers/axp2101-module
- Drivers/pcf85063-module
- Drivers/qmi8658-module
- Drivers/ft5x06-module
- Drivers/es8311-module
- Drivers/audio-stream-module
- Drivers/tca9534-module
dts: waveshare,s3-touch-amoled-18.dts
86 changes: 86 additions & 0 deletions Devices/waveshare-s3-touch-amoled-18/source/expander_reset.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: Apache-2.0

// The Waveshare 1.8" board does not wire the AMOLED panel or touch reset lines to ESP32 GPIOs.
// They hang off a TCA9554 I2C expander (address 0x20), whose pins the board's own examples drive
// low then high (EXIO0, EXIO1, EXIO2 and EXIO6) before initializing the panel and touch
// controller. Without that pulse the SH8601 panel stays blank and the FT3168 touch NAKs every
// I2C transaction (the later "FT5x06 init failed").
//
// This runs from the board module's start(), which is before the kernel constructs and starts the
// devicetree's devices, so the reset is released ahead of the display/touch drivers' own init. It
// uses a temporary ESP-IDF I2C master (on a spare port, so it can't collide with the kernel's
// i2c0) and tears it down again, leaving the bus for i2c0 to set up afterwards.

#include <tactility/log.h>

#include <driver/i2c_master.h>
#include <driver/gpio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

namespace {

constexpr auto* TAG = "ws-amoled18-expander";

constexpr uint8_t EXPANDER_ADDRESS = 0x20;

constexpr gpio_num_t I2C_SDA = GPIO_NUM_15;
constexpr gpio_num_t I2C_SCL = GPIO_NUM_14;

// TCA9554 register map (shared with the TCA9534).
constexpr uint8_t REG_OUTPUT = 0x01;
constexpr uint8_t REG_CONFIG = 0x03;

// EXIO0/1/2/6 drive the panel and touch reset lines.
constexpr uint8_t RESET_PINS = (uint8_t)((1u << 0) | (1u << 1) | (1u << 2) | (1u << 6));

bool write_register(i2c_master_dev_handle_t device, uint8_t reg, uint8_t value) {
uint8_t buffer[2] = { reg, value };
return i2c_master_transmit(device, buffer, sizeof(buffer), -1) == ESP_OK;
}

} // namespace

void waveshare_amoled18_expander_reset() {
i2c_master_bus_config_t bus_config = {};
bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
bus_config.i2c_port = I2C_NUM_1; // spare port; i2c0 belongs to the kernel devicetree
bus_config.sda_io_num = I2C_SDA;
bus_config.scl_io_num = I2C_SCL;
bus_config.glitch_ignore_cnt = 7;
bus_config.flags.enable_internal_pullup = true;

i2c_master_bus_handle_t bus = nullptr;
esp_err_t err = i2c_new_master_bus(&bus_config, &bus);
if (err != ESP_OK) {
LOG_W(TAG, "Failed to open I2C bus: %s", esp_err_to_name(err));
return;
}

i2c_device_config_t device_config = {};
device_config.dev_addr_length = I2C_ADDR_BIT_LEN_7;
device_config.device_address = EXPANDER_ADDRESS;
device_config.scl_speed_hz = 400000;

i2c_master_dev_handle_t device = nullptr;
err = i2c_master_bus_add_device(bus, &device_config, &device);
if (err == ESP_OK) {
// EXIO0/1/2/6 as outputs (config bit 0 = output), the rest left as inputs; then the
// reset pulse itself.
if (!write_register(device, REG_CONFIG, (uint8_t)~RESET_PINS) ||
!write_register(device, REG_OUTPUT, 0x00)) {
LOG_W(TAG, "Expander reset: initial writes failed");
}
vTaskDelay(pdMS_TO_TICKS(20));
if (!write_register(device, REG_OUTPUT, RESET_PINS)) {
LOG_W(TAG, "Expander reset: release write failed");
} else {
LOG_I(TAG, "Panel/touch reset released via TCA9554");
}
i2c_master_bus_rm_device(device);
} else {
LOG_W(TAG, "Failed to add expander device: %s", esp_err_to_name(err));
}

i2c_del_master_bus(bus);
}
48 changes: 48 additions & 0 deletions Devices/waveshare-s3-touch-amoled-18/source/lvgl_insets.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: Apache-2.0

// Keeps this board's UI clear of the display edges with a fixed inset.
//
// Core Tactility only applies the insets it derives from the display shape for round displays (see
// Tactility/Source/lvgl/Lvgl.cpp), and this rectangular AMOLED reports no shape, so instead of
// changing core source this wraps the window-manager screen-init callback - the same board-local
// link-time wrapping used in lvgl_scaling.cpp.
//
// The padding is applied to the window-manager root widget (the argument the screen-init callback
// receives and builds its chrome under), not just to the app container it returns. That way the
// statusbar, which the callback adds as a sibling of the app container, is inset with the rest of
// the UI instead of sitting on the top edge of the display.
//
// lv_obj_set_style_pad_all() writes the local style prop directly, so it is NOT affected by the
// /8 padding wrapper in lvgl_scaling.cpp (that one only covers lv_style_set_pad_*).

#include <lvgl_window_manager/window_manager.h>

#include <lvgl/lvgl.h>

#include <stdint.h>

namespace {

// Fixed inset, in pixels, applied on all four edges.
constexpr int32_t INSET_PX = 22;

// The callback passed to window_manager_configure(), invoked later to build the window tree.
WindowManagerScreenInitFn original_screen_init = nullptr;

} // namespace

extern "C" {

static lv_obj_t* wrapping_screen_init(lv_obj_t* root_widget) {
lv_obj_set_style_pad_all(root_widget, INSET_PX, LV_STATE_DEFAULT);
return original_screen_init != nullptr ? original_screen_init(root_widget) : root_widget;
}

void __real_window_manager_configure(WindowManagerScreenInitFn screen_init);

void __wrap_window_manager_configure(WindowManagerScreenInitFn screen_init) {
original_screen_init = screen_init;
__real_window_manager_configure(screen_init != nullptr ? wrapping_screen_init : nullptr);
}

} // extern "C"
86 changes: 86 additions & 0 deletions Devices/waveshare-s3-touch-amoled-18/source/lvgl_scaling.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: Apache-2.0

// Tactility's Material themes (Modules/lvgl-module) derive both widget metrics (switch/checkbox/
// slider sizes, radius, borders, ...) and spacing (padding/gaps) from the display DPI. On this
// high-DPI panel (`display.dpi` = 322) those metrics are far too large relative to the text, but
// simply lowering the DPI to tighten the spacing also shrinks the widgets to unusable sizes - the
// switch track, for example, is LV_DPX_CALC(dpi, 52) x LV_DPX_CALC(dpi, 32).
//
// So the two are decoupled here, board-locally, with link-time wrappers (the same mechanism used
// elsewhere) instead of editing core Tactility/LVGL source:
// - lv_display_create(): the display DPI tracks the text font, so widget sizes stay usable;
// - lv_style_set_pad_*(): the theme's style paddings/gaps are divided, so spacing stays tight.
// Only the two theme files use style-based padding (apps use lv_obj_set_style_*, which does not go
// through these setters), so the padding wrappers affect the theme and nothing else.

#include <tactility/log.h>

#include <lvgl/fonts.h>
#include <lvgl/lvgl.h>

constexpr auto* TAG = "waveshare-s3-touch-amoled-18";

namespace {

// Widget metrics scale with the display DPI; this puts them near Material's baseline at the
// default 22 px font (e.g. the switch track is ~50 x 31 px).
constexpr int32_t WIDGET_DPI_PER_FONT_PIXEL = 7;

// Spacing is divided by this instead of following the widget DPI (e.g. the theme's largest control
// padding, dp(24), becomes ~3 px).
constexpr int32_t PADDING_DIVISOR = 8;

int32_t scalePadding(int32_t value) {
// Keep zero and negative values (handle/knob geometry) as the theme designed them.
if (value <= 0) {
return value;
}
return (value + PADDING_DIVISOR / 2) / PADDING_DIVISOR;
}

int32_t displayDpiForFontSize(int32_t font_size) {
return font_size * WIDGET_DPI_PER_FONT_PIXEL;
}

} // namespace

extern "C" {

lv_display_t* __real_lv_display_create(int32_t hor_res, int32_t ver_res);

lv_display_t* __wrap_lv_display_create(int32_t hor_res, int32_t ver_res) {
lv_display_t* display = __real_lv_display_create(hor_res, ver_res);
if (display == nullptr) {
return display;
}

const int32_t font_size = static_cast<int32_t>(lvgl_get_text_font_height(FONT_SIZE_DEFAULT));
if (font_size <= 0) {
return display;
}

const int32_t dpi = displayDpiForFontSize(font_size);
if (lv_display_get_dpi(display) != dpi) {
lv_display_set_dpi(display, dpi);
LOG_I(TAG, "LVGL display DPI set to %d for text font %d px", (int)dpi, (int)font_size);
}

return display;
}

// lv_style_set_pad_all/hor/ver/gap are static inline and call these base setters, so wrapping the
// six base ones covers every padding a theme style sets.
#define WRAP_PADDING_SETTER(name) \
void __real_##name(lv_style_t* style, int32_t value); \
void __wrap_##name(lv_style_t* style, int32_t value) { __real_##name(style, scalePadding(value)); }

WRAP_PADDING_SETTER(lv_style_set_pad_top)
WRAP_PADDING_SETTER(lv_style_set_pad_bottom)
WRAP_PADDING_SETTER(lv_style_set_pad_left)
WRAP_PADDING_SETTER(lv_style_set_pad_right)
WRAP_PADDING_SETTER(lv_style_set_pad_row)
WRAP_PADDING_SETTER(lv_style_set_pad_column)

#undef WRAP_PADDING_SETTER

} // extern "C"
28 changes: 28 additions & 0 deletions Devices/waveshare-s3-touch-amoled-18/source/module.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include <tactility/module.h>

// Defined in expander_reset.cpp (C++ linkage).
void waveshare_amoled18_expander_reset();

extern "C" {

static error_t start() {
// The panel/touch reset lines hang off the TCA9554 expander, so release them before the kernel
// starts the devicetree's display and touch devices.
waveshare_amoled18_expander_reset();
return ERROR_NONE;
}

static error_t stop() {
return ERROR_NONE;
}

Module waveshare_s3_touch_amoled_18_module = {
.name = "waveshare-s3-touch-amoled-18",
.start = start,
.stop = stop,
.drivers = nullptr,
.symbols = nullptr,
.internal = nullptr
};

}
Loading
Loading