Add Waveshare ESP32-S3-Touch-AMOLED-1.8 (368×448) board support
Port the firmware to a second Waveshare AMOLED board variant alongside
the original 2.16" / 480×480. Selection is at build time via a board
macro; both envs continue to build and flash independently.
Hardware delta vs AMOLED-2.16:
* Display: SH8601 QSPI (vs CO5300), 368×448 portrait (vs 480² square),
SCLK on GPIO 11 (vs 38), RST routed through an XCA9554 I/O expander
(vs direct GPIO).
* Touch: FT3168 @ 0x38 (vs CST9220 @ 0x5A). Driven by a ~30-line
inline FocalTech-protocol reader in main.cpp — Waveshare's
Arduino_DriveBus library is GPLv3 and would force the project's
license, which the README explicitly avoids.
* I/O expander: new XCA9554/PCA9554 @ I2C 0x20 gates LCD_RST, TP_RST,
audio amp enable, and the PWR button. Wrapped in io_expander.{h,cpp}.
Must initialize BEFORE display/touch or both stay in reset.
* PMU + IMU: same AXP2101 + QMI8658 as 2.16, so power.cpp/imu.cpp
largely reuse. IMU is initialized for I2C bus health but rotation
is fixed at 0° (the portrait panel doesn't benefit from auto-rotate
and the strip-rotation CPU path is excluded entirely).
* Buttons: only BOOT (GPIO 0) is a direct GPIO; PWR is read via
XCA9554 EXIO4 polling. No third button — the AMOLED-2.16's
Shift+Tab key (GPIO 18) is dropped for this board.
* Flash: 16MB (vs 8MB), partition table set to default_16MB.csv.
Code structure:
* display_cfg.h hosts a #ifdef BOARD_AMOLED_18 / #else / #endif split
with a PlatformDisplay typedef so call sites elsewhere stay clean.
* main.cpp, power.cpp, ui.cpp, splash.cpp gate board-specific bits on
the same macro. Layout constants (panel heights, content Y, font
choices on the Bluetooth screen) compress for the 368×448 portrait
footprint so two usage panels + bottom animation label fit without
horizontal overflow.
* Splash scales the 20×20 pixel-art grid to 360×360 (CELL=18 vs 24)
and centers in the portrait canvas.
* Old AMOLED-2.16 env is unchanged at the source level (gated under
#ifndef BOARD_AMOLED_18) and continues to compile + flash on the
original hardware.
CLAUDE.md updated with the two-board pin maps, build/flash commands
for both envs (macOS + Linux device paths), and the per-board gotchas.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
57e5d73c6e
commit
f3ed2425bf
@@ -1,49 +1,66 @@
|
|||||||
# Project context
|
# Project context
|
||||||
|
|
||||||
ESP32-S3 firmware for a desk-side Claude Code usage monitor on a **Waveshare ESP32-S3-Touch-AMOLED-2.16** board (480×480 square AMOLED). Connects to a host daemon over BLE; daemon polls Anthropic API for usage data.
|
ESP32-S3 firmware for a desk-side Claude Code usage monitor. Two board variants are supported via a build-flag macro:
|
||||||
|
|
||||||
This file is for future Claude Code sessions to bootstrap quickly. Read this first.
|
- **`-DBOARD_AMOLED_216`** → original Waveshare ESP32-S3-Touch-AMOLED-2.16 (CO5300, 480×480 square, CST9220 touch). Build env: `waveshare_amoled_216`.
|
||||||
|
- **`-DBOARD_AMOLED_18`** → Waveshare ESP32-S3-Touch-AMOLED-1.8 (SH8601, 368×448 portrait, FT3168 touch). Build env: `waveshare_amoled_18`.
|
||||||
|
|
||||||
|
`display_cfg.h` selects pins / typedefs / extern decls based on the macro. Per-board layout deltas (panel heights, fonts) are scoped with `#ifdef` in `ui.cpp` and `splash.cpp`.
|
||||||
|
|
||||||
|
Connects to a host daemon over BLE; daemon polls Anthropic API for usage data. This file is for future Claude Code sessions to bootstrap quickly. Read this first.
|
||||||
|
|
||||||
## Hardware (critical pins)
|
## Hardware (critical pins)
|
||||||
|
|
||||||
|
### AMOLED-2.16 (original)
|
||||||
- Display: **CO5300** AMOLED via QSPI (CS=12, SCLK=38, SDIO0..3=4..7, RST=2)
|
- Display: **CO5300** AMOLED via QSPI (CS=12, SCLK=38, SDIO0..3=4..7, RST=2)
|
||||||
- Touch: **CST9220** via I2C (SDA=15, SCL=14, INT=11, addr=0x5A)
|
- Touch: **CST9220** via I2C (SDA=15, SCL=14, INT=11, addr=0x5A)
|
||||||
- PMU: **AXP2101** on same I2C bus (addr=0x34) — battery, USB VBUS, PWR button IRQ
|
- PMU: **AXP2101** on same I2C bus (addr=0x34) — battery, USB VBUS, PWR button IRQ
|
||||||
- IMU: **QMI8658** on same I2C bus (addr=0x6B) — accelerometer for auto-rotation
|
- IMU: **QMI8658** on same I2C bus (addr=0x6B) — accelerometer for auto-rotation
|
||||||
- Buttons: GPIO 0 (left → Space/voice-mode), GPIO 18 (right → Shift+Tab/mode-toggle), AXP PKEY (middle → cycle screens; on splash → cycle animations)
|
- Buttons: GPIO 0 (left → Space/voice-mode), GPIO 18 (right → Shift+Tab/mode-toggle), AXP PKEY (middle → cycle screens; on splash → cycle animations)
|
||||||
|
|
||||||
|
### AMOLED-1.8 (newer port)
|
||||||
|
- Display: **SH8601** AMOLED via QSPI (CS=12, **SCLK=11** ← different!, SDIO0..3=4..7, RST routed via XCA9554 EXIO1)
|
||||||
|
- Touch: **FT3168** via I2C (SDA=15, SCL=14, INT=21, addr=0x38). Driven by minimal inline reader in `main.cpp` (FocalTech standard register layout — avoids vendoring the GPLv3 `Arduino_DriveBus` library).
|
||||||
|
- PMU: AXP2101 @ 0x34 (same chip as 2.16 — `XPowersLib` reused; battery is an optional kit add-on but PMU + charging circuitry are populated)
|
||||||
|
- IMU: QMI8658 @ 0x6B (same chip — initialized for I2C bus health, rotation logic disabled)
|
||||||
|
- IO expander: **XCA9554 / PCA9554** @ I2C 0x20. Gates LCD_RST, TP_RST, audio amp enable, and reads the PWR button. **`io_expander_init()` MUST run before `gfx->begin()` or `ft3168_init()`** — otherwise display/touch stay in reset and silently fail. PWR button is on EXIO4, active HIGH (verified empirically with the deleted `iox` serial debug command).
|
||||||
|
- Orientation: **fixed at 0°**. IMU auto-rotation is disabled; `rotate_strip()` / `handle_rotation_change()` are excluded via `#ifndef BOARD_AMOLED_18`.
|
||||||
|
- Buttons: GPIO 0 (BOOT → Space/voice-mode), XCA9554 EXIO4 (PWR → cycle screens; on splash → cycle animations). **No third button** (GPIO 18 button doesn't exist on this board).
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```text
|
```text
|
||||||
main.cpp — setup(), loop(), button polling (left→Space, right→Shift+Tab, mid→cycle), rotation flash
|
main.cpp — setup(), loop(), button polling, FT3168 minimal reader (AMOLED-1.8), rotation flash (2.16 only)
|
||||||
display_cfg.h — pin defines, extern object decls
|
display_cfg.h — board-conditional pin defines, typedefs (PlatformDisplay = CO5300|SH8601), extern object decls
|
||||||
ui.{h,cpp} — 3-screen UI (splash, usage, bluetooth); splash is touch-toggled, usage↔bluetooth via mid button
|
io_expander.{h,cpp} — XCA9554/PCA9554 wrapper (AMOLED-1.8 only): LCD/TP reset release + PWR button read
|
||||||
splash.{h,cpp} — 20×20 pixel-art animation engine, 24× upscale to 480×480
|
ui.{h,cpp} — 3-screen UI (splash, usage, bluetooth); splash is touch-toggled, usage↔bluetooth via PWR button
|
||||||
imu.{h,cpp} — accelerometer-driven rotation tracker (returns 0..3)
|
splash.{h,cpp} — 20×20 pixel-art animation engine. CELL = 24 (480²) for 2.16, 18 (360² centered) for 1.8
|
||||||
power.{h,cpp} — AXP2101 wrapper (battery %, charging, VBUS, PWR button)
|
imu.{h,cpp} — accelerometer-driven rotation tracker (returns 0..3). Result is ignored on AMOLED-1.8.
|
||||||
touch.{h,cpp} — minimal tap detector → ui_toggle_splash() (Usage/Splash) or ble_clear_bonds() (BT reset zone)
|
power.{h,cpp} — AXP2101 wrapper (battery %, charging, VBUS, PWR button). PWR source is conditional: AXP PKEY IRQ on 2.16, XCA9554 EXIO4 polling on 1.8.
|
||||||
ble.{h,cpp} — NimBLE peripheral: custom data service + HID keyboard
|
ble.{h,cpp} — NimBLE peripheral: custom data service + HID keyboard
|
||||||
data.h — UsageData struct
|
data.h — UsageData struct
|
||||||
icons.h — icon arrays. Battery (5×) are RGB565A8 with alpha; rest are raw RGB565.
|
icons.h — icon arrays. Battery (5×) are RGB565A8 with alpha; rest are raw RGB565.
|
||||||
logo.h — 80×80 RGB565 logo
|
logo.h — 80×80 RGB565 logo
|
||||||
font_*.c — pre-compiled LVGL 9 bitmap fonts (Tiempos 56, Styrene 48/28/24/20, Mono 32)
|
font_*.c — pre-compiled LVGL 9 bitmap fonts (Tiempos 56/34, Styrene 48/28/24/20/16/14/12, Mono 32/18)
|
||||||
splash_animations.h — generated, do not hand-edit
|
splash_animations.h — generated, do not hand-edit
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build / flash
|
## Build / flash
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pio run -d firmware # build
|
pio run -d firmware -e waveshare_amoled_216 # build 2.16 (default original)
|
||||||
pio run -d firmware -t upload --upload-port /dev/ttyACM0 # flash (binary path uses USB JTAG)
|
pio run -d firmware -e waveshare_amoled_18 # build 1.8 (new port)
|
||||||
|
pio run -d firmware -e waveshare_amoled_18 -t upload --upload-port /dev/cu.usbmodem101 # flash 1.8 on macOS
|
||||||
|
pio run -d firmware -e waveshare_amoled_216 -t upload --upload-port /dev/ttyACM0 # flash 2.16 on Linux
|
||||||
```
|
```
|
||||||
|
|
||||||
`/home/hermann/.platformio/penv/bin/pio` if `pio` isn't on PATH.
|
If `pio` isn't on PATH: try `~/.platformio/penv/bin/pio` (Linux/macOS pio install) or `brew install platformio` on macOS.
|
||||||
|
|
||||||
Device shows up as `/dev/ttyACM0` (Espressif USB JTAG/serial debug unit). No boot-mode gymnastics needed — direct flash works.
|
Device path differs by OS: `/dev/cu.usbmodem*` on macOS, `/dev/ttyACM0` on Linux. Both expose the ESP32-S3 native USB-JTAG (no boot-mode dance needed).
|
||||||
|
|
||||||
## QA your own UI changes — don't ask the user
|
## QA your own UI changes — don't ask the user
|
||||||
|
|
||||||
The firmware ships a `screenshot` serial command that dumps the LVGL framebuffer over `/dev/ttyACM0`. `./screenshot.sh out.png /dev/ttyACM0` captures a 480×480 PNG. **Use this on every UI iteration** — Read the PNG with the Read tool, verify the change visually, iterate.
|
The firmware ships a `screenshot` serial command that dumps the LVGL framebuffer. `./screenshot.sh out.png [port]` captures a PNG sized to the active display (480×480 or 368×448). **Use this on every UI iteration** — Read the PNG with the Read tool, verify the change visually, iterate. Script auto-picks the macOS/Linux default port and falls back to pio's bundled Python if pyserial isn't on the system Python.
|
||||||
|
|
||||||
The boot screen is `SCREEN_SPLASH` and only advances on a physical button press, so a fresh flash will sit on the splash. To screenshot the screen you're actually editing without asking the user to press a button, **temporarily change the default boot screen** in `main.cpp` (search for `ui_show_screen(SCREEN_SPLASH);`) to `SCREEN_USAGE` / `SCREEN_CONTROLLER` / `SCREEN_BLUETOOTH`, do your iteration, then revert before committing.
|
The boot screen is `SCREEN_SPLASH` and only advances on a physical button press, so a fresh flash will sit on the splash. To screenshot the screen you're actually editing without asking the user to press a button, **temporarily change the default boot screen** in `main.cpp` (search for `ui_show_screen(SCREEN_SPLASH);`) to `SCREEN_USAGE` / `SCREEN_CONTROLLER` / `SCREEN_BLUETOOTH`, do your iteration, then revert before committing.
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ upload_speed = 921600
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
|
-DBOARD_AMOLED_216
|
||||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
-DBOARD_HAS_PSRAM
|
-DBOARD_HAS_PSRAM
|
||||||
; AXP2101 PMU
|
; AXP2101 PMU
|
||||||
@@ -42,3 +43,55 @@ lib_deps =
|
|||||||
lvgl/lvgl@^9.2.0
|
lvgl/lvgl@^9.2.0
|
||||||
bblanchon/ArduinoJson@^7.0.0
|
bblanchon/ArduinoJson@^7.0.0
|
||||||
h2zero/NimBLE-Arduino@^2.1.1
|
h2zero/NimBLE-Arduino@^2.1.1
|
||||||
|
|
||||||
|
|
||||||
|
[env:waveshare_amoled_18]
|
||||||
|
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.38-1/platform-espressif32.zip
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
framework = arduino
|
||||||
|
board_build.arduino.memory_type = qio_opi
|
||||||
|
board_upload.flash_size = 16MB
|
||||||
|
board_upload.maximum_size = 16777216
|
||||||
|
board_build.partitions = default_16MB.csv
|
||||||
|
upload_speed = 921600
|
||||||
|
monitor_speed = 115200
|
||||||
|
|
||||||
|
build_flags =
|
||||||
|
-DBOARD_AMOLED_18
|
||||||
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
-DBOARD_HAS_PSRAM
|
||||||
|
; AXP2101 PMU is present on the AMOLED-1.8 board too (battery is optional kit add-on)
|
||||||
|
-DXPOWERS_CHIP_AXP2101
|
||||||
|
; NimBLE config — peripheral, 2 simultaneous connections so the OS can
|
||||||
|
; hold the HID link (Space key from BOOT button) while the daemon holds
|
||||||
|
; its own connection for the custom data service.
|
||||||
|
-DCONFIG_BT_NIMBLE_ROLE_BROADCASTER=1
|
||||||
|
-DCONFIG_BT_NIMBLE_ROLE_PERIPHERAL=1
|
||||||
|
-DCONFIG_BT_NIMBLE_ROLE_CENTRAL=0
|
||||||
|
-DCONFIG_BT_NIMBLE_ROLE_OBSERVER=0
|
||||||
|
-DCONFIG_BT_NIMBLE_MAX_CONNECTIONS=2
|
||||||
|
; LVGL config via build flags
|
||||||
|
-DLV_CONF_SKIP
|
||||||
|
-DLV_COLOR_DEPTH=16
|
||||||
|
-DLV_USE_LOG=0
|
||||||
|
-DLV_USE_BAR=1
|
||||||
|
-DLV_USE_LABEL=1
|
||||||
|
-DLV_USE_ARC=1
|
||||||
|
-DLV_USE_BTN=1
|
||||||
|
-DLV_USE_LINE=1
|
||||||
|
-DLV_USE_OBJ=1
|
||||||
|
-DLV_USE_IMG=1
|
||||||
|
-DLV_USE_IMAGE=1
|
||||||
|
-DLV_USE_ANIMIMG=0
|
||||||
|
-DLV_TICK_CUSTOM=1
|
||||||
|
-DLV_USE_SNAPSHOT=1
|
||||||
|
|
||||||
|
lib_deps =
|
||||||
|
; 1.6.4+ ships Arduino_SH8601 in mainline
|
||||||
|
moononournation/GFX Library for Arduino@^1.6.4
|
||||||
|
; SensorLib still used for QMI8658 IMU on AMOLED-1.8
|
||||||
|
lewisxhe/SensorLib@^0.2.6
|
||||||
|
lewisxhe/XPowersLib@^0.2.7
|
||||||
|
lvgl/lvgl@^9.2.0
|
||||||
|
bblanchon/ArduinoJson@^7.0.0
|
||||||
|
h2zero/NimBLE-Arduino@^2.1.1
|
||||||
|
|||||||
@@ -1,11 +1,63 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Arduino_GFX_Library.h>
|
#include <Arduino_GFX_Library.h>
|
||||||
#include <TouchDrvCSTXXX.hpp>
|
|
||||||
#include <XPowersLib.h>
|
#include <XPowersLib.h>
|
||||||
#include <SensorQMI8658.hpp>
|
#include <SensorQMI8658.hpp>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Board variant selection — driven by build flag in platformio.ini
|
||||||
|
// -DBOARD_AMOLED_216 → original Waveshare ESP32-S3-Touch-AMOLED-2.16 (CO5300, CST9220, 480x480)
|
||||||
|
// -DBOARD_AMOLED_18 → newer Waveshare ESP32-S3-Touch-AMOLED-1.8 (SH8601, FT3168, 368x448)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#if defined(BOARD_AMOLED_18)
|
||||||
|
|
||||||
|
// ---- Display resolution (portrait) ----
|
||||||
|
#define LCD_WIDTH 368
|
||||||
|
#define LCD_HEIGHT 448
|
||||||
|
|
||||||
|
// ---- QSPI display pins (SH8601) ----
|
||||||
|
#define LCD_CS 12
|
||||||
|
#define LCD_SCLK 11 // NOTE: different from AMOLED-2.16 (was GPIO 38)
|
||||||
|
#define LCD_SDIO0 4
|
||||||
|
#define LCD_SDIO1 5
|
||||||
|
#define LCD_SDIO2 6
|
||||||
|
#define LCD_SDIO3 7
|
||||||
|
#define LCD_RESET GFX_NOT_DEFINED // routed via XCA9554 EXIO1
|
||||||
|
|
||||||
|
// ---- I2C bus (touch + PMU + IMU + IO expander all share one bus) ----
|
||||||
|
#define IIC_SDA 15
|
||||||
|
#define IIC_SCL 14
|
||||||
|
|
||||||
|
// ---- Touch (FT3168 via I2C) ----
|
||||||
|
#define TP_INT 21
|
||||||
|
#define FT3168_ADDR 0x38
|
||||||
|
|
||||||
|
// ---- PMU (AXP2101 via I2C) ----
|
||||||
|
#define AXP2101_ADDR 0x34
|
||||||
|
|
||||||
|
// ---- IO expander (XCA9554/PCA9554-compatible via I2C) ----
|
||||||
|
// Gates LCD_RST, TP_RST, audio amp reset, and PWR button readback.
|
||||||
|
#define XCA9554_ADDR 0x20
|
||||||
|
#define IOX_PIN_TP_RST 0 // EXIO0 → touch reset (active LOW)
|
||||||
|
#define IOX_PIN_LCD_RST 1 // EXIO1 → display reset (active LOW)
|
||||||
|
#define IOX_PIN_PA_EN 2 // EXIO2 → audio amp enable (we keep HIGH to release)
|
||||||
|
#define IOX_PIN_PWR_BTN 4 // EXIO4 → PWR button input, active HIGH
|
||||||
|
|
||||||
|
// ---- Display class typedef ----
|
||||||
|
typedef Arduino_SH8601 PlatformDisplay;
|
||||||
|
|
||||||
|
// ---- Global hardware objects (defined in main.cpp) ----
|
||||||
|
extern Arduino_DataBus *bus;
|
||||||
|
extern PlatformDisplay *gfx;
|
||||||
|
extern XPowersPMU pmu;
|
||||||
|
extern SensorQMI8658 imu;
|
||||||
|
|
||||||
|
#else // BOARD_AMOLED_216 (default / original board)
|
||||||
|
|
||||||
|
#include <TouchDrvCSTXXX.hpp>
|
||||||
|
|
||||||
// ---- Display resolution ----
|
// ---- Display resolution ----
|
||||||
#define LCD_WIDTH 480
|
#define LCD_WIDTH 480
|
||||||
#define LCD_HEIGHT 480
|
#define LCD_HEIGHT 480
|
||||||
@@ -29,9 +81,14 @@
|
|||||||
// ---- PMU (AXP2101 via same I2C) ----
|
// ---- PMU (AXP2101 via same I2C) ----
|
||||||
#define AXP2101_ADDR 0x34
|
#define AXP2101_ADDR 0x34
|
||||||
|
|
||||||
|
// ---- Display class typedef ----
|
||||||
|
typedef Arduino_CO5300 PlatformDisplay;
|
||||||
|
|
||||||
// ---- Global hardware objects (defined in main.cpp) ----
|
// ---- Global hardware objects (defined in main.cpp) ----
|
||||||
extern Arduino_DataBus *bus;
|
extern Arduino_DataBus *bus;
|
||||||
extern Arduino_CO5300 *gfx;
|
extern PlatformDisplay *gfx;
|
||||||
extern TouchDrvCST92xx touch;
|
extern TouchDrvCST92xx touch;
|
||||||
extern XPowersPMU pmu;
|
extern XPowersPMU pmu;
|
||||||
extern SensorQMI8658 imu;
|
extern SensorQMI8658 imu;
|
||||||
|
|
||||||
|
#endif // BOARD_AMOLED_*
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#include "io_expander.h"
|
||||||
|
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
|
||||||
|
#include "display_cfg.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
|
// XCA9554/PCA9554 register map
|
||||||
|
#define IOX_REG_INPUT 0x00
|
||||||
|
#define IOX_REG_OUTPUT 0x01
|
||||||
|
#define IOX_REG_POLARITY 0x02
|
||||||
|
#define IOX_REG_CONFIG 0x03 // 1 = input, 0 = output
|
||||||
|
|
||||||
|
// EXIO0..2 are outputs (reset lines + audio amp). Everything else is input.
|
||||||
|
// Bit layout: 0bIIIIIOOO = 0xF8
|
||||||
|
#define IOX_CONFIG_MASK 0xF8
|
||||||
|
|
||||||
|
// All three outputs HIGH = resets released, amp enabled.
|
||||||
|
#define IOX_OUTPUT_DEFAULT 0x07
|
||||||
|
|
||||||
|
static uint8_t output_state = 0x00;
|
||||||
|
|
||||||
|
static bool write_reg(uint8_t reg, uint8_t val) {
|
||||||
|
Wire.beginTransmission(XCA9554_ADDR);
|
||||||
|
Wire.write(reg);
|
||||||
|
Wire.write(val);
|
||||||
|
return Wire.endTransmission() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool read_reg(uint8_t reg, uint8_t &val) {
|
||||||
|
Wire.beginTransmission(XCA9554_ADDR);
|
||||||
|
Wire.write(reg);
|
||||||
|
if (Wire.endTransmission(false) != 0) return false;
|
||||||
|
if (Wire.requestFrom(XCA9554_ADDR, (uint8_t)1) != 1) return false;
|
||||||
|
val = Wire.read();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool io_expander_init(void) {
|
||||||
|
// 1. Configure direction: EXIO0..2 outputs, rest inputs.
|
||||||
|
if (!write_reg(IOX_REG_CONFIG, IOX_CONFIG_MASK)) {
|
||||||
|
Serial.println("XCA9554 init failed (config)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 2. Drive all outputs LOW → hold display + touch in reset.
|
||||||
|
output_state = 0x00;
|
||||||
|
write_reg(IOX_REG_OUTPUT, output_state);
|
||||||
|
delay(20);
|
||||||
|
// 3. Release resets and enable audio amp output line.
|
||||||
|
output_state = IOX_OUTPUT_DEFAULT;
|
||||||
|
write_reg(IOX_REG_OUTPUT, output_state);
|
||||||
|
delay(20); // give SH8601 / FT3168 time to come out of reset
|
||||||
|
Serial.println("XCA9554 init OK");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void io_expander_set(uint8_t pin, bool high) {
|
||||||
|
if (pin > 7) return;
|
||||||
|
if (high) output_state |= (1u << pin);
|
||||||
|
else output_state &= ~(1u << pin);
|
||||||
|
write_reg(IOX_REG_OUTPUT, output_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool io_expander_get(uint8_t pin) {
|
||||||
|
if (pin > 7) return false;
|
||||||
|
uint8_t v = 0;
|
||||||
|
if (!read_reg(IOX_REG_INPUT, v)) return false;
|
||||||
|
return (v & (1u << pin)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BOARD_AMOLED_18
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// XCA9554 / PCA9554-compatible 8-bit I2C IO expander @ 0x20.
|
||||||
|
// Only compiled for BOARD_AMOLED_18 (the AMOLED-1.8 board routes LCD_RST,
|
||||||
|
// TP_RST, audio amp enable, and the PWR button through this expander).
|
||||||
|
//
|
||||||
|
// Must be initialized BEFORE the display or touch — skipping the reset
|
||||||
|
// release leaves the SH8601 and FT3168 in reset and they will fail to probe.
|
||||||
|
|
||||||
|
bool io_expander_init(void);
|
||||||
|
|
||||||
|
// Drive an EXIO pin (one of IOX_PIN_* in display_cfg.h that is configured
|
||||||
|
// as output). Updates the cached output register.
|
||||||
|
void io_expander_set(uint8_t pin, bool high);
|
||||||
|
|
||||||
|
// Read an EXIO pin configured as input (e.g. PWR button on EXIO4).
|
||||||
|
bool io_expander_get(uint8_t pin);
|
||||||
+127
-21
@@ -1,4 +1,5 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include "display_cfg.h"
|
#include "display_cfg.h"
|
||||||
@@ -12,26 +13,44 @@
|
|||||||
#include "idle.h"
|
#include "idle.h"
|
||||||
#include "idle_cfg.h"
|
#include "idle_cfg.h"
|
||||||
|
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
#include "io_expander.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// Physical buttons (global, screen-independent):
|
// Physical buttons (global, screen-independent):
|
||||||
// BTN_BACK (GPIO 0) — left, send Space (Claude Code voice mode push-to-talk)
|
// BTN_BACK (GPIO 0, BOOT) — left, send Space (Claude Code voice-mode PTT)
|
||||||
// BTN_FWD (GPIO 18) — right, send Shift+Tab (Claude Code mode toggle)
|
// BTN_FWD (GPIO 18) — AMOLED-2.16 only: Shift+Tab (mode toggle)
|
||||||
// AXP PWR (PMU) — middle, cycle screens; on splash, cycle animations
|
// PWR — middle, cycle screens; on splash, cycle animations
|
||||||
|
// AMOLED-2.16: AXP2101 PKEY IRQ
|
||||||
|
// AMOLED-1.8 : XCA9554 EXIO4 (polled over I2C)
|
||||||
#define BTN_BACK 0
|
#define BTN_BACK 0
|
||||||
|
#ifndef BOARD_AMOLED_18
|
||||||
#define BTN_FWD 18
|
#define BTN_FWD 18
|
||||||
|
#endif
|
||||||
|
|
||||||
// ---- Hardware objects ----
|
// ---- Hardware objects ----
|
||||||
Arduino_DataBus *bus = new Arduino_ESP32QSPI(
|
Arduino_DataBus *bus = new Arduino_ESP32QSPI(
|
||||||
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
|
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
|
||||||
Arduino_CO5300 *gfx = new Arduino_CO5300(
|
#ifdef BOARD_AMOLED_18
|
||||||
|
// SH8601 constructor: (bus, rst, rotation, w, h)
|
||||||
|
PlatformDisplay *gfx = new PlatformDisplay(
|
||||||
|
bus, LCD_RESET /* GFX_NOT_DEFINED — reset via XCA9554 */, 0,
|
||||||
|
LCD_WIDTH, LCD_HEIGHT);
|
||||||
|
#else
|
||||||
|
// CO5300 constructor: (bus, rst, rotation, w, h, col_offset1..2, row_offset1..2)
|
||||||
|
PlatformDisplay *gfx = new PlatformDisplay(
|
||||||
bus, LCD_RESET, 0 /* rotation */,
|
bus, LCD_RESET, 0 /* rotation */,
|
||||||
LCD_WIDTH, LCD_HEIGHT, 0, 0, 0, 0);
|
LCD_WIDTH, LCD_HEIGHT, 0, 0, 0, 0);
|
||||||
TouchDrvCST92xx touch;
|
TouchDrvCST92xx touch;
|
||||||
|
#endif
|
||||||
XPowersPMU pmu;
|
XPowersPMU pmu;
|
||||||
SensorQMI8658 imu;
|
SensorQMI8658 imu;
|
||||||
|
|
||||||
static UsageData usage = {};
|
static UsageData usage = {};
|
||||||
|
|
||||||
// ---- Touch interrupt + shared state ----
|
// ---- Touch interrupt + shared state ----
|
||||||
|
// Centralized once-per-loop read (CLAUDE.md gotcha #5): calling getPoint() /
|
||||||
|
// reading FT3168 from multiple sites consumes each other's data.
|
||||||
static volatile bool touch_pressed = false;
|
static volatile bool touch_pressed = false;
|
||||||
static volatile uint16_t touch_x = 0;
|
static volatile uint16_t touch_x = 0;
|
||||||
static volatile uint16_t touch_y = 0;
|
static volatile uint16_t touch_y = 0;
|
||||||
@@ -41,10 +60,56 @@ static void IRAM_ATTR touch_isr(void) {
|
|||||||
touch_data_ready = true;
|
touch_data_ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
// Minimal FT3168 reader (FocalTech standard register layout).
|
||||||
|
// Avoids vendoring Waveshare's GPLv3 Arduino_DriveBus library.
|
||||||
|
// reg 0x02: low nibble = active finger count
|
||||||
|
// reg 0x03/0x04: X1 high (low nibble), X1 low
|
||||||
|
// reg 0x05/0x06: Y1 high (low nibble), Y1 low
|
||||||
|
static void ft3168_init(void) {
|
||||||
|
// Power-mode register 0xA5 = 0x00: active scanning.
|
||||||
|
Wire.beginTransmission(FT3168_ADDR);
|
||||||
|
Wire.write(0xA5);
|
||||||
|
Wire.write(0x00);
|
||||||
|
Wire.endTransmission();
|
||||||
|
// Verify device ID register 0xA0 (FT3168 reports 0x03 but Waveshare's
|
||||||
|
// panel sometimes returns 0x86 — log but don't fail).
|
||||||
|
Wire.beginTransmission(FT3168_ADDR);
|
||||||
|
Wire.write(0xA0);
|
||||||
|
if (Wire.endTransmission(false) == 0 && Wire.requestFrom(FT3168_ADDR, (uint8_t)1) == 1) {
|
||||||
|
Serial.printf("FT3168 ID=0x%02X\n", Wire.read());
|
||||||
|
} else {
|
||||||
|
Serial.println("FT3168 ID read failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ft3168_read_into_shared_state(void) {
|
||||||
|
Wire.beginTransmission(FT3168_ADDR);
|
||||||
|
Wire.write(0x02);
|
||||||
|
if (Wire.endTransmission(false) != 0) { touch_pressed = false; return; }
|
||||||
|
if (Wire.requestFrom(FT3168_ADDR, (uint8_t)5) != 5) { touch_pressed = false; return; }
|
||||||
|
uint8_t fingers = Wire.read() & 0x0F;
|
||||||
|
uint8_t xH = Wire.read();
|
||||||
|
uint8_t xL = Wire.read();
|
||||||
|
uint8_t yH = Wire.read();
|
||||||
|
uint8_t yL = Wire.read();
|
||||||
|
if (fingers == 0 || fingers > 5) {
|
||||||
|
touch_pressed = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
touch_x = ((uint16_t)(xH & 0x0F) << 8) | xL;
|
||||||
|
touch_y = ((uint16_t)(yH & 0x0F) << 8) | yL;
|
||||||
|
touch_pressed = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void touch_read() {
|
static void touch_read() {
|
||||||
if (!touch_data_ready) return;
|
if (!touch_data_ready) return;
|
||||||
touch_data_ready = false;
|
touch_data_ready = false;
|
||||||
|
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
ft3168_read_into_shared_state();
|
||||||
|
#else
|
||||||
int16_t tx[5], ty[5];
|
int16_t tx[5], ty[5];
|
||||||
uint8_t n = touch.getPoint(tx, ty, touch.getSupportTouchPoint());
|
uint8_t n = touch.getPoint(tx, ty, touch.getSupportTouchPoint());
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
@@ -54,6 +119,7 @@ static void touch_read() {
|
|||||||
} else {
|
} else {
|
||||||
touch_pressed = false;
|
touch_pressed = false;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Touch policy is driven by IDLE_WAKE_ON_TOUCH:
|
// Touch policy is driven by IDLE_WAKE_ON_TOUCH:
|
||||||
// true → a press edge while asleep wakes the device and the first
|
// true → a press edge while asleep wakes the device and the first
|
||||||
@@ -99,9 +165,11 @@ static uint32_t my_tick(void) {
|
|||||||
return millis();
|
return millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef BOARD_AMOLED_18
|
||||||
// Rotate a w×h strip and compute destination coordinates on the 480×480 display.
|
// Rotate a w×h strip and compute destination coordinates on the 480×480 display.
|
||||||
// src pixels are in row-major order for the rectangle (sx, sy, w, h).
|
// src pixels are in row-major order for the rectangle (sx, sy, w, h).
|
||||||
// Output goes to rot_buf in row-major order for the destination rectangle.
|
// Output goes to rot_buf in row-major order for the destination rectangle.
|
||||||
|
// AMOLED-1.8 port is fixed at 0° so this code is excluded.
|
||||||
static void rotate_strip(const uint16_t *src, int32_t w, int32_t h,
|
static void rotate_strip(const uint16_t *src, int32_t w, int32_t h,
|
||||||
int32_t sx, int32_t sy, uint8_t r,
|
int32_t sx, int32_t sy, uint8_t r,
|
||||||
int32_t *dx, int32_t *dy, int32_t *dw, int32_t *dh) {
|
int32_t *dx, int32_t *dy, int32_t *dw, int32_t *dh) {
|
||||||
@@ -148,14 +216,20 @@ static void rotate_strip(const uint16_t *src, int32_t w, int32_t h,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif // !BOARD_AMOLED_18
|
||||||
|
|
||||||
// LVGL flush callback — rotates partial strips and writes to display
|
// LVGL flush callback — writes pixels to display.
|
||||||
|
// AMOLED-2.16: applies CPU strip rotation based on IMU.
|
||||||
|
// AMOLED-1.8 : fixed orientation, direct pass-through.
|
||||||
static void my_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map) {
|
static void my_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map) {
|
||||||
int32_t w = area->x2 - area->x1 + 1;
|
int32_t w = area->x2 - area->x1 + 1;
|
||||||
int32_t h = area->y2 - area->y1 + 1;
|
int32_t h = area->y2 - area->y1 + 1;
|
||||||
uint16_t *src = (uint16_t*)px_map;
|
uint16_t *src = (uint16_t*)px_map;
|
||||||
uint8_t r = imu_get_rotation();
|
|
||||||
|
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
gfx->draw16bitRGBBitmap(area->x1, area->y1, src, w, h);
|
||||||
|
#else
|
||||||
|
uint8_t r = imu_get_rotation();
|
||||||
if (r == 0) {
|
if (r == 0) {
|
||||||
gfx->draw16bitRGBBitmap(area->x1, area->y1, src, w, h);
|
gfx->draw16bitRGBBitmap(area->x1, area->y1, src, w, h);
|
||||||
} else {
|
} else {
|
||||||
@@ -163,10 +237,12 @@ static void my_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* px_m
|
|||||||
rotate_strip(src, w, h, area->x1, area->y1, r, &dx, &dy, &dw, &dh);
|
rotate_strip(src, w, h, area->x1, area->y1, r, &dx, &dy, &dw, &dh);
|
||||||
gfx->draw16bitRGBBitmap(dx, dy, rot_buf, dw, dh);
|
gfx->draw16bitRGBBitmap(dx, dy, rot_buf, dw, dh);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
lv_display_flush_ready(disp);
|
lv_display_flush_ready(disp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CO5300 requires even-aligned flush regions
|
// CO5300 requires even-aligned flush regions. SH8601's driver doesn't
|
||||||
|
// enforce this in source, but keeping the rounder is harmless.
|
||||||
static void rounder_cb(lv_event_t* e) {
|
static void rounder_cb(lv_event_t* e) {
|
||||||
lv_area_t *area = (lv_area_t*)lv_event_get_param(e);
|
lv_area_t *area = (lv_area_t*)lv_event_get_param(e);
|
||||||
area->x1 = area->x1 & ~1;
|
area->x1 = area->x1 & ~1;
|
||||||
@@ -260,9 +336,15 @@ void setup() {
|
|||||||
delay(300);
|
delay(300);
|
||||||
Serial.println("{\"ready\":true}");
|
Serial.println("{\"ready\":true}");
|
||||||
|
|
||||||
// Init I2C (shared by touch + PMU)
|
// Init I2C (shared by touch + PMU + IMU + IO expander)
|
||||||
Wire.begin(IIC_SDA, IIC_SCL);
|
Wire.begin(IIC_SDA, IIC_SCL);
|
||||||
|
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
// XCA9554 must come up FIRST — display + touch are held in reset until
|
||||||
|
// EXIO0..2 go HIGH (see io_expander_init()).
|
||||||
|
io_expander_init();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Init display
|
// Init display
|
||||||
gfx->begin();
|
gfx->begin();
|
||||||
gfx->fillScreen(0x0000);
|
gfx->fillScreen(0x0000);
|
||||||
@@ -271,10 +353,17 @@ void setup() {
|
|||||||
// Init PMU
|
// Init PMU
|
||||||
power_init();
|
power_init();
|
||||||
|
|
||||||
// Init IMU (accelerometer for auto-rotation)
|
// Init IMU (accelerometer for auto-rotation; on AMOLED-1.8 we keep init
|
||||||
|
// for I2C bus health but ignore rotation — see imu.cpp).
|
||||||
imu_init();
|
imu_init();
|
||||||
|
|
||||||
// Init touch
|
// Init touch
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
ft3168_init();
|
||||||
|
pinMode(TP_INT, INPUT_PULLUP);
|
||||||
|
attachInterrupt(TP_INT, touch_isr, FALLING);
|
||||||
|
Serial.println("FT3168 attached on INT pin");
|
||||||
|
#else
|
||||||
touch.setPins(TP_RST, TP_INT);
|
touch.setPins(TP_RST, TP_INT);
|
||||||
if (!touch.begin(Wire, CST9220_ADDR, IIC_SDA, IIC_SCL)) {
|
if (!touch.begin(Wire, CST9220_ADDR, IIC_SDA, IIC_SCL)) {
|
||||||
Serial.println("Touch init failed");
|
Serial.println("Touch init failed");
|
||||||
@@ -285,6 +374,7 @@ void setup() {
|
|||||||
attachInterrupt(TP_INT, touch_isr, FALLING);
|
attachInterrupt(TP_INT, touch_isr, FALLING);
|
||||||
Serial.println("Touch init OK");
|
Serial.println("Touch init OK");
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Init LVGL
|
// Init LVGL
|
||||||
lv_init();
|
lv_init();
|
||||||
@@ -293,9 +383,11 @@ void setup() {
|
|||||||
// Allocate PSRAM-backed partial render buffers
|
// Allocate PSRAM-backed partial render buffers
|
||||||
buf1 = (uint16_t*)heap_caps_malloc(LCD_WIDTH * BUF_LINES * 2, MALLOC_CAP_SPIRAM);
|
buf1 = (uint16_t*)heap_caps_malloc(LCD_WIDTH * BUF_LINES * 2, MALLOC_CAP_SPIRAM);
|
||||||
buf2 = (uint16_t*)heap_caps_malloc(LCD_WIDTH * BUF_LINES * 2, MALLOC_CAP_SPIRAM);
|
buf2 = (uint16_t*)heap_caps_malloc(LCD_WIDTH * BUF_LINES * 2, MALLOC_CAP_SPIRAM);
|
||||||
// rot_buf needs to hold the largest possible strip after rotation
|
#ifndef BOARD_AMOLED_18
|
||||||
// A 480×40 strip rotated 90° becomes 40×480, same pixel count
|
// rot_buf only needed for AMOLED-2.16 (CPU strip rotation).
|
||||||
|
// Holds the largest possible strip after rotation (same pixel count as src).
|
||||||
rot_buf = (uint16_t*)heap_caps_malloc(LCD_WIDTH * BUF_LINES * 2, MALLOC_CAP_SPIRAM);
|
rot_buf = (uint16_t*)heap_caps_malloc(LCD_WIDTH * BUF_LINES * 2, MALLOC_CAP_SPIRAM);
|
||||||
|
#endif
|
||||||
|
|
||||||
lv_display_t* disp = lv_display_create(LCD_WIDTH, LCD_HEIGHT);
|
lv_display_t* disp = lv_display_create(LCD_WIDTH, LCD_HEIGHT);
|
||||||
lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
|
lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
|
||||||
@@ -313,9 +405,11 @@ void setup() {
|
|||||||
// Init BLE data channel
|
// Init BLE data channel
|
||||||
ble_init();
|
ble_init();
|
||||||
|
|
||||||
// Physical buttons: back (GPIO 0) and forward (GPIO 18)
|
// Physical buttons
|
||||||
pinMode(BTN_BACK, INPUT_PULLUP);
|
pinMode(BTN_BACK, INPUT_PULLUP);
|
||||||
|
#ifndef BOARD_AMOLED_18
|
||||||
pinMode(BTN_FWD, INPUT_PULLUP);
|
pinMode(BTN_FWD, INPUT_PULLUP);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Build dashboard
|
// Build dashboard
|
||||||
ui_init();
|
ui_init();
|
||||||
@@ -333,7 +427,9 @@ void setup() {
|
|||||||
|
|
||||||
static ble_state_t last_ble_state = BLE_STATE_INIT;
|
static ble_state_t last_ble_state = BLE_STATE_INIT;
|
||||||
|
|
||||||
// Brightness ramp state for rotation transition
|
#ifndef BOARD_AMOLED_18
|
||||||
|
// Brightness ramp state for rotation transition.
|
||||||
|
// AMOLED-2.16 only — the 1.8" port is fixed at 0° (no IMU rotation).
|
||||||
// On rotation change we blank the panel, force a full LVGL redraw at the
|
// On rotation change we blank the panel, force a full LVGL redraw at the
|
||||||
// new orientation, then ramp brightness back up over ~125ms so the
|
// new orientation, then ramp brightness back up over ~125ms so the
|
||||||
// transition reads as deliberate instead of as a glitch.
|
// transition reads as deliberate instead of as a glitch.
|
||||||
@@ -366,6 +462,7 @@ static void handle_rotation_change(void) {
|
|||||||
if (ramp_step >= 4) ramp_step = 0;
|
if (ramp_step >= 4) ramp_step = 0;
|
||||||
else ramp_step++;
|
else ramp_step++;
|
||||||
}
|
}
|
||||||
|
#endif // !BOARD_AMOLED_18
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
touch_read();
|
touch_read();
|
||||||
@@ -377,20 +474,23 @@ void loop() {
|
|||||||
imu_tick();
|
imu_tick();
|
||||||
splash_tick();
|
splash_tick();
|
||||||
|
|
||||||
// Three-button input (global, screen-independent):
|
// Physical button input (global, screen-independent):
|
||||||
// LEFT (GPIO 0) → Space (voice-mode push-to-talk; press & release tracked)
|
// LEFT (GPIO 0 / BOOT) → Space (voice-mode push-to-talk)
|
||||||
// RIGHT (GPIO 18) → Shift+Tab (Claude Code mode toggle)
|
// RIGHT (GPIO 18) → Shift+Tab (Claude Code mode toggle). AMOLED-2.16 only.
|
||||||
// PWR (AXP) → cycle screens; on splash, cycle animations
|
// PWR → cycle screens; on splash, cycle animations.
|
||||||
|
// AMOLED-2.16: AXP2101 PKEY IRQ; AMOLED-1.8: XCA9554 EXIO4.
|
||||||
// First press from sleep is consumed for wake only (idle_consume_wake_press
|
// First press from sleep is consumed for wake only (idle_consume_wake_press
|
||||||
// returns true) — the normal action only fires from the second press.
|
// returns true) — the normal action only fires from the second press.
|
||||||
// Activity bookkeeping happens inside idle_consume_wake_press, so no
|
// Activity bookkeeping happens inside idle_consume_wake_press, so no
|
||||||
// separate idle_note_activity() call is needed here.
|
// separate idle_note_activity() call is needed here.
|
||||||
{
|
{
|
||||||
static bool back_was = false, fwd_was = false;
|
static bool back_was = false;
|
||||||
static bool back_wake_swallowed = false, fwd_wake_swallowed = false;
|
static bool back_wake_swallowed = false;
|
||||||
|
#ifndef BOARD_AMOLED_18
|
||||||
|
static bool fwd_was = false;
|
||||||
|
static bool fwd_wake_swallowed = false;
|
||||||
|
#endif
|
||||||
bool back_now = (digitalRead(BTN_BACK) == LOW);
|
bool back_now = (digitalRead(BTN_BACK) == LOW);
|
||||||
bool fwd_now = (digitalRead(BTN_FWD) == LOW);
|
|
||||||
|
|
||||||
if (back_now != back_was) {
|
if (back_now != back_was) {
|
||||||
if (back_now) {
|
if (back_now) {
|
||||||
if (idle_consume_wake_press()) {
|
if (idle_consume_wake_press()) {
|
||||||
@@ -404,6 +504,9 @@ void loop() {
|
|||||||
}
|
}
|
||||||
back_was = back_now;
|
back_was = back_now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef BOARD_AMOLED_18
|
||||||
|
bool fwd_now = (digitalRead(BTN_FWD) == LOW);
|
||||||
if (fwd_now != fwd_was) {
|
if (fwd_now != fwd_was) {
|
||||||
if (fwd_now) {
|
if (fwd_now) {
|
||||||
if (idle_consume_wake_press()) {
|
if (idle_consume_wake_press()) {
|
||||||
@@ -417,6 +520,7 @@ void loop() {
|
|||||||
}
|
}
|
||||||
fwd_was = fwd_now;
|
fwd_was = fwd_now;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (power_pwr_pressed()) {
|
if (power_pwr_pressed()) {
|
||||||
if (!idle_consume_wake_press()) {
|
if (!idle_consume_wake_press()) {
|
||||||
@@ -426,7 +530,9 @@ void loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef BOARD_AMOLED_18
|
||||||
handle_rotation_change();
|
handle_rotation_change();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Update BLE status on screen when state changes
|
// Update BLE status on screen when state changes
|
||||||
ble_state_t bs = ble_get_state();
|
ble_state_t bs = ble_get_state();
|
||||||
|
|||||||
+23
-2
@@ -2,6 +2,10 @@
|
|||||||
#include "display_cfg.h"
|
#include "display_cfg.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
#include "io_expander.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// Poll intervals
|
// Poll intervals
|
||||||
#define BATTERY_POLL_MS 2000
|
#define BATTERY_POLL_MS 2000
|
||||||
#define CHARGING_POLL_MS 500
|
#define CHARGING_POLL_MS 500
|
||||||
@@ -15,6 +19,10 @@ static uint32_t last_charging_ms = 0;
|
|||||||
static uint32_t last_pwr_ms = 0;
|
static uint32_t last_pwr_ms = 0;
|
||||||
#define PWR_POLL_MS 50
|
#define PWR_POLL_MS 50
|
||||||
|
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
static bool last_pwr_state = false; // edge detection for XCA9554 EXIO4
|
||||||
|
#endif
|
||||||
|
|
||||||
void power_init(void) {
|
void power_init(void) {
|
||||||
if (!pmu.begin(Wire, AXP2101_ADDR, IIC_SDA, IIC_SCL)) {
|
if (!pmu.begin(Wire, AXP2101_ADDR, IIC_SDA, IIC_SCL)) {
|
||||||
Serial.println("AXP2101 init failed");
|
Serial.println("AXP2101 init failed");
|
||||||
@@ -25,10 +33,14 @@ void power_init(void) {
|
|||||||
pmu.enableBattDetection();
|
pmu.enableBattDetection();
|
||||||
pmu.enableBattVoltageMeasure();
|
pmu.enableBattVoltageMeasure();
|
||||||
|
|
||||||
// Enable PWR button short-press IRQ (mid button for cycling screens)
|
#ifndef BOARD_AMOLED_18
|
||||||
|
// AMOLED-2.16: PWR button events come from AXP2101 PKEY short-press IRQ.
|
||||||
|
// AMOLED-1.8 routes the PWR button through XCA9554 EXIO4 instead — we
|
||||||
|
// poll it in power_tick() rather than subscribing to the PMU IRQ.
|
||||||
pmu.disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
pmu.disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||||||
pmu.clearIrqStatus();
|
pmu.clearIrqStatus();
|
||||||
pmu.enableIRQ(XPOWERS_AXP2101_PKEY_SHORT_IRQ);
|
pmu.enableIRQ(XPOWERS_AXP2101_PKEY_SHORT_IRQ);
|
||||||
|
#endif
|
||||||
|
|
||||||
cached_charging = pmu.isCharging();
|
cached_charging = pmu.isCharging();
|
||||||
cached_vbus = pmu.isVbusIn();
|
cached_vbus = pmu.isVbusIn();
|
||||||
@@ -49,14 +61,23 @@ void power_tick(void) {
|
|||||||
cached_pct = pmu.getBatteryPercent();
|
cached_pct = pmu.getBatteryPercent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll PWR button (AXP2101 short-press IRQ)
|
// Poll PWR button
|
||||||
if (now - last_pwr_ms >= PWR_POLL_MS) {
|
if (now - last_pwr_ms >= PWR_POLL_MS) {
|
||||||
last_pwr_ms = now;
|
last_pwr_ms = now;
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
// XCA9554 EXIO4 — active HIGH, edge-trigger on press
|
||||||
|
bool pwr_now = io_expander_get(IOX_PIN_PWR_BTN);
|
||||||
|
if (pwr_now && !last_pwr_state) {
|
||||||
|
pwr_pressed_flag = true;
|
||||||
|
}
|
||||||
|
last_pwr_state = pwr_now;
|
||||||
|
#else
|
||||||
pmu.getIrqStatus();
|
pmu.getIrqStatus();
|
||||||
if (pmu.isPekeyShortPressIrq()) {
|
if (pmu.isPekeyShortPressIrq()) {
|
||||||
pwr_pressed_flag = true;
|
pwr_pressed_flag = true;
|
||||||
}
|
}
|
||||||
pmu.clearIrqStatus();
|
pmu.clearIrqStatus();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -2,13 +2,21 @@
|
|||||||
#include "splash_animations.h"
|
#include "splash_animations.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
#include "usage_rate.h"
|
#include "usage_rate.h"
|
||||||
|
#include "display_cfg.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <esp_heap_caps.h>
|
#include <esp_heap_caps.h>
|
||||||
|
|
||||||
// 20x20 grid scaled 24x to fill 480x480
|
// 20x20 grid. CELL chosen per board so the canvas fits the screen
|
||||||
|
// (must satisfy GRID*CELL <= min(LCD_WIDTH, LCD_HEIGHT)).
|
||||||
|
// AMOLED-2.16 (480x480 square): CELL=24 → 480x480 fills screen
|
||||||
|
// AMOLED-1.8 (368x448 portrait): CELL=18 → 360x360 centered, vertical margin
|
||||||
#define GRID 20
|
#define GRID 20
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
#define CELL 18
|
||||||
|
#else
|
||||||
#define CELL 24
|
#define CELL 24
|
||||||
|
#endif
|
||||||
#define CANVAS_W (GRID * CELL)
|
#define CANVAS_W (GRID * CELL)
|
||||||
#define CANVAS_H (GRID * CELL)
|
#define CANVAS_H (GRID * CELL)
|
||||||
|
|
||||||
@@ -99,7 +107,7 @@ void splash_init(lv_obj_t *parent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
splash_container = lv_obj_create(parent);
|
splash_container = lv_obj_create(parent);
|
||||||
lv_obj_set_size(splash_container, 480, 480);
|
lv_obj_set_size(splash_container, LCD_WIDTH, LCD_HEIGHT);
|
||||||
lv_obj_set_pos(splash_container, 0, 0);
|
lv_obj_set_pos(splash_container, 0, 0);
|
||||||
lv_obj_set_style_bg_color(splash_container, THEME_BG, 0);
|
lv_obj_set_style_bg_color(splash_container, THEME_BG, 0);
|
||||||
lv_obj_set_style_bg_opa(splash_container, LV_OPA_COVER, 0);
|
lv_obj_set_style_bg_opa(splash_container, LV_OPA_COVER, 0);
|
||||||
|
|||||||
+68
-23
@@ -7,12 +7,31 @@
|
|||||||
|
|
||||||
// Custom fonts (scaled for 314 PPI, ~1.9x from original 165 PPI)
|
// Custom fonts (scaled for 314 PPI, ~1.9x from original 165 PPI)
|
||||||
LV_FONT_DECLARE(font_tiempos_56);
|
LV_FONT_DECLARE(font_tiempos_56);
|
||||||
|
LV_FONT_DECLARE(font_tiempos_34);
|
||||||
LV_FONT_DECLARE(font_styrene_48);
|
LV_FONT_DECLARE(font_styrene_48);
|
||||||
LV_FONT_DECLARE(font_styrene_28);
|
LV_FONT_DECLARE(font_styrene_28);
|
||||||
LV_FONT_DECLARE(font_styrene_24);
|
LV_FONT_DECLARE(font_styrene_24);
|
||||||
LV_FONT_DECLARE(font_styrene_20);
|
LV_FONT_DECLARE(font_styrene_20);
|
||||||
|
LV_FONT_DECLARE(font_styrene_16);
|
||||||
|
LV_FONT_DECLARE(font_styrene_14);
|
||||||
LV_FONT_DECLARE(font_mono_32);
|
LV_FONT_DECLARE(font_mono_32);
|
||||||
|
|
||||||
|
// AMOLED-1.8 (368 wide) needs smaller fonts on the Bluetooth screen so the
|
||||||
|
// MAC address and credit lines don't overflow horizontally.
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
#define BT_TITLE_FONT font_tiempos_34
|
||||||
|
#define BT_STATUS_FONT font_styrene_28
|
||||||
|
#define BT_DEVICE_FONT font_styrene_20
|
||||||
|
#define BT_CREDIT_1_FONT font_styrene_16
|
||||||
|
#define BT_CREDIT_2_FONT font_styrene_14
|
||||||
|
#else
|
||||||
|
#define BT_TITLE_FONT font_tiempos_56
|
||||||
|
#define BT_STATUS_FONT font_styrene_48
|
||||||
|
#define BT_DEVICE_FONT font_styrene_28
|
||||||
|
#define BT_CREDIT_1_FONT font_styrene_24
|
||||||
|
#define BT_CREDIT_2_FONT font_styrene_20
|
||||||
|
#endif
|
||||||
|
|
||||||
// Anthropic brand palette — design tokens live in theme.h
|
// Anthropic brand palette — design tokens live in theme.h
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
#define COL_BG THEME_BG
|
#define COL_BG THEME_BG
|
||||||
@@ -25,13 +44,19 @@ LV_FONT_DECLARE(font_mono_32);
|
|||||||
#define COL_RED THEME_RED
|
#define COL_RED THEME_RED
|
||||||
#define COL_BAR_BG THEME_BAR_BG
|
#define COL_BAR_BG THEME_BAR_BG
|
||||||
|
|
||||||
// ---- Layout constants for 480x480 (scaled for 2.16" high-DPI + rounded corners) ----
|
// ---- Layout constants ----
|
||||||
#define SCR_W 480
|
// Width/height track the active display (480x480 for AMOLED-2.16, 368x448 for AMOLED-1.8).
|
||||||
#define SCR_H 480
|
// MARGIN clears rounded display corners on both panels.
|
||||||
#define MARGIN 20 // wider margin for rounded display corners
|
#define SCR_W LCD_WIDTH
|
||||||
|
#define SCR_H LCD_HEIGHT
|
||||||
|
#define MARGIN 20
|
||||||
#define TITLE_Y 30
|
#define TITLE_Y 30
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
#define CONTENT_Y 85 // tighter vertical packing for 448-tall portrait
|
||||||
|
#else
|
||||||
#define CONTENT_Y 100
|
#define CONTENT_Y 100
|
||||||
#define CONTENT_W (SCR_W - 2 * MARGIN) // 440
|
#endif
|
||||||
|
#define CONTENT_W (SCR_W - 2 * MARGIN)
|
||||||
|
|
||||||
// ---- Usage screen widgets ----
|
// ---- Usage screen widgets ----
|
||||||
static lv_obj_t* usage_container;
|
static lv_obj_t* usage_container;
|
||||||
@@ -218,14 +243,26 @@ static void init_battery_icons(void) {
|
|||||||
init_icon_dsc_rgb565a8(&battery_dscs[4], ICON_BATTERY_CHARGING_W, ICON_BATTERY_CHARGING_H, icon_battery_charging_data);
|
init_icon_dsc_rgb565a8(&battery_dscs[4], ICON_BATTERY_CHARGING_W, ICON_BATTERY_CHARGING_H, icon_battery_charging_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======== Usage Screen (480x480) ========
|
// ======== Usage Screen ========
|
||||||
|
|
||||||
#define PANEL_H 150
|
#ifdef BOARD_AMOLED_18
|
||||||
#define PANEL_GAP 16
|
// 368x448 portrait — compressed vertical layout so two panels + bottom anim
|
||||||
|
// label fit without overlap.
|
||||||
|
#define PANEL_H 130
|
||||||
|
#define PANEL_GAP 12
|
||||||
|
#define PANEL_BAR_Y 48
|
||||||
|
#define PANEL_RESET_Y 78
|
||||||
|
#else
|
||||||
|
// 480x480 square (original)
|
||||||
|
#define PANEL_H 150
|
||||||
|
#define PANEL_GAP 16
|
||||||
|
#define PANEL_BAR_Y 56
|
||||||
|
#define PANEL_RESET_Y 94
|
||||||
|
#endif
|
||||||
|
|
||||||
// One Session/Weekly panel: big % label, pill on the right, bar, reset label.
|
// One Session/Weekly panel: big % label, pill on the right, bar, reset label.
|
||||||
// Pill y=1: symmetric inside the panel — panel-outer-top → pill-top equals
|
// Pill y=1: symmetric inside the panel — panel-outer-top → pill-top equals
|
||||||
// pill-bottom → bar-top (pill height 42 + panel pad_top 12 + bar y=56).
|
// pill-bottom → bar-top.
|
||||||
static void make_usage_panel(lv_obj_t* parent, int y, const char* pill_text,
|
static void make_usage_panel(lv_obj_t* parent, int y, const char* pill_text,
|
||||||
lv_obj_t** out_pct, lv_obj_t** out_pill,
|
lv_obj_t** out_pct, lv_obj_t** out_pill,
|
||||||
lv_obj_t** out_bar, lv_obj_t** out_reset) {
|
lv_obj_t** out_bar, lv_obj_t** out_reset) {
|
||||||
@@ -240,13 +277,13 @@ static void make_usage_panel(lv_obj_t* parent, int y, const char* pill_text,
|
|||||||
*out_pill = make_pill(panel, pill_text);
|
*out_pill = make_pill(panel, pill_text);
|
||||||
lv_obj_align(*out_pill, LV_ALIGN_TOP_RIGHT, 0, 1);
|
lv_obj_align(*out_pill, LV_ALIGN_TOP_RIGHT, 0, 1);
|
||||||
|
|
||||||
*out_bar = make_bar(panel, 0, 56, CONTENT_W - 32, 24);
|
*out_bar = make_bar(panel, 0, PANEL_BAR_Y, CONTENT_W - 32, 24);
|
||||||
|
|
||||||
*out_reset = lv_label_create(panel);
|
*out_reset = lv_label_create(panel);
|
||||||
lv_label_set_text(*out_reset, "---");
|
lv_label_set_text(*out_reset, "---");
|
||||||
lv_obj_set_style_text_font(*out_reset, &font_styrene_28, 0);
|
lv_obj_set_style_text_font(*out_reset, &font_styrene_28, 0);
|
||||||
lv_obj_set_style_text_color(*out_reset, COL_DIM, 0);
|
lv_obj_set_style_text_color(*out_reset, COL_DIM, 0);
|
||||||
lv_obj_set_pos(*out_reset, 0, 94);
|
lv_obj_set_pos(*out_reset, 0, PANEL_RESET_Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void init_usage_screen(lv_obj_t* scr) {
|
static void init_usage_screen(lv_obj_t* scr) {
|
||||||
@@ -279,7 +316,15 @@ static void init_usage_screen(lv_obj_t* scr) {
|
|||||||
lv_obj_align(lbl_anim, LV_ALIGN_BOTTOM_MID, 0, -15);
|
lv_obj_align(lbl_anim, LV_ALIGN_BOTTOM_MID, 0, -15);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======== Bluetooth Screen (480x480) ========
|
// ======== Bluetooth Screen ========
|
||||||
|
|
||||||
|
#ifdef BOARD_AMOLED_18
|
||||||
|
#define BT_INFO_PANEL_H 140
|
||||||
|
#define BT_RESET_ZONE_H 90
|
||||||
|
#else
|
||||||
|
#define BT_INFO_PANEL_H 160
|
||||||
|
#define BT_RESET_ZONE_H 110
|
||||||
|
#endif
|
||||||
|
|
||||||
static void init_bluetooth_screen(lv_obj_t* scr) {
|
static void init_bluetooth_screen(lv_obj_t* scr) {
|
||||||
ble_container = lv_obj_create(scr);
|
ble_container = lv_obj_create(scr);
|
||||||
@@ -293,12 +338,12 @@ static void init_bluetooth_screen(lv_obj_t* scr) {
|
|||||||
// Title
|
// Title
|
||||||
lv_obj_t* lbl_ble_title = lv_label_create(ble_container);
|
lv_obj_t* lbl_ble_title = lv_label_create(ble_container);
|
||||||
lv_label_set_text(lbl_ble_title, "Bluetooth");
|
lv_label_set_text(lbl_ble_title, "Bluetooth");
|
||||||
lv_obj_set_style_text_font(lbl_ble_title, &font_tiempos_56, 0);
|
lv_obj_set_style_text_font(lbl_ble_title, &BT_TITLE_FONT, 0);
|
||||||
lv_obj_set_style_text_color(lbl_ble_title, COL_TEXT, 0);
|
lv_obj_set_style_text_color(lbl_ble_title, COL_TEXT, 0);
|
||||||
lv_obj_align(lbl_ble_title, LV_ALIGN_TOP_MID, 16, TITLE_Y);
|
lv_obj_align(lbl_ble_title, LV_ALIGN_TOP_MID, 16, TITLE_Y);
|
||||||
|
|
||||||
// Info panel (taller for 480x480)
|
// Info panel
|
||||||
lv_obj_t* p_info = make_panel(ble_container, MARGIN, CONTENT_Y, CONTENT_W, 160);
|
lv_obj_t* p_info = make_panel(ble_container, MARGIN, CONTENT_Y, CONTENT_W, BT_INFO_PANEL_H);
|
||||||
|
|
||||||
// Bluetooth icon + status row
|
// Bluetooth icon + status row
|
||||||
static lv_image_dsc_t icon_bt_dsc;
|
static lv_image_dsc_t icon_bt_dsc;
|
||||||
@@ -310,27 +355,27 @@ static void init_bluetooth_screen(lv_obj_t* scr) {
|
|||||||
|
|
||||||
lbl_ble_status = lv_label_create(p_info);
|
lbl_ble_status = lv_label_create(p_info);
|
||||||
lv_label_set_text(lbl_ble_status, "Initializing...");
|
lv_label_set_text(lbl_ble_status, "Initializing...");
|
||||||
lv_obj_set_style_text_font(lbl_ble_status, &font_styrene_48, 0);
|
lv_obj_set_style_text_font(lbl_ble_status, &BT_STATUS_FONT, 0);
|
||||||
lv_obj_set_style_text_color(lbl_ble_status, COL_DIM, 0);
|
lv_obj_set_style_text_color(lbl_ble_status, COL_DIM, 0);
|
||||||
lv_obj_set_pos(lbl_ble_status, 56, 2);
|
lv_obj_set_pos(lbl_ble_status, 56, 2);
|
||||||
|
|
||||||
lbl_ble_device = lv_label_create(p_info);
|
lbl_ble_device = lv_label_create(p_info);
|
||||||
lv_label_set_text(lbl_ble_device, "Device: ---");
|
lv_label_set_text(lbl_ble_device, "Device: ---");
|
||||||
lv_obj_set_style_text_font(lbl_ble_device, &font_styrene_28, 0);
|
lv_obj_set_style_text_font(lbl_ble_device, &BT_DEVICE_FONT, 0);
|
||||||
lv_obj_set_style_text_color(lbl_ble_device, COL_DIM, 0);
|
lv_obj_set_style_text_color(lbl_ble_device, COL_DIM, 0);
|
||||||
lv_obj_set_pos(lbl_ble_device, 0, 64);
|
lv_obj_set_pos(lbl_ble_device, 0, 64);
|
||||||
|
|
||||||
lbl_ble_mac = lv_label_create(p_info);
|
lbl_ble_mac = lv_label_create(p_info);
|
||||||
lv_label_set_text(lbl_ble_mac, "Address: ---");
|
lv_label_set_text(lbl_ble_mac, "Address: ---");
|
||||||
lv_obj_set_style_text_font(lbl_ble_mac, &font_styrene_28, 0);
|
lv_obj_set_style_text_font(lbl_ble_mac, &BT_DEVICE_FONT, 0);
|
||||||
lv_obj_set_style_text_color(lbl_ble_mac, COL_DIM, 0);
|
lv_obj_set_style_text_color(lbl_ble_mac, COL_DIM, 0);
|
||||||
lv_obj_set_pos(lbl_ble_mac, 0, 100);
|
lv_obj_set_pos(lbl_ble_mac, 0, 100);
|
||||||
|
|
||||||
// Reset Bluetooth tap zone with trash icon
|
// Reset Bluetooth tap zone with trash icon
|
||||||
int reset_y = CONTENT_Y + 160 + 16;
|
int reset_y = CONTENT_Y + BT_INFO_PANEL_H + 16;
|
||||||
lv_obj_t* reset_zone = lv_obj_create(ble_container);
|
lv_obj_t* reset_zone = lv_obj_create(ble_container);
|
||||||
lv_obj_set_pos(reset_zone, MARGIN, reset_y);
|
lv_obj_set_pos(reset_zone, MARGIN, reset_y);
|
||||||
lv_obj_set_size(reset_zone, CONTENT_W, 110);
|
lv_obj_set_size(reset_zone, CONTENT_W, BT_RESET_ZONE_H);
|
||||||
lv_obj_set_style_bg_color(reset_zone, COL_PANEL, 0);
|
lv_obj_set_style_bg_color(reset_zone, COL_PANEL, 0);
|
||||||
lv_obj_set_style_bg_opa(reset_zone, LV_OPA_COVER, 0);
|
lv_obj_set_style_bg_opa(reset_zone, LV_OPA_COVER, 0);
|
||||||
lv_obj_set_style_radius(reset_zone, 8, 0);
|
lv_obj_set_style_radius(reset_zone, 8, 0);
|
||||||
@@ -348,19 +393,19 @@ static void init_bluetooth_screen(lv_obj_t* scr) {
|
|||||||
|
|
||||||
lv_obj_t* reset_lbl = lv_label_create(reset_zone);
|
lv_obj_t* reset_lbl = lv_label_create(reset_zone);
|
||||||
lv_label_set_text(reset_lbl, "Reset Bluetooth");
|
lv_label_set_text(reset_lbl, "Reset Bluetooth");
|
||||||
lv_obj_set_style_text_font(reset_lbl, &font_styrene_28, 0);
|
lv_obj_set_style_text_font(reset_lbl, &BT_DEVICE_FONT, 0);
|
||||||
lv_obj_set_style_text_color(reset_lbl, COL_DIM, 0);
|
lv_obj_set_style_text_color(reset_lbl, COL_DIM, 0);
|
||||||
|
|
||||||
// Attribution
|
// Attribution
|
||||||
lv_obj_t* lbl_credit = lv_label_create(ble_container);
|
lv_obj_t* lbl_credit = lv_label_create(ble_container);
|
||||||
lv_label_set_text(lbl_credit, "Built by @hermannbjorgvin");
|
lv_label_set_text(lbl_credit, "Built by @hermannbjorgvin");
|
||||||
lv_obj_set_style_text_font(lbl_credit, &font_styrene_24, 0);
|
lv_obj_set_style_text_font(lbl_credit, &BT_CREDIT_1_FONT, 0);
|
||||||
lv_obj_set_style_text_color(lbl_credit, COL_DIM, 0);
|
lv_obj_set_style_text_color(lbl_credit, COL_DIM, 0);
|
||||||
lv_obj_align(lbl_credit, LV_ALIGN_BOTTOM_MID, 0, -46);
|
lv_obj_align(lbl_credit, LV_ALIGN_BOTTOM_MID, 0, -46);
|
||||||
|
|
||||||
lv_obj_t* lbl_credit2 = lv_label_create(ble_container);
|
lv_obj_t* lbl_credit2 = lv_label_create(ble_container);
|
||||||
lv_label_set_text(lbl_credit2, "Clawd animation by @amaanbuilds");
|
lv_label_set_text(lbl_credit2, "Clawd animation by @amaanbuilds");
|
||||||
lv_obj_set_style_text_font(lbl_credit2, &font_styrene_20, 0);
|
lv_obj_set_style_text_font(lbl_credit2, &BT_CREDIT_2_FONT, 0);
|
||||||
lv_obj_set_style_text_color(lbl_credit2, COL_DIM, 0);
|
lv_obj_set_style_text_color(lbl_credit2, COL_DIM, 0);
|
||||||
lv_obj_align(lbl_credit2, LV_ALIGN_BOTTOM_MID, 0, -20);
|
lv_obj_align(lbl_credit2, LV_ALIGN_BOTTOM_MID, 0, -20);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user