Device-abstraction refactor: HAL + per-board folders + responsive UI
Replaces the build-flag-driven #ifdef sprawl (~30 blocks across 6 files)
with a small HAL in firmware/src/hal/ and per-board folders under
firmware/src/boards/. Shared code (main.cpp, ui.cpp, splash.cpp) no
longer contains a single `#ifdef BOARD_*` — optional features are
guarded by BoardCaps (runtime) and BOARD_HAS_* macros (compile-time,
inside the board's own files).
Why: lets community contributors port to new ESP32 + AMOLED + touch
combos by dropping in a boards/<name>/ folder + a PlatformIO env,
without touching shared files. See docs/porting/adding-a-board.md.
Highlights:
- New HAL: display_hal, touch_hal, input_hal, power_hal, imu_hal,
board_caps. Each board provides display.cpp, touch.cpp, input.cpp,
power.cpp, imu.cpp, caps.cpp, board_init.cpp + private hardware
drivers (e.g. io_expander.{h,cpp} on AMOLED-1.8).
- PlatformIO build_src_filter selects each board's folder per env.
- ui.cpp picks fonts and layout from board_caps() via compute_layout()
with screen-height breakpoints (>= 460 → large, else compact).
- splash.cpp computes CELL = min(W,H)/20 — responsive instead of two
hardcoded values.
- idle.cpp (from #24) rewired through display_hal + power_hal — no
longer depends on the deleted display_cfg.h / power.h.
- power_hal gains power_hal_is_vbus_in() for idle's
IDLE_SLEEP_WHEN_CHARGING gate.
- boards/template/ + docs/porting/{adding-a-board,hal-contract,
capability-flags}.md to bootstrap new ports.
- display_cfg.h, power.{h,cpp}, imu.{h,cpp}, io_expander.{h,cpp}
deleted from src/ root (moved into boards/<name>/ or hal/).
Verification: both `pio run -e waveshare_amoled_216` and
`pio run -e waveshare_amoled_18` succeed unchanged.
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
f3ed2425bf
commit
20351212b2
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
// Template board.h — copy this file (and the rest of boards/template/) to
|
||||
// boards/<your_board>/ and fill in each TODO. See docs/porting/adding-a-board.md
|
||||
// for a walk-through.
|
||||
|
||||
#define BOARD_NAME "TODO: human-readable board name"
|
||||
|
||||
// ---- Display geometry ----
|
||||
// Active panel pixel dimensions (post-orientation).
|
||||
#define LCD_WIDTH 240 // TODO
|
||||
#define LCD_HEIGHT 240 // TODO
|
||||
|
||||
// ---- QSPI display pins ----
|
||||
// Wire your panel datasheet's QSPI pins to MCU GPIOs and list them here.
|
||||
#define LCD_CS 12 // TODO
|
||||
#define LCD_SCLK 38 // TODO
|
||||
#define LCD_SDIO0 4 // TODO
|
||||
#define LCD_SDIO1 5 // TODO
|
||||
#define LCD_SDIO2 6 // TODO
|
||||
#define LCD_SDIO3 7 // TODO
|
||||
#define LCD_RESET 2 // TODO; use GFX_NOT_DEFINED if you reset via an expander
|
||||
|
||||
// ---- I2C bus (shared by touch, PMU, IMU, IO expander) ----
|
||||
#define IIC_SDA 15 // TODO
|
||||
#define IIC_SCL 14 // TODO
|
||||
|
||||
// ---- Touch ----
|
||||
// I2C address depends on the controller. Replace TODO_TP_ADDR below and pick
|
||||
// the matching driver in touch.cpp.
|
||||
#define TP_INT 11 // TODO
|
||||
#define TP_ADDR 0x00 // TODO
|
||||
|
||||
// ---- PMU ----
|
||||
// Drop or change if your board doesn't ship an AXP2101.
|
||||
#define AXP2101_ADDR 0x34
|
||||
|
||||
// ---- Buttons ----
|
||||
#define BTN_BACK_GPIO 0 // BOOT — primary, Space (PTT)
|
||||
// If your board has a second physical button, set its GPIO and bump
|
||||
// BOARD_HAS_SECONDARY_BUTTON to 1 below.
|
||||
|
||||
// ---- Capability flags ----
|
||||
// Compile-time switches the linker uses to dead-strip optional features.
|
||||
// Keep these in sync with the BoardCaps instance in caps.cpp.
|
||||
#define BOARD_HAS_SECONDARY_BUTTON 0 // TODO
|
||||
#define BOARD_HAS_ROTATION 0 // TODO: IMU-driven CPU rotation in display.cpp
|
||||
#define BOARD_HAS_IMU 0 // TODO
|
||||
#define BOARD_HAS_BATTERY 0 // TODO
|
||||
#define BOARD_HAS_IO_EXPANDER 0 // TODO
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
// Called once at the very start of setup(), before any HAL device init.
|
||||
// At minimum bring up the shared I2C bus. If your board has an IO expander
|
||||
// gating the LCD or touch reset lines, initialize and release it here too
|
||||
// (otherwise display_hal_init() will fail to probe the panel).
|
||||
extern "C" void board_init(void) {
|
||||
Wire.begin(IIC_SDA, IIC_SCL);
|
||||
// TODO: io_expander_init() if your board needs one
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "../../hal/board_caps.h"
|
||||
#include "board.h"
|
||||
|
||||
static const BoardCaps caps = {
|
||||
.name = BOARD_NAME,
|
||||
.width = LCD_WIDTH,
|
||||
.height = LCD_HEIGHT,
|
||||
.button_count = (uint8_t)(1 + BOARD_HAS_SECONDARY_BUTTON),
|
||||
.has_rotation = (bool)BOARD_HAS_ROTATION,
|
||||
.has_battery = (bool)BOARD_HAS_BATTERY,
|
||||
.has_imu = (bool)BOARD_HAS_IMU,
|
||||
};
|
||||
|
||||
const BoardCaps& board_caps(void) { return caps; }
|
||||
@@ -0,0 +1,59 @@
|
||||
#include "../../hal/display_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Arduino_GFX_Library.h>
|
||||
|
||||
// TODO: pick the right driver class from Arduino_GFX_Library (e.g.
|
||||
// Arduino_CO5300, Arduino_SH8601, Arduino_NV3041A). Most QSPI AMOLED
|
||||
// panels are supported in the upstream library — check the panel's
|
||||
// chip and grep the library include path.
|
||||
|
||||
static Arduino_DataBus* bus = nullptr;
|
||||
// static Arduino_<YOUR_PANEL>* gfx = nullptr;
|
||||
|
||||
void display_hal_init(void) {
|
||||
bus = new Arduino_ESP32QSPI(
|
||||
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
|
||||
// gfx = new Arduino_<YOUR_PANEL>(bus, LCD_RESET, 0, LCD_WIDTH, LCD_HEIGHT, ...);
|
||||
}
|
||||
|
||||
void display_hal_begin(void) {
|
||||
// gfx->begin();
|
||||
// gfx->fillScreen(0x0000);
|
||||
// gfx->setBrightness(200);
|
||||
}
|
||||
|
||||
void display_hal_set_brightness(uint8_t level) {
|
||||
(void)level;
|
||||
// gfx->setBrightness(level);
|
||||
}
|
||||
|
||||
void display_hal_fill_screen(uint16_t color) {
|
||||
(void)color;
|
||||
// gfx->fillScreen(color);
|
||||
}
|
||||
|
||||
void display_hal_draw_bitmap(int32_t x, int32_t y, int32_t w, int32_t h,
|
||||
const uint16_t* pixels) {
|
||||
(void)x; (void)y; (void)w; (void)h; (void)pixels;
|
||||
// gfx->draw16bitRGBBitmap(x, y, (uint16_t*)pixels, w, h);
|
||||
//
|
||||
// If your panel needs CPU rotation (no native MADCTL rotate), set
|
||||
// BOARD_HAS_ROTATION=1 in board.h, allocate a rotation strip buffer
|
||||
// in display_hal_begin(), and transform (x, y, w, h) + pixels here.
|
||||
// See boards/waveshare_amoled_216/display.cpp for a worked example.
|
||||
}
|
||||
|
||||
void display_hal_tick(void) {
|
||||
// Only needed for boards that animate the brightness ramp during a
|
||||
// CPU-rotation transition (see the 2.16 reference port).
|
||||
}
|
||||
|
||||
void display_hal_round_area(int32_t* x1, int32_t* y1, int32_t* x2, int32_t* y2) {
|
||||
// Most QSPI AMOLED drivers expect even-aligned flush regions. Harmless
|
||||
// to apply on panels that don't strictly require it.
|
||||
*x1 = *x1 & ~1;
|
||||
*y1 = *y1 & ~1;
|
||||
*x2 = *x2 | 1;
|
||||
*y2 = *y2 | 1;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#include "../../hal/imu_hal.h"
|
||||
|
||||
// No IMU on this template. If your board ships an accelerometer (e.g.
|
||||
// QMI8658 + want auto-rotation), copy boards/waveshare_amoled_216/imu.cpp
|
||||
// here and set BOARD_HAS_ROTATION=1 in board.h.
|
||||
|
||||
void imu_hal_init(void) {}
|
||||
void imu_hal_tick(void) {}
|
||||
uint8_t imu_hal_rotation_quadrant(void) { return 0; }
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "../../hal/input_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
void input_hal_init(void) {
|
||||
pinMode(BTN_BACK_GPIO, INPUT_PULLUP);
|
||||
#if BOARD_HAS_SECONDARY_BUTTON
|
||||
// pinMode(BTN_FWD_GPIO, INPUT_PULLUP); // TODO
|
||||
#endif
|
||||
}
|
||||
|
||||
bool input_hal_is_held(InputButton btn) {
|
||||
switch (btn) {
|
||||
case INPUT_BTN_PRIMARY:
|
||||
return digitalRead(BTN_BACK_GPIO) == LOW;
|
||||
case INPUT_BTN_SECONDARY:
|
||||
#if BOARD_HAS_SECONDARY_BUTTON
|
||||
// return digitalRead(BTN_FWD_GPIO) == LOW; // TODO
|
||||
return false;
|
||||
#else
|
||||
return false; // not present on this board
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "../../hal/power_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
// Minimal stub — replace with real power management for your board.
|
||||
//
|
||||
// If your board has an AXP2101 or similar PMU, mirror
|
||||
// boards/waveshare_amoled_216/power.cpp. If the PWR button is wired
|
||||
// somewhere other than the PMU's PKEY pin (e.g. through an IO expander
|
||||
// like the AMOLED-1.8 board), look at that port instead.
|
||||
//
|
||||
// If your board has no PMU and no PWR button, leave the stubs as below
|
||||
// and set BOARD_HAS_BATTERY=0 in board.h — the UI honors caps.has_battery
|
||||
// and hides the battery indicator.
|
||||
|
||||
void power_hal_init(void) {}
|
||||
void power_hal_tick(void) {}
|
||||
|
||||
int power_hal_battery_pct(void) { return -1; }
|
||||
bool power_hal_is_charging(void) { return false; }
|
||||
bool power_hal_is_vbus_in(void) { return false; }
|
||||
bool power_hal_pwr_pressed(void) { return false; }
|
||||
@@ -0,0 +1,39 @@
|
||||
#include "../../hal/touch_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
// TODO: replace the body with a driver for your controller. Two patterns:
|
||||
// 1. A library (SensorLib's CSTxxx, TAMC_GT911, etc.) — add to lib_deps
|
||||
// in platformio.ini, mirror the AMOLED-2.16 port's touch.cpp.
|
||||
// 2. A minimal vendored reader — preferred when the only available
|
||||
// library is GPL-licensed (see boards/waveshare_amoled_18/touch.cpp).
|
||||
//
|
||||
// Whichever you pick, touch_hal_read() must complete in well under 5 ms
|
||||
// (a single I2C burst is fine) so it doesn't drop frames.
|
||||
|
||||
static volatile bool touch_data_ready = false;
|
||||
static volatile bool touch_pressed = false;
|
||||
static volatile uint16_t touch_x = 0;
|
||||
static volatile uint16_t touch_y = 0;
|
||||
|
||||
static void IRAM_ATTR touch_isr(void) {
|
||||
touch_data_ready = true;
|
||||
}
|
||||
|
||||
void touch_hal_init(void) {
|
||||
// TODO: initialize your controller over I2C; configure to active scanning.
|
||||
pinMode(TP_INT, INPUT_PULLUP);
|
||||
attachInterrupt(TP_INT, touch_isr, FALLING);
|
||||
}
|
||||
|
||||
void touch_hal_read(uint16_t* x, uint16_t* y, bool* pressed) {
|
||||
if (touch_data_ready) {
|
||||
touch_data_ready = false;
|
||||
// TODO: read coords from your controller into touch_x, touch_y,
|
||||
// touch_pressed.
|
||||
}
|
||||
*x = touch_x;
|
||||
*y = touch_y;
|
||||
*pressed = touch_pressed;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
// Waveshare ESP32-S3-Touch-AMOLED-1.8 — portrait AMOLED kit.
|
||||
// 368x448 SH8601 + FT3168 touch + AXP2101 PMU + QMI8658 IMU + XCA9554 expander.
|
||||
// IMU is present (initialized for I2C bus health) but rotation is disabled
|
||||
// because the panel mounts in a fixed orientation in the kit's enclosure.
|
||||
|
||||
#define BOARD_NAME "Waveshare AMOLED 1.8"
|
||||
|
||||
// ---- Display geometry (portrait) ----
|
||||
#define LCD_WIDTH 368
|
||||
#define LCD_HEIGHT 448
|
||||
|
||||
// ---- QSPI display pins (SH8601) ----
|
||||
#define LCD_CS 12
|
||||
#define LCD_SCLK 11 // different from 2.16 board (was GPIO 38)
|
||||
#define LCD_SDIO0 4
|
||||
#define LCD_SDIO1 5
|
||||
#define LCD_SDIO2 6
|
||||
#define LCD_SDIO3 7
|
||||
// LCD reset is routed through the XCA9554 IO expander (EXIO1). The Arduino
|
||||
// GFX driver gets GFX_NOT_DEFINED; the expander releases reset before
|
||||
// gfx->begin() runs.
|
||||
|
||||
// ---- I2C bus (touch + PMU + IMU + IO expander all share one bus) ----
|
||||
#define IIC_SDA 15
|
||||
#define IIC_SCL 14
|
||||
|
||||
// ---- Touch (FT3168 via vendored minimal I2C reader) ----
|
||||
#define TP_INT 21
|
||||
#define FT3168_ADDR 0x38
|
||||
|
||||
// ---- PMU ----
|
||||
#define AXP2101_ADDR 0x34
|
||||
|
||||
// ---- IO expander (XCA9554/PCA9554 compatible) ----
|
||||
// Gates LCD_RST, TP_RST, audio amp enable, and reads the PWR button.
|
||||
#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
|
||||
#define IOX_PIN_PWR_BTN 4 // EXIO4 → PWR button input, active HIGH
|
||||
|
||||
// ---- Buttons ----
|
||||
#define BTN_BACK_GPIO 0 // BOOT — primary, Space (PTT)
|
||||
// PWR comes via XCA9554 EXIO4 (see power.cpp); there is no secondary button.
|
||||
|
||||
// ---- Capability flags ----
|
||||
#define BOARD_HAS_SECONDARY_BUTTON 0
|
||||
#define BOARD_HAS_ROTATION 0
|
||||
#define BOARD_HAS_IMU 1 // present + initialized, but rotation off
|
||||
#define BOARD_HAS_BATTERY 1
|
||||
#define BOARD_HAS_IO_EXPANDER 1
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "board.h"
|
||||
#include "io_expander.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
// AMOLED-1.8 also needs the XCA9554 IO expander up first — the display
|
||||
// and touch controllers stay in reset until EXIO0..1 go HIGH.
|
||||
extern "C" void board_init(void) {
|
||||
Wire.begin(IIC_SDA, IIC_SCL);
|
||||
io_expander_init();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "../../hal/board_caps.h"
|
||||
#include "board.h"
|
||||
|
||||
static const BoardCaps caps = {
|
||||
.name = BOARD_NAME,
|
||||
.width = LCD_WIDTH,
|
||||
.height = LCD_HEIGHT,
|
||||
.button_count = 1,
|
||||
.has_rotation = false,
|
||||
.has_battery = true,
|
||||
.has_imu = true,
|
||||
};
|
||||
|
||||
const BoardCaps& board_caps(void) { return caps; }
|
||||
@@ -0,0 +1,54 @@
|
||||
#include "../../hal/display_hal.h"
|
||||
#include "board.h"
|
||||
#include "io_expander.h"
|
||||
#include <Arduino.h>
|
||||
#include <Arduino_GFX_Library.h>
|
||||
|
||||
// AMOLED-1.8 is fixed at 0°. No CPU rotation, no rot_buf.
|
||||
// Display reset is routed through the XCA9554 IO expander (EXIO1) which
|
||||
// must be initialized + released before gfx->begin() runs — main.cpp
|
||||
// arranges this by calling display_hal_init() after io_expander_init().
|
||||
|
||||
static Arduino_DataBus* bus = nullptr;
|
||||
static Arduino_SH8601* gfx = nullptr;
|
||||
|
||||
void display_hal_init(void) {
|
||||
bus = new Arduino_ESP32QSPI(
|
||||
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
|
||||
// SH8601 constructor: (bus, rst, rotation, w, h)
|
||||
gfx = new Arduino_SH8601(
|
||||
bus, GFX_NOT_DEFINED /* reset via XCA9554 */, 0,
|
||||
LCD_WIDTH, LCD_HEIGHT);
|
||||
}
|
||||
|
||||
void display_hal_begin(void) {
|
||||
gfx->begin();
|
||||
gfx->fillScreen(0x0000);
|
||||
gfx->setBrightness(200);
|
||||
}
|
||||
|
||||
void display_hal_set_brightness(uint8_t level) {
|
||||
if (gfx) gfx->setBrightness(level);
|
||||
}
|
||||
|
||||
void display_hal_fill_screen(uint16_t color) {
|
||||
if (gfx) gfx->fillScreen(color);
|
||||
}
|
||||
|
||||
void display_hal_draw_bitmap(int32_t x, int32_t y, int32_t w, int32_t h,
|
||||
const uint16_t* pixels) {
|
||||
if (gfx) gfx->draw16bitRGBBitmap(x, y, (uint16_t*)pixels, w, h);
|
||||
}
|
||||
|
||||
void display_hal_tick(void) {
|
||||
// No rotation handling needed on this board.
|
||||
}
|
||||
|
||||
// SH8601 driver doesn't strictly require even alignment in source, but the
|
||||
// rounder is harmless and keeps behavior consistent with the CO5300 port.
|
||||
void display_hal_round_area(int32_t* x1, int32_t* y1, int32_t* x2, int32_t* y2) {
|
||||
*x1 = *x1 & ~1;
|
||||
*y1 = *y1 & ~1;
|
||||
*x2 = *x2 | 1;
|
||||
*y2 = *y2 | 1;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "../../hal/imu_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <SensorQMI8658.hpp>
|
||||
|
||||
// AMOLED-1.8 ships with QMI8658 populated, but the kit's enclosure mounts
|
||||
// the panel in a fixed orientation. We initialize the device anyway so the
|
||||
// shared I2C bus stays healthy, but always report rotation 0.
|
||||
|
||||
static SensorQMI8658 imu;
|
||||
|
||||
void imu_hal_init(void) {
|
||||
if (!imu.begin(Wire, QMI8658_L_SLAVE_ADDRESS, IIC_SDA, IIC_SCL)) {
|
||||
Serial.println("QMI8658 init failed");
|
||||
return;
|
||||
}
|
||||
Serial.println("QMI8658 init OK (rotation disabled on this board)");
|
||||
}
|
||||
|
||||
void imu_hal_tick(void) {
|
||||
// No-op — rotation is disabled.
|
||||
}
|
||||
|
||||
uint8_t imu_hal_rotation_quadrant(void) { return 0; }
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "../../hal/input_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
// AMOLED-1.8 has only the BOOT button as a secondary input — the PWR
|
||||
// button comes through power_hal (XCA9554 EXIO4). No secondary button.
|
||||
|
||||
void input_hal_init(void) {
|
||||
pinMode(BTN_BACK_GPIO, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
bool input_hal_is_held(InputButton btn) {
|
||||
switch (btn) {
|
||||
case INPUT_BTN_PRIMARY:
|
||||
return digitalRead(BTN_BACK_GPIO) == LOW;
|
||||
case INPUT_BTN_SECONDARY:
|
||||
return false; // not present on this board
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "io_expander.h"
|
||||
#include "board.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) {
|
||||
if (!write_reg(IOX_REG_CONFIG, IOX_CONFIG_MASK)) {
|
||||
Serial.println("XCA9554 init failed (config)");
|
||||
return false;
|
||||
}
|
||||
// Hold display + touch in reset.
|
||||
output_state = 0x00;
|
||||
write_reg(IOX_REG_OUTPUT, output_state);
|
||||
delay(20);
|
||||
// Release resets and enable audio amp output line.
|
||||
output_state = IOX_OUTPUT_DEFAULT;
|
||||
write_reg(IOX_REG_OUTPUT, output_state);
|
||||
delay(20);
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// XCA9554 / PCA9554-compatible 8-bit I2C IO expander.
|
||||
// Board-private to the AMOLED-1.8 port; not exposed in hal/.
|
||||
//
|
||||
// Must be initialized BEFORE the display or touch — skipping the reset
|
||||
// release leaves the SH8601 and FT3168 in reset and they fail to probe.
|
||||
|
||||
bool io_expander_init(void);
|
||||
void io_expander_set(uint8_t pin, bool high);
|
||||
bool io_expander_get(uint8_t pin);
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "../../hal/power_hal.h"
|
||||
#include "board.h"
|
||||
#include "io_expander.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <XPowersLib.h>
|
||||
|
||||
// PWR button comes from XCA9554 EXIO4 (active HIGH). The PMU still
|
||||
// provides battery monitoring; we just don't subscribe to its PKEY IRQ.
|
||||
|
||||
#define BATTERY_POLL_MS 2000
|
||||
#define CHARGING_POLL_MS 500
|
||||
#define PWR_POLL_MS 50
|
||||
|
||||
static XPowersPMU pmu;
|
||||
|
||||
static int cached_pct = -1;
|
||||
static bool cached_charging = false;
|
||||
static bool cached_vbus = false;
|
||||
static bool pwr_pressed_flag = false;
|
||||
static bool last_pwr_state = false; // edge detector for EXIO4
|
||||
static uint32_t last_battery_ms = 0;
|
||||
static uint32_t last_charging_ms = 0;
|
||||
static uint32_t last_pwr_ms = 0;
|
||||
|
||||
void power_hal_init(void) {
|
||||
if (!pmu.begin(Wire, AXP2101_ADDR, IIC_SDA, IIC_SCL)) {
|
||||
Serial.println("AXP2101 init failed");
|
||||
return;
|
||||
}
|
||||
Serial.println("AXP2101 init OK");
|
||||
|
||||
pmu.enableBattDetection();
|
||||
pmu.enableBattVoltageMeasure();
|
||||
// No PMU IRQ wiring — PWR comes via io_expander_get() below.
|
||||
|
||||
cached_charging = pmu.isCharging();
|
||||
cached_vbus = pmu.isVbusIn();
|
||||
cached_pct = pmu.getBatteryPercent();
|
||||
}
|
||||
|
||||
void power_hal_tick(void) {
|
||||
uint32_t now = millis();
|
||||
|
||||
if (now - last_charging_ms >= CHARGING_POLL_MS) {
|
||||
last_charging_ms = now;
|
||||
cached_charging = pmu.isCharging();
|
||||
cached_vbus = pmu.isVbusIn();
|
||||
}
|
||||
if (now - last_battery_ms >= BATTERY_POLL_MS) {
|
||||
last_battery_ms = now;
|
||||
cached_pct = pmu.getBatteryPercent();
|
||||
}
|
||||
if (now - last_pwr_ms >= PWR_POLL_MS) {
|
||||
last_pwr_ms = now;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
int power_hal_battery_pct(void) { return cached_pct; }
|
||||
bool power_hal_is_charging(void) { return cached_charging; }
|
||||
bool power_hal_is_vbus_in(void) { return cached_vbus; }
|
||||
|
||||
bool power_hal_pwr_pressed(void) {
|
||||
if (pwr_pressed_flag) {
|
||||
pwr_pressed_flag = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#include "../../hal/touch_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
// 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 volatile bool touch_data_ready = false;
|
||||
static volatile bool touch_pressed = false;
|
||||
static volatile uint16_t touch_x = 0;
|
||||
static volatile uint16_t touch_y = 0;
|
||||
|
||||
static void IRAM_ATTR touch_isr(void) {
|
||||
touch_data_ready = true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void touch_hal_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");
|
||||
}
|
||||
|
||||
pinMode(TP_INT, INPUT_PULLUP);
|
||||
attachInterrupt(TP_INT, touch_isr, FALLING);
|
||||
Serial.println("FT3168 attached on INT pin");
|
||||
}
|
||||
|
||||
void touch_hal_read(uint16_t* x, uint16_t* y, bool* pressed) {
|
||||
if (touch_data_ready) {
|
||||
touch_data_ready = false;
|
||||
ft3168_read_into_shared_state();
|
||||
}
|
||||
*x = touch_x;
|
||||
*y = touch_y;
|
||||
*pressed = touch_pressed;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
// Waveshare ESP32-S3-Touch-AMOLED-2.16 — original square AMOLED kit.
|
||||
// 480x480 CO5300 + CST9220 touch + AXP2101 PMU + QMI8658 IMU.
|
||||
// IMU-driven CPU rotation is enabled.
|
||||
|
||||
#define BOARD_NAME "Waveshare AMOLED 2.16"
|
||||
|
||||
// ---- Display geometry (matches BoardCaps; duplicated here as compile-time
|
||||
// constants because the buffer-size math runs at file scope) ----
|
||||
#define LCD_WIDTH 480
|
||||
#define LCD_HEIGHT 480
|
||||
|
||||
// ---- QSPI display pins (CO5300) ----
|
||||
#define LCD_CS 12
|
||||
#define LCD_SCLK 38
|
||||
#define LCD_SDIO0 4
|
||||
#define LCD_SDIO1 5
|
||||
#define LCD_SDIO2 6
|
||||
#define LCD_SDIO3 7
|
||||
#define LCD_RESET 2
|
||||
|
||||
// ---- I2C bus (touch + PMU + IMU) ----
|
||||
#define IIC_SDA 15
|
||||
#define IIC_SCL 14
|
||||
|
||||
// ---- Touch (CST9220 via TouchDrvCST92xx library) ----
|
||||
#define TP_INT 11
|
||||
#define TP_RST 2 // shared with LCD_RESET
|
||||
#define CST9220_ADDR 0x5A
|
||||
|
||||
// ---- PMU ----
|
||||
#define AXP2101_ADDR 0x34
|
||||
|
||||
// ---- Buttons ----
|
||||
#define BTN_BACK_GPIO 0 // BOOT — primary, Space (PTT)
|
||||
#define BTN_FWD_GPIO 18 // secondary, Shift+Tab (mode toggle)
|
||||
|
||||
// ---- Capability flags (compile-time; redundant with BoardCaps but lets
|
||||
// the linker dead-strip whole functions on boards that don't need them) ----
|
||||
#define BOARD_HAS_SECONDARY_BUTTON 1
|
||||
#define BOARD_HAS_ROTATION 1
|
||||
#define BOARD_HAS_IMU 1
|
||||
#define BOARD_HAS_BATTERY 1
|
||||
#define BOARD_HAS_IO_EXPANDER 0
|
||||
@@ -0,0 +1,9 @@
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
// Bring up the shared I2C bus. AMOLED-2.16 has no IO expander, so this is
|
||||
// all the early init needed before display/touch/power/imu HAL calls.
|
||||
extern "C" void board_init(void) {
|
||||
Wire.begin(IIC_SDA, IIC_SCL);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "../../hal/board_caps.h"
|
||||
#include "board.h"
|
||||
|
||||
static const BoardCaps caps = {
|
||||
.name = BOARD_NAME,
|
||||
.width = LCD_WIDTH,
|
||||
.height = LCD_HEIGHT,
|
||||
.button_count = 2,
|
||||
.has_rotation = true,
|
||||
.has_battery = true,
|
||||
.has_imu = true,
|
||||
};
|
||||
|
||||
const BoardCaps& board_caps(void) { return caps; }
|
||||
@@ -0,0 +1,134 @@
|
||||
#include "../../hal/display_hal.h"
|
||||
#include "../../hal/imu_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Arduino_GFX_Library.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <lvgl.h>
|
||||
|
||||
// Render strip used when rotating in software. Sized to the largest LVGL
|
||||
// partial flush we ever do (LCD_WIDTH × BUF_LINES, set in main.cpp).
|
||||
#define ROT_BUF_LINES 40
|
||||
static uint16_t* rot_buf = nullptr;
|
||||
|
||||
static Arduino_DataBus* bus = nullptr;
|
||||
static Arduino_CO5300* gfx = nullptr;
|
||||
|
||||
void display_hal_init(void) {
|
||||
bus = new Arduino_ESP32QSPI(
|
||||
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
|
||||
// CO5300 constructor: (bus, rst, rotation, w, h, col_offset1..2, row_offset1..2)
|
||||
gfx = new Arduino_CO5300(
|
||||
bus, LCD_RESET, 0 /* rotation handled in software */,
|
||||
LCD_WIDTH, LCD_HEIGHT, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
void display_hal_begin(void) {
|
||||
gfx->begin();
|
||||
gfx->fillScreen(0x0000);
|
||||
gfx->setBrightness(200);
|
||||
|
||||
// Allocate rotation strip (PSRAM). Sized to match main.cpp's BUF_LINES.
|
||||
rot_buf = (uint16_t*)heap_caps_malloc(LCD_WIDTH * ROT_BUF_LINES * 2, MALLOC_CAP_SPIRAM);
|
||||
}
|
||||
|
||||
void display_hal_set_brightness(uint8_t level) {
|
||||
if (gfx) gfx->setBrightness(level);
|
||||
}
|
||||
|
||||
void display_hal_fill_screen(uint16_t color) {
|
||||
if (gfx) gfx->fillScreen(color);
|
||||
}
|
||||
|
||||
// Rotate a w×h strip into rot_buf and compute destination coordinates on the
|
||||
// 480×480 panel. Src is row-major over the rectangle (sx, sy, w, 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* dx, int32_t* dy, int32_t* dw, int32_t* dh) {
|
||||
const int S = LCD_WIDTH;
|
||||
|
||||
switch (r) {
|
||||
case 1: // 90° CW: (x,y) -> (S-1-y, x)
|
||||
*dw = h; *dh = w;
|
||||
*dx = S - sy - h;
|
||||
*dy = sx;
|
||||
for (int32_t y = 0; y < h; y++) {
|
||||
for (int32_t x = 0; x < w; x++) {
|
||||
rot_buf[x * h + (h - 1 - y)] = src[y * w + x];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2: // 180°: (x,y) -> (S-1-x, S-1-y)
|
||||
*dw = w; *dh = h;
|
||||
*dx = S - sx - w;
|
||||
*dy = S - sy - h;
|
||||
for (int32_t y = 0; y < h; y++) {
|
||||
for (int32_t x = 0; x < w; x++) {
|
||||
rot_buf[(h - 1 - y) * w + (w - 1 - x)] = src[y * w + x];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3: // 270° CW: (x,y) -> (y, S-1-x)
|
||||
*dw = h; *dh = w;
|
||||
*dx = sy;
|
||||
*dy = S - sx - w;
|
||||
for (int32_t y = 0; y < h; y++) {
|
||||
for (int32_t x = 0; x < w; x++) {
|
||||
rot_buf[(w - 1 - x) * h + y] = src[y * w + x];
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
*dx = sx; *dy = sy; *dw = w; *dh = h;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void display_hal_draw_bitmap(int32_t x, int32_t y, int32_t w, int32_t h,
|
||||
const uint16_t* pixels) {
|
||||
if (!gfx) return;
|
||||
uint8_t r = imu_hal_rotation_quadrant();
|
||||
if (r == 0 || !rot_buf) {
|
||||
gfx->draw16bitRGBBitmap(x, y, (uint16_t*)pixels, w, h);
|
||||
return;
|
||||
}
|
||||
int32_t dx, dy, dw, dh;
|
||||
rotate_strip(pixels, w, h, x, y, r, &dx, &dy, &dw, &dh);
|
||||
gfx->draw16bitRGBBitmap(dx, dy, rot_buf, dw, dh);
|
||||
}
|
||||
|
||||
// On rotation change, blank the panel, force a full LVGL redraw at the new
|
||||
// orientation, then ramp brightness back up over ~125ms so the transition
|
||||
// reads as deliberate.
|
||||
void display_hal_tick(void) {
|
||||
static uint8_t last_rotation = 0;
|
||||
static uint8_t ramp_step = 0; // 0=idle, 1..4=ramping
|
||||
static uint32_t ramp_last = 0;
|
||||
|
||||
uint8_t rot = imu_hal_rotation_quadrant();
|
||||
if (rot != last_rotation) {
|
||||
display_hal_set_brightness(0);
|
||||
last_rotation = rot;
|
||||
lv_obj_invalidate(lv_screen_active());
|
||||
ramp_step = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ramp_step == 0) return;
|
||||
uint32_t now = millis();
|
||||
if (now - ramp_last < 25) return;
|
||||
ramp_last = now;
|
||||
|
||||
static const uint8_t levels[] = {60, 120, 170, 200};
|
||||
display_hal_set_brightness(levels[ramp_step - 1]);
|
||||
if (ramp_step >= 4) ramp_step = 0;
|
||||
else ramp_step++;
|
||||
}
|
||||
|
||||
// CO5300 requires even-aligned flush regions.
|
||||
void display_hal_round_area(int32_t* x1, int32_t* y1, int32_t* x2, int32_t* y2) {
|
||||
*x1 = *x1 & ~1;
|
||||
*y1 = *y1 & ~1;
|
||||
*x2 = *x2 | 1;
|
||||
*y2 = *y2 | 1;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "../../hal/imu_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <SensorQMI8658.hpp>
|
||||
|
||||
// Poll and hysteresis timing
|
||||
#define IMU_POLL_MS 100 // ~10 Hz
|
||||
#define STABLE_TIME_MS 300 // orientation must hold this long before rotating
|
||||
#define TILT_THRESHOLD 0.5f // ~30° from axis (sin 30° ≈ 0.5)
|
||||
|
||||
static SensorQMI8658 imu;
|
||||
static uint8_t current_rotation = 0;
|
||||
static uint8_t candidate_rotation = 0;
|
||||
static uint32_t candidate_since = 0;
|
||||
static uint32_t last_poll_ms = 0;
|
||||
static bool imu_ok = false;
|
||||
|
||||
static uint8_t accel_to_rotation(float ax, float ay) {
|
||||
float abs_ax = fabsf(ax);
|
||||
float abs_ay = fabsf(ay);
|
||||
if (abs_ax < TILT_THRESHOLD && abs_ay < TILT_THRESHOLD) {
|
||||
return 255; // ambiguous (face-up/down)
|
||||
}
|
||||
if (abs_ay > abs_ax) return (ay > 0) ? 3 : 1;
|
||||
return (ax > 0) ? 0 : 2;
|
||||
}
|
||||
|
||||
void imu_hal_init(void) {
|
||||
if (!imu.begin(Wire, QMI8658_L_SLAVE_ADDRESS, IIC_SDA, IIC_SCL)) {
|
||||
Serial.println("QMI8658 init failed");
|
||||
return;
|
||||
}
|
||||
Serial.println("QMI8658 init OK");
|
||||
imu.configAccelerometer(
|
||||
SensorQMI8658::ACC_RANGE_4G,
|
||||
SensorQMI8658::ACC_ODR_LOWPOWER_21Hz,
|
||||
SensorQMI8658::LPF_MODE_3);
|
||||
imu.enableAccelerometer();
|
||||
imu_ok = true;
|
||||
}
|
||||
|
||||
void imu_hal_tick(void) {
|
||||
if (!imu_ok) return;
|
||||
uint32_t now = millis();
|
||||
if (now - last_poll_ms < IMU_POLL_MS) return;
|
||||
last_poll_ms = now;
|
||||
|
||||
float ax, ay, az;
|
||||
if (!imu.getAccelerometer(ax, ay, az)) return;
|
||||
|
||||
uint8_t target = accel_to_rotation(ax, ay);
|
||||
if (target == 255 || target == current_rotation) {
|
||||
candidate_rotation = current_rotation;
|
||||
return;
|
||||
}
|
||||
if (target != candidate_rotation) {
|
||||
candidate_rotation = target;
|
||||
candidate_since = now;
|
||||
} else if (now - candidate_since >= STABLE_TIME_MS) {
|
||||
current_rotation = target;
|
||||
Serial.printf("Rotation: %d\n", current_rotation);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t imu_hal_rotation_quadrant(void) { return current_rotation; }
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "../../hal/input_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
void input_hal_init(void) {
|
||||
pinMode(BTN_BACK_GPIO, INPUT_PULLUP);
|
||||
pinMode(BTN_FWD_GPIO, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
bool input_hal_is_held(InputButton btn) {
|
||||
switch (btn) {
|
||||
case INPUT_BTN_PRIMARY:
|
||||
return digitalRead(BTN_BACK_GPIO) == LOW;
|
||||
case INPUT_BTN_SECONDARY:
|
||||
return digitalRead(BTN_FWD_GPIO) == LOW;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "../../hal/power_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <XPowersLib.h>
|
||||
|
||||
// PWR button comes from AXP2101 PKEY short-press IRQ.
|
||||
|
||||
#define BATTERY_POLL_MS 2000
|
||||
#define CHARGING_POLL_MS 500
|
||||
#define PWR_POLL_MS 50
|
||||
|
||||
static XPowersPMU pmu;
|
||||
|
||||
static int cached_pct = -1;
|
||||
static bool cached_charging = false;
|
||||
static bool cached_vbus = false;
|
||||
static bool pwr_pressed_flag = false;
|
||||
static uint32_t last_battery_ms = 0;
|
||||
static uint32_t last_charging_ms = 0;
|
||||
static uint32_t last_pwr_ms = 0;
|
||||
|
||||
void power_hal_init(void) {
|
||||
if (!pmu.begin(Wire, AXP2101_ADDR, IIC_SDA, IIC_SCL)) {
|
||||
Serial.println("AXP2101 init failed");
|
||||
return;
|
||||
}
|
||||
Serial.println("AXP2101 init OK");
|
||||
|
||||
pmu.enableBattDetection();
|
||||
pmu.enableBattVoltageMeasure();
|
||||
|
||||
pmu.disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||||
pmu.clearIrqStatus();
|
||||
pmu.enableIRQ(XPOWERS_AXP2101_PKEY_SHORT_IRQ);
|
||||
|
||||
cached_charging = pmu.isCharging();
|
||||
cached_vbus = pmu.isVbusIn();
|
||||
cached_pct = pmu.getBatteryPercent();
|
||||
}
|
||||
|
||||
void power_hal_tick(void) {
|
||||
uint32_t now = millis();
|
||||
|
||||
if (now - last_charging_ms >= CHARGING_POLL_MS) {
|
||||
last_charging_ms = now;
|
||||
cached_charging = pmu.isCharging();
|
||||
cached_vbus = pmu.isVbusIn();
|
||||
}
|
||||
if (now - last_battery_ms >= BATTERY_POLL_MS) {
|
||||
last_battery_ms = now;
|
||||
cached_pct = pmu.getBatteryPercent();
|
||||
}
|
||||
if (now - last_pwr_ms >= PWR_POLL_MS) {
|
||||
last_pwr_ms = now;
|
||||
pmu.getIrqStatus();
|
||||
if (pmu.isPekeyShortPressIrq()) {
|
||||
pwr_pressed_flag = true;
|
||||
}
|
||||
pmu.clearIrqStatus();
|
||||
}
|
||||
}
|
||||
|
||||
int power_hal_battery_pct(void) { return cached_pct; }
|
||||
bool power_hal_is_charging(void) { return cached_charging; }
|
||||
bool power_hal_is_vbus_in(void) { return cached_vbus; }
|
||||
|
||||
bool power_hal_pwr_pressed(void) {
|
||||
if (pwr_pressed_flag) {
|
||||
pwr_pressed_flag = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "../../hal/touch_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <TouchDrvCSTXXX.hpp>
|
||||
|
||||
static TouchDrvCST92xx touch;
|
||||
|
||||
static volatile bool touch_data_ready = false;
|
||||
static volatile bool touch_pressed = false;
|
||||
static volatile uint16_t touch_x = 0;
|
||||
static volatile uint16_t touch_y = 0;
|
||||
|
||||
static void IRAM_ATTR touch_isr(void) {
|
||||
touch_data_ready = true;
|
||||
}
|
||||
|
||||
void touch_hal_init(void) {
|
||||
touch.setPins(TP_RST, TP_INT);
|
||||
if (!touch.begin(Wire, CST9220_ADDR, IIC_SDA, IIC_SCL)) {
|
||||
Serial.println("Touch init failed");
|
||||
return;
|
||||
}
|
||||
touch.setMaxCoordinates(LCD_WIDTH, LCD_HEIGHT);
|
||||
touch.setSwapXY(true);
|
||||
touch.setMirrorXY(true, false);
|
||||
pinMode(TP_INT, INPUT_PULLUP);
|
||||
attachInterrupt(TP_INT, touch_isr, FALLING);
|
||||
Serial.println("Touch init OK");
|
||||
}
|
||||
|
||||
void touch_hal_read(uint16_t* x, uint16_t* y, bool* pressed) {
|
||||
if (touch_data_ready) {
|
||||
touch_data_ready = false;
|
||||
int16_t tx[5], ty[5];
|
||||
uint8_t n = touch.getPoint(tx, ty, touch.getSupportTouchPoint());
|
||||
if (n > 0) {
|
||||
touch_pressed = true;
|
||||
touch_x = (uint16_t)tx[0];
|
||||
touch_y = (uint16_t)ty[0];
|
||||
} else {
|
||||
touch_pressed = false;
|
||||
}
|
||||
}
|
||||
*x = touch_x;
|
||||
*y = touch_y;
|
||||
*pressed = touch_pressed;
|
||||
}
|
||||
Reference in New Issue
Block a user