Add Waveshare ESP32-S3-Touch-AMOLED-2.06 board port + Windows daemon fixes
- New board firmware/src/boards/waveshare_amoled_206/ (CO5300 410x502, FT3168 touch, AXP2101 PMU, QMI8658 IMU). PlatformIO env waveshare_amoled_206. CO5300_COL_OFFSET=22, fixed orientation, PWR button on GPIO10, FocalTech inline touch reader. - Windows daemon: fix advertised device name (Claude Controller -> Clawdmeter) to match firmware ble.cpp, and add CLAWDMETER_ADDRESS env fallback so it connects by address when the bonded HID device is not advertising (Windows keeps it HID-connected). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
// Waveshare ESP32-S3-Touch-AMOLED-2.06 — rectangular AMOLED smartwatch kit.
|
||||
// 410x502 CO5300 + FT3168 touch + AXP2101 PMU + QMI8658 IMU + PCF85063 RTC.
|
||||
// No IO expander: display/touch reset lines are direct ESP32 GPIOs.
|
||||
// IMU is present (initialized for I2C bus health) but rotation is disabled —
|
||||
// the panel is non-square and the kit mounts in a fixed orientation.
|
||||
|
||||
#define BOARD_NAME "Waveshare AMOLED 2.06"
|
||||
|
||||
// ---- Display geometry (portrait, non-square) ----
|
||||
#define LCD_WIDTH 410
|
||||
#define LCD_HEIGHT 502
|
||||
|
||||
// ---- QSPI display pins (CO5300) ----
|
||||
// Same data lines as the 2.16 board, but SCLK and RESET differ.
|
||||
#define LCD_CS 12
|
||||
#define LCD_SCLK 11 // 2.06: GPIO11 (2.16 board used GPIO38)
|
||||
#define LCD_SDIO0 4
|
||||
#define LCD_SDIO1 5
|
||||
#define LCD_SDIO2 6
|
||||
#define LCD_SDIO3 7
|
||||
#define LCD_RESET 8 // direct GPIO (2.16 used GPIO2)
|
||||
#define LCD_TE 13 // tearing-effect line (unused by GFX driver)
|
||||
|
||||
// CO5300 GRAM is wider than the 410px active area, so the visible image needs
|
||||
// a horizontal start offset. 22 verified for this panel (matches the value used
|
||||
// by other 2.06 firmwares; eliminates the uninitialized green band on the right).
|
||||
#define CO5300_COL_OFFSET 22
|
||||
|
||||
// ---- I2C bus (touch + PMU + IMU + RTC all share one bus) ----
|
||||
#define IIC_SDA 15
|
||||
#define IIC_SCL 14
|
||||
|
||||
// ---- Touch (FT3168 via minimal inline FocalTech-style I2C reader) ----
|
||||
#define TP_INT 38
|
||||
#define TP_RST 9 // direct GPIO (active LOW)
|
||||
#define FT3168_ADDR 0x38
|
||||
|
||||
// ---- PMU ----
|
||||
#define AXP2101_ADDR 0x34
|
||||
|
||||
// ---- Buttons ----
|
||||
#define BTN_BACK_GPIO 0 // BOOT — primary, Space (PTT)
|
||||
#define BTN_PWR_GPIO 10 // PWR side button — cycle screens / animations
|
||||
// PWR polarity: assumed active LOW with internal pull-up (button to GND, like
|
||||
// BOOT). If the screen-cycle button never registers, flip BTN_PWR_ACTIVE_LOW.
|
||||
#define BTN_PWR_ACTIVE_LOW 1
|
||||
|
||||
// ---- Capability flags ----
|
||||
#define BOARD_HAS_SECONDARY_BUTTON 0 // only BOOT via input_hal; PWR via power_hal
|
||||
#define BOARD_HAS_ROTATION 0 // non-square panel, fixed orientation
|
||||
#define BOARD_HAS_IMU 1 // present + initialized, rotation off
|
||||
#define BOARD_HAS_BATTERY 1
|
||||
#define BOARD_HAS_IO_EXPANDER 0
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
// AMOLED-2.06 has no IO expander — display and touch reset lines are direct
|
||||
// ESP32 GPIOs. We bring up the shared I2C bus and release both reset lines
|
||||
// before any display/touch HAL call runs.
|
||||
//
|
||||
// Display power: on this kit the AMOLED rail is supplied by the AXP2101 PMU
|
||||
// (the "DSI_PWR_EN" net on the schematic sits on the PMIC, not an ESP32 pin),
|
||||
// matching the AMOLED-2.16 sibling which lights up with no explicit rail
|
||||
// poke after pmu.begin(). If the panel stays dark on first flash, enable the
|
||||
// AXP rails in power.cpp (see the commented block there).
|
||||
extern "C" void board_init(void) {
|
||||
Wire.begin(IIC_SDA, IIC_SCL);
|
||||
|
||||
// Release the touch controller from reset (active LOW). The display's own
|
||||
// reset is driven by the Arduino_GFX CO5300 driver via LCD_RESET.
|
||||
pinMode(TP_RST, OUTPUT);
|
||||
digitalWrite(TP_RST, LOW);
|
||||
delay(5);
|
||||
digitalWrite(TP_RST, HIGH);
|
||||
delay(50);
|
||||
}
|
||||
@@ -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,55 @@
|
||||
#include "../../hal/display_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Arduino_GFX_Library.h>
|
||||
|
||||
// AMOLED-2.06 is fixed at 0° (non-square 410x502 panel — the 2.16 board's CPU
|
||||
// strip-rotation assumes a square panel and can't be reused here). No rot_buf.
|
||||
//
|
||||
// Display driver is the CO5300 (same controller family as the 2.16 board).
|
||||
// Reset is a direct GPIO (LCD_RESET), so the GFX driver owns it. The CO5300's
|
||||
// active area starts partway into the controller GRAM, so it gets a column
|
||||
// offset to center the image (CO5300_COL_OFFSET — verify on hardware).
|
||||
|
||||
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: (bus, rst, rotation, w, h, col_off1, row_off1, col_off2, row_off2)
|
||||
gfx = new Arduino_CO5300(
|
||||
bus, LCD_RESET, 0 /* no rotation */,
|
||||
LCD_WIDTH, LCD_HEIGHT, CO5300_COL_OFFSET, 0, 0, 0);
|
||||
}
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
// 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,25 @@
|
||||
#include "../../hal/imu_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <SensorQMI8658.hpp>
|
||||
|
||||
// AMOLED-2.06 ships with QMI8658 populated, but the panel is non-square and
|
||||
// mounts in a fixed orientation, so rotation is disabled. We initialize the
|
||||
// device anyway to keep the shared I2C bus healthy, but always report 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-2.06 exposes the BOOT button as the primary HID input. The PWR side
|
||||
// button is handled in power_hal (BTN_PWR_GPIO). 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,119 @@
|
||||
#include "../../hal/power_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <XPowersLib.h>
|
||||
|
||||
// AXP2101 provides battery monitoring. The PWR side button on this kit is a
|
||||
// direct ESP32 GPIO (BTN_PWR_GPIO), not the AXP PKEY, so we poll it and
|
||||
// synthesize the same three edges the AXP IRQ path produces on the 2.16 board:
|
||||
// short — fired on release if the hold was shorter than PWR_LONG_MS
|
||||
// long — fired once when a hold crosses PWR_LONG_MS
|
||||
// release — fired on every release edge
|
||||
// This keeps the hold-to-pair gesture logic in main.cpp board-agnostic.
|
||||
|
||||
#define BATTERY_POLL_MS 2000
|
||||
#define CHARGING_POLL_MS 500
|
||||
#define PWR_POLL_MS 50
|
||||
#define PWR_LONG_MS 1500 // hold threshold, mirrors the AXP LONG IRQ
|
||||
|
||||
#if BTN_PWR_ACTIVE_LOW
|
||||
#define PWR_IS_DOWN(level) ((level) == LOW)
|
||||
#else
|
||||
#define PWR_IS_DOWN(level) ((level) == HIGH)
|
||||
#endif
|
||||
|
||||
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 pwr_long_flag = false;
|
||||
static bool pwr_released_flag = false;
|
||||
static bool last_pwr_down = false; // edge detector for BTN_PWR_GPIO
|
||||
static uint32_t pwr_press_started_ms = 0;
|
||||
static bool pwr_long_fired = 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();
|
||||
|
||||
// ---- OPTIONAL: explicit AMOLED rail enable ----------------------------
|
||||
// The 2.16 sibling lights up without this, so it's left off by default.
|
||||
// If the panel stays DARK on first flash, the display rail isn't powered.
|
||||
// Uncomment and set the channels/voltages to match the Waveshare 2.06
|
||||
// schematic (typical AXP2101 AMOLED kits use the ALDO/BLDO rails):
|
||||
//
|
||||
// pmu.setALDO1Voltage(1800); pmu.enableALDO1(); // panel logic 1.8V
|
||||
// pmu.setBLDO1Voltage(3300); pmu.enableBLDO1(); // panel/touch 3.3V
|
||||
// pmu.setBLDO2Voltage(3300); pmu.enableBLDO2();
|
||||
// delay(50);
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// PWR button is a direct GPIO on this board.
|
||||
pinMode(BTN_PWR_GPIO, BTN_PWR_ACTIVE_LOW ? INPUT_PULLUP : INPUT_PULLDOWN);
|
||||
|
||||
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_down = PWR_IS_DOWN(digitalRead(BTN_PWR_GPIO));
|
||||
if (pwr_down && !last_pwr_down) { // press begins
|
||||
pwr_press_started_ms = now;
|
||||
pwr_long_fired = false;
|
||||
} else if (pwr_down && last_pwr_down) { // held
|
||||
if (!pwr_long_fired && (now - pwr_press_started_ms >= PWR_LONG_MS)) {
|
||||
pwr_long_flag = true;
|
||||
pwr_long_fired = true;
|
||||
}
|
||||
} else if (!pwr_down && last_pwr_down) { // release
|
||||
pwr_released_flag = true;
|
||||
if (!pwr_long_fired) pwr_pressed_flag = true; // short press
|
||||
}
|
||||
last_pwr_down = pwr_down;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool power_hal_pwr_long_pressed(void) {
|
||||
if (pwr_long_flag) { pwr_long_flag = false; return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
bool power_hal_pwr_released(void) {
|
||||
if (pwr_released_flag) { pwr_released_flag = false; return true; }
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "../../hal/touch_hal.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
// Minimal capacitive-touch reader for the FT3168 (FocalTech). Same data layout
|
||||
// as the FT3168 on the AMOLED-1.8 board, so we reuse the inline reader instead
|
||||
// of vendoring the 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
|
||||
//
|
||||
// Axis orientation: started with no swap/mirror (matches the portrait FT3168
|
||||
// on the 1.8 board). If touch coordinates come out flipped/swapped on this
|
||||
// panel, mirror/swap them in touch_read_into_shared_state() below.
|
||||
|
||||
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 touch_read_into_shared_state(void) {
|
||||
Wire.beginTransmission(FT3168_ADDR);
|
||||
Wire.write(0x02);
|
||||
if (Wire.endTransmission(false) != 0) { touch_pressed = false; return; }
|
||||
if (Wire.requestFrom((uint8_t)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) {
|
||||
// FT3168 power-mode register 0xA5 = 0x00: active scanning.
|
||||
Wire.beginTransmission(FT3168_ADDR);
|
||||
Wire.write(0xA5);
|
||||
Wire.write(0x00);
|
||||
Wire.endTransmission();
|
||||
|
||||
// Verify the controller answers (FT3168 chip-id is reg 0xA0).
|
||||
Wire.beginTransmission(FT3168_ADDR);
|
||||
Wire.write(0xA0);
|
||||
if (Wire.endTransmission(false) == 0 && Wire.requestFrom((uint8_t)FT3168_ADDR, (uint8_t)1) == 1) {
|
||||
Serial.printf("Touch FT3168 ID=0x%02X (addr 0x%02X)\n", Wire.read(), FT3168_ADDR);
|
||||
} else {
|
||||
Serial.printf("Touch FT3168 ID read failed (addr 0x%02X)\n", FT3168_ADDR);
|
||||
}
|
||||
|
||||
pinMode(TP_INT, INPUT_PULLUP);
|
||||
attachInterrupt(TP_INT, touch_isr, FALLING);
|
||||
Serial.println("Touch attached on INT pin");
|
||||
}
|
||||
|
||||
void touch_hal_read(uint16_t* x, uint16_t* y, bool* pressed) {
|
||||
if (touch_data_ready) {
|
||||
touch_data_ready = false;
|
||||
touch_read_into_shared_state();
|
||||
}
|
||||
*x = touch_x;
|
||||
*y = touch_y;
|
||||
*pressed = touch_pressed;
|
||||
}
|
||||
Reference in New Issue
Block a user