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:
tobby168
2026-05-20 18:27:24 -07:00
co-authored by Claude Opus 4.7
parent f3ed2425bf
commit 20351212b2
49 changed files with 1604 additions and 775 deletions
@@ -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; }