Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .planning/phases/01-sticks3-port/SPEC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Phase 01 — M5StickS3 hardware port

## Goal

Make the firmware build, flash, and run on the **M5Stack StickS3** (ESP32-S3-PICO-1-N8R8) without breaking the existing **M5StickC Plus** (ESP32) build. Both targets coexist in `platformio.ini` as separate envs.

Target device docs: https://docs.m5stack.com/en/core/StickS3

## Hardware deltas vs current target

| Component | StickC Plus (current) | StickS3 (new) |
|---|---|---|
| MCU | ESP32 (xtensa) | ESP32-S3 (LX7), 8 MB flash, 8 MB PSRAM |
| Library | `m5stack/M5StickCPlus` | `m5stack/M5Unified` |
| Display | ST7789v2 135×240 | ST7789P3 135×240 (same resolution) |
| PMIC | AXP192 | M5PM1 |
| IMU | MPU6886 | BMI270 |
| RTC | BM8563 (hardware) | **none** |
| Audio | passive buzzer (PWM) | ES8311 codec + AW8737 amp + speaker |
| Sprites | `TFT_eSprite` | `M5Canvas` (M5GFX) |

## Decisions (locked before planning)

1. **Two coexisting PIO envs.** `m5stickc-plus` (untouched) and `m5stickc-plus-s3` (new). No shared `[env]` block — keep envs independent so a regression in one doesn't silently affect the other.
2. **Library: M5Unified** for the S3 build. We do **not** also migrate the StickC Plus build to M5Unified in this phase — out of scope, would balloon the diff. Source files use `#ifdef ARDUINO_M5STACK_STAMPS3` (or the actual board macro for StickS3) to switch include + API surface.
3. **Clock feature: stubbed.** No hardware RTC on StickS3 and WiFi/NTP is deferred to a future phase. Landscape clock view shows "—:—" on the S3 build. The view layout still renders (so the gesture/orientation code is exercised), but time/date strings are placeholders. NTP wiring can be added later without touching the port-level changes.
4. **AXP temp readout dropped on S3.** `M5.Axp.GetTempInAXP192()` (debug page) has no equivalent on M5PM1; show `--` on S3.
5. **No new features.** Pure port. No UX changes, no new menu items beyond the WiFi config needed for NTP.
6. **BLE bridge preserved.** `src/ble_bridge.cpp` has zero M5 API references — should compile as-is on S3 (Arduino-ESP32 BLE stack is unchanged).

## Scope (file-by-file)

| File | LOC | M5 API sites | Change |
|---|---|---|---|
| `platformio.ini` | 16 | — | Add `[env:m5stickc-plus-s3]` |
| `src/main.cpp` | 1265 | ~38 | Wrap `M5.Axp/Imu/Rtc/Beep` behind `#ifdef`; replace `TFT_eSprite` with portable typedef; stub clock strings to `"—:—"` on S3 |
| `src/buddy.cpp` | 196 | 4 | Sprite typedef swap |
| `src/character.cpp` | 401 | 4 | Sprite typedef swap |
| `src/ble_bridge.cpp` | 180 | 0 | No change (no M5 API references) |
| `src/buddy_common.h` | 37 | — | Central `BuddySprite` typedef alias (`TFT_eSprite` or `M5Canvas`) |

Rough budget: ~150 LOC changed (smaller without NTP), 2–3 hours of focused work plus on-device validation.

## Out of scope

- Migrating the StickC Plus build to M5Unified
- Any new buddy/character/UI features
- Removing the legacy StickC Plus env (kept indefinitely)
- WiFi configuration UX (deferred — separate phase)
- NTP time sync (deferred — separate phase)
- Captive portal setup
- OTA updates

## Verification (what "done" looks like)

1. `pio run -e m5stickc-plus` succeeds (existing build unchanged)
2. `pio run -e m5stickc-plus-s3 -t upload` flashes to the StickS3 over USB
3. On hardware:
- Boot to default buddy view, GIF/sprite render correct
- BtnA/BtnB cycle pets, brightness menu works
- Speaker plays the boot beep
- Shake / face-down detection trigger expected state changes
- Landscape clock view renders layout but time/date show "—:—" (NTP deferred)
- BLE bridge pairs from desktop and round-trips a prompt

## Next step

`/gsd-plan-phase` style breakdown isn't available (no GSD bootstrap). Write `PLAN.md` in this directory by hand once open questions are resolved.
21 changes: 21 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,24 @@ lib_deps =
m5stack/M5StickCPlus
bitbank2/AnimatedGIF @ ^2.1.1
bblanchon/ArduinoJson @ ^7.0.0

[env:m5stickc-plus-s3]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
board_build.filesystem = littlefs
board_build.partitions = no_ota.csv
board_build.flash_size = 8MB
board_build.psram_type = opi
board_upload.flash_size = 8MB
board_build.arduino.memory_type = qio_opi
build_flags =
-DCORE_DEBUG_LEVEL=0
-DBOARD_HAS_PSRAM
-DBUDDY_TARGET_STICKS3=1
build_src_filter = +<*> +<buddies/>
lib_deps =
m5stack/M5Unified @ ^0.2.0
bitbank2/AnimatedGIF @ ^2.1.1
bblanchon/ArduinoJson @ ^7.0.0
3 changes: 0 additions & 3 deletions src/buddies/axolotl.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace axolotl {

// Base silhouette: }~ ... ~{ are the feathery gills, ( .--. ) is the smile,
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/blob.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace blob {

// ─── SLEEP ─── ~12s cycle, 6 poses
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/cactus.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace cactus {

// ─── SLEEP ─── ~12s cycle, 6 poses
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/capybara.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace capybara {

// ─── SLEEP ─── ~12s cycle, 6 poses
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/cat.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace cat {

// ─── SLEEP ─── ~12s cycle, 6 poses (curled loaf, breathing, twitching tail)
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/chonk.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace chonk {

// ─── SLEEP ─── ~12s cycle, 6 poses (heavy belly breathing)
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/dragon.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace dragon {

// ─── SLEEP ─── ~12s cycle, 6 poses
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/duck.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace duck {

// ─── SLEEP ─── ~12s cycle, head tucked, gentle bobs on water
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/ghost.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace ghost {

// ─── SLEEP ─── ~12s cycle, drifting + fading
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/goose.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace goose {

// ─── SLEEP ─── ~12s cycle, 6 poses, head tucked under wing
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/mushroom.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace mushroom {

// ─── SLEEP ─── ~12s cycle, 6 poses
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/octopus.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace octopus {

// ─── SLEEP ─── ~12s cycle, 6 poses
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/owl.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace owl {

// ─── SLEEP ─── ~12s cycle, 6 poses
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/penguin.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace penguin {

// ─── SLEEP ─── ~12s cycle, 6 poses, curled on ice
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/rabbit.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace rabbit {

// ─── SLEEP ─── ~12s cycle, 6 poses
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/robot.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace robot {

// ─── SLEEP ─── ~12s cycle, 6 poses, robot in low-power mode
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/snail.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace snail {

// ─── SLEEP ─── ~12s cycle, 6 poses — tucked in shell, snoring slime bubbles
Expand Down
3 changes: 0 additions & 3 deletions src/buddies/turtle.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "../buddy.h"
#include "../buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;

namespace turtle {

// ─── SLEEP ─── ~12s cycle, 6 poses (turtle retracts into shell)
Expand Down
9 changes: 4 additions & 5 deletions src/buddy.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
#include "buddy.h"
#include "buddy_common.h"
#include <M5StickCPlus.h>
#include <string.h>

extern TFT_eSprite spr;
extern BuddySprite spr;

// Mirrors PersonaState in main.cpp
enum { B_SLEEP, B_IDLE, B_BUSY, B_ATTENTION, B_CELEBRATE, B_DIZZY, B_HEART };
Expand Down Expand Up @@ -33,7 +32,7 @@ const uint16_t BUDDY_BLUE = 0x041F;
// M5.Lcd for landscape clock mode (both inherit TFT_eSPI). Coords stay
// fixed — species hardcode BUDDY_X_CENTER/BUDDY_Y_OVERLAY in their
// particle calls, so retargeting position would only move the body.
static TFT_eSPI* _tgt = &spr;
static BuddyCanvas* _tgt = &spr;
// 2× on home screen, 1× in peek (PET/INFO) and landscape clock. Species
// art is space-padded to a fixed width for alignment at 1×; at 2× we trim
// and re-center per line so the padding doesn't push ink off-screen.
Expand Down Expand Up @@ -158,12 +157,12 @@ void buddySetPeek(bool peek) {
// clearing. Advances the frame counter so animation runs even when
// buddyTick is bypassed.
// Landscape clock callsite — always 1×.
void buddyRenderTo(TFT_eSPI* tgt, uint8_t personaState) {
void buddyRenderTo(BuddyCanvas* tgt, uint8_t personaState) {
uint8_t prevS = _scale; _scale = 1;
if (personaState >= 7) personaState = B_IDLE;
uint32_t now = millis();
if ((int32_t)(now - nextTickAt) >= 0) { nextTickAt = now + TICK_MS; tickCount++; }
TFT_eSPI* prev = _tgt;
BuddyCanvas* prev = _tgt;
_tgt = tgt;
const Species* sp = SPECIES_TABLE[currentSpeciesIdx];
if (sp->states[personaState]) sp->states[personaState](tickCount);
Expand Down
4 changes: 2 additions & 2 deletions src/buddy.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once
#include <stdint.h>
#include "buddy_common.h" // BuddyCanvas typedef

// Multi-species ASCII buddy renderer. Each species lives in its own
// src/buddies/<name>.cpp file and exposes 7 state functions matching
Expand All @@ -8,8 +9,7 @@
void buddyInit();
void buddyTick(uint8_t personaState);
void buddyInvalidate();
class TFT_eSPI;
void buddyRenderTo(TFT_eSPI* tgt, uint8_t personaState);
void buddyRenderTo(BuddyCanvas* tgt, uint8_t personaState);
void buddySetSpecies(const char* name);
void buddySetSpeciesIdx(uint8_t idx);
void buddyNextSpecies();
Expand Down
12 changes: 12 additions & 0 deletions src/buddy_common.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
#pragma once
#include <stdint.h>

// One include site for the M5 SDK. Switches by build target so the rest
// of the code can use BuddySprite/BuddyCanvas without #ifdefs.
#ifdef BUDDY_TARGET_STICKS3
#include <M5Unified.h>
using BuddySprite = M5Canvas; // sprite-like offscreen buffer
using BuddyCanvas = LovyanGFX; // common rendering base for sprite + display
#else
#include <M5StickCPlus.h>
using BuddySprite = TFT_eSprite;
using BuddyCanvas = TFT_eSPI;
#endif

// Shared constants and helpers for buddy species files.
// Each species file (src/buddies/<name>.cpp) includes this header
// and defines its 7 state functions.
Expand Down
Loading