Skip to content

Add M5Stack FIRE board port (ESP32-classic, ILI9342C, no touch) - #146

Open
Sompong6103 wants to merge 4 commits into
HermannBjorgvin:mainfrom
Sompong6103:feat/m5stack-fire-port
Open

Add M5Stack FIRE board port (ESP32-classic, ILI9342C, no touch)#146
Sompong6103 wants to merge 4 commits into
HermannBjorgvin:mainfrom
Sompong6103:feat/m5stack-fire-port

Conversation

@Sompong6103

@Sompong6103 Sompong6103 commented Aug 5, 2026

Copy link
Copy Markdown

Add M5Stack FIRE board port (ESP32-classic, ILI9342C, no touch)

Summary

Adds a seventh board port: the M5Stack FIRE. It's the first ESP32-classic target (all existing ports are S3 or C6) and the first touch-less board, so it exercises two paths the HAL hadn't before.

Verified end-to-end on hardware: display + orientation, IP5306 battery/charging, the BLE data service showing live usage from the daemon (session 6% / weekly 50%), and button navigation.
IMG_3526

What's in it

New board (firmware/src/boards/m5stack_fire/)

  • display.cppArduino_ILI9342 (mainline GFX ≥1.6.4) over 4-wire SPI @ 40 MHz, ips=true, rotation 2 (upright with the front buttons along the bottom edge). LEDC-PWM backlight, same as the LCD-1.54.
  • touch.cpp — permanent no-op; there is no touch panel.
  • input.cpp — front buttons A (GPIO39) → HID Space, C (GPIO37) → HID Shift+Tab, on the input-only GPIOs (external pull-ups, plain INPUT).
  • power.cppIP5306 over I2C for battery (coarse 25/50/75/100 % gauge → the four-state icon) and charging; middle button B (GPIO38) is the PWR-role button. No software power-off — the IP5306's side button handles hardware power.
  • imu.cpp / sound.cpp — MPU6886 unused (fixed orientation); speaker is a DAC path, not the I2S/ES8311 chime engine, so sound no-ops (same posture as the 2.06 / C6 ports).

Touch-less navigation (has_touch capability)

  • The splash↔usage toggle was touch-only (global_click_cb on LV_EVENT_CLICKED), so a board with no touch panel could never leave the splash — it sat there forever even with data flowing.
  • Adds a has_touch field to BoardCaps and routes the PWR button to ui_toggle_splash() (previously dead code) on touch-less boards. Animations still auto-rotate (~20 s) and follow the usage-rate group, so manual cycling isn't lost.
  • All existing boards are touch boards and are set has_touch = true; no behavior change on them.

Env + docs

  • New [env:m5stack_fire] in platformio.ini. The m5stack-fire board JSON already supplies the classic-ESP32 MCU, PSRAM (+ rev1 cache workaround), 16 MB flash and partitions, so the env stays minimal.
  • No ARDUINO_USB_CDC_ON_BOOT: classic ESP32 has no native USB — Serial (and the screenshot command) run over UART0 through the on-board CH9102 bridge, which enumerates as /dev/cu.usbserial-* / /dev/ttyUSB* (not usbmodem*), and upload runs at 460800 (921600 is flaky on that bridge).
  • CLAUDE.md + docs/porting/adding-a-board.md updated: the port disproves the old "S3-only / QSPI-AMOLED-only / touch-required" assumptions.

Build / flash

./flash-mac.sh m5stack_fire                       # auto-detects

or

pio run -d firmware -e m5stack_fire
pio run -d firmware -e m5stack_fire -t upload --upload-port /dev/cu.usbserial-XXXX

Notes for reviewers / follow-ups

  • The has_touch change touches every board's caps.cpp (one line each) — that's the intended blast radius of adding a capability field, not scope creep.
  • IP5306 charging-bit masks come from community notes and are commented as such in power.cpp; the bolt reads correctly on this unit but the masks are worth a second look on other IP5306 revisions.
  • The 240 px-tall panel lands on the existing 240×240 "small" UI breakpoint; the extra 80 px of width is unused headroom a future dedicated landscape breakpoint could claim.
  • Audio is intentionally deferred (DAC path unwired).

Commits

  1. feat: add has_touch cap so touchless boards navigate splash via PWR
  2. feat: add M5Stack FIRE board port

Each commit builds on its own.

🤖 Generated with Claude Code

Sompong6103 and others added 3 commits August 6, 2026 05:39
The splash<->usage screen toggle was driven only by a touchscreen tap
(global_click_cb on LV_EVENT_CLICKED). Boards with no touch panel had no
way to leave the splash at all — the PWR button only cycled animations, so
the device sat on the splash forever even with data flowing.

Add a `has_touch` field to BoardCaps and route the PWR button to
ui_toggle_splash() (previously dead code) on touchless boards, giving them
the splash<->usage navigation the tap provides elsewhere. Animations still
auto-rotate (~20s) and follow the usage-rate group, so manual cycling isn't
lost. Every existing board is a touch board and is set has_touch = true.

No behavior change on touch boards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gjg2WZ5FuhRRivQCL3sQm1
First ESP32-classic port (all others are S3/C6) and the first touch-less
board. 320x240 ILI9342C TFT over 4-wire SPI, three front buttons in place
of touch, IP5306 power-bank IC for battery, MPU6886 IMU (populated, unused).

- display.cpp: Arduino_ILI9342 @ 40 MHz, ips=true, rotation 2 (upright with
  the front buttons along the bottom edge). LEDC-PWM backlight.
- touch.cpp: permanent no-op (no touch panel); the has_touch=false cap makes
  the middle button (B) the splash<->usage toggle.
- input.cpp: BtnA (GPIO39) -> HID Space, BtnC (GPIO37) -> HID Shift+Tab, on
  the input-only GPIOs (external pull-ups, plain INPUT).
- power.cpp: IP5306 over I2C for battery (coarse 25/50/75/100% gauge) and
  charging; middle button (GPIO38) is the PWR-role button. No software
  power-off (the IP5306's side button handles hardware power).
- sound.cpp: no-op (speaker is a DAC path, not the I2S/ES8311 chime engine).

The m5stack-fire board JSON supplies the classic-ESP32 MCU, PSRAM (+ rev1
cache workaround), 16 MB flash and partitions, so the env stays minimal. No
ARDUINO_USB_CDC_ON_BOOT: classic ESP32 has no native USB — Serial (and the
screenshot command) run over UART0 through the CH9102 bridge, which also
means upload is at 460800 (921600 is flaky on that bridge).

Verified on hardware: display, orientation, IP5306 battery/charging, BLE
data service end-to-end (live usage from the daemon), and button navigation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gjg2WZ5FuhRRivQCL3sQm1
The M5Stack FIRE is ESP32-classic over a CH9102 UART bridge, so it
enumerates as /dev/cu.usbserial-* (or /dev/cu.wchusbserial*), not the
/dev/cu.usbmodem* that the native-USB S3/C6 boards expose. Auto-detect
only globbed usbmodem*, so `./flash-mac.sh m5stack_fire` failed to find
the port. Make port detection board-aware and point at the CH34x/CH9102
driver in the not-found message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PZgpe4j1XPLai2KY9PZTky

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new firmware board port for the M5Stack FIRE (ESP32-classic, ILI9342C SPI TFT, no touch) and extends the existing board capability model so shared UI/navigation works on touchless targets.

Changes:

  • Introduces a BoardCaps.has_touch capability and updates shared button handling to allow splash ↔ usage navigation without touch.
  • Adds firmware/src/boards/m5stack_fire/ implementing the HAL (display, input, power, etc.) for the M5Stack FIRE.
  • Updates PlatformIO env, flashing helper, and porting/docs to reflect support for ESP32-classic + non-touch boards.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
flash-mac.sh Auto-detects UART-bridge serial devices for m5stack_fire vs native USB-JTAG for S3/C6 boards.
firmware/src/main.cpp Routes PWR short-press differently for touchless boards via BoardCaps.has_touch.
firmware/src/hal/board_caps.h Adds has_touch to runtime capabilities used by shared code.
firmware/src/boards/waveshare_lcd_154/caps.cpp Sets has_touch = true for existing board.
firmware/src/boards/waveshare_amoled_216/caps.cpp Sets has_touch = true for existing board.
firmware/src/boards/waveshare_amoled_216_c6/caps.cpp Sets has_touch = true for existing board.
firmware/src/boards/waveshare_amoled_206/caps.cpp Sets has_touch = true for existing board.
firmware/src/boards/waveshare_amoled_18/caps.cpp Sets has_touch = true for existing board.
firmware/src/boards/waveshare_amoled_18_c6/caps.cpp Sets has_touch = true for existing board.
firmware/src/boards/template/caps.cpp Updates template to include has_touch guidance for new ports.
firmware/src/boards/m5stack_fire/touch.cpp Implements a permanent no-op touch HAL for a non-touch device.
firmware/src/boards/m5stack_fire/sound.cpp No-op sound HAL for the FIRE’s DAC speaker path (not I2S/ES8311).
firmware/src/boards/m5stack_fire/power.cpp Implements IP5306 battery/charge + synthesized PWR button edges.
firmware/src/boards/m5stack_fire/input.cpp Implements A/C front buttons on input-only GPIOs (active LOW).
firmware/src/boards/m5stack_fire/imu.cpp No-op IMU HAL (fixed orientation).
firmware/src/boards/m5stack_fire/display.cpp Implements ILI9342C SPI TFT + LEDC PWM backlight.
firmware/src/boards/m5stack_fire/caps.cpp Declares FIRE runtime capabilities (size/buttons/battery/touch=false).
firmware/src/boards/m5stack_fire/board.h Defines pin map and capability flags for the M5Stack FIRE.
firmware/src/boards/m5stack_fire/board_init.cpp Initializes I2C bus for IP5306/MPU6886.
firmware/platformio.ini Adds [env:m5stack_fire] and dependencies/build flags for the new target.
docs/porting/adding-a-board.md Updates porting assumptions: broader ESP32 family + SPI TFT + optional touch.
CLAUDE.md Updates project context/docs to include the new FIRE port and ESP32-classic support.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread firmware/src/boards/m5stack_fire/power.cpp Outdated
Comment thread CLAUDE.md
- `boards/waveshare_amoled_18_c6/` — Waveshare ESP32-C6-Touch-AMOLED-1.8 (368×448 portrait, SH8601, FT3168 touch, TCA9554 expander). Build env: `waveshare_amoled_18_c6`. Same panel as the S3 1.8 but on the C6 SoC. All subsystems (display, touch, BOOT + PWR buttons, battery, BLE) verified on hardware.
- `boards/waveshare_amoled_206/` — Waveshare ESP32-S3-Touch-AMOLED-2.06 (CO5300, 410×502 watch form factor, FT3168 touch, no IO expander, 32 MB flash, PCF85063 RTC, ES8311 codec). Build env: `waveshare_amoled_206`. Display, touch, battery, IMU init, and BLE verified on hardware; the ES8311 chime path is not wired up (`sound.cpp` no-ops).
- `boards/waveshare_lcd_154/` — Waveshare ESP32-S3-Touch-LCD-1.54 (ST7789, 240×240 square, CST816T touch @ 0x15). Build env: `waveshare_lcd_154`. **The first non-AMOLED port**: a plain 4-wire SPI TFT, not QSPI, and the panel has no brightness command — backlight is LEDC PWM on `LCD_BL`. **No PMU**: battery is an ADC divider on GPIO1 and `BAT_EN` (GPIO2) is a power-hold line that must be driven HIGH early in `board_init()` or the board browns out on battery. Three buttons (BOOT + GPIO5 + a PWR-role GPIO4); ES8311 chime wired up; QMI8658 populated but unused (fixed orientation, no rotation).
- `boards/m5stack_fire/` — M5Stack FIRE (ILI9342C, 320×240 landscape SPI TFT, **no touch**, IP5306 PMU, MPU6886 unused). Build env: `m5stack_fire`. **The first ESP32-classic port and the first touch-less board**: navigation is the three front buttons (A=GPIO39 left→Space, C=GPIO37 right→Shift+Tab, B=GPIO38 middle→PWR-role: cycle screens/brightness + hold-to-pair). Battery/charging come from the **IP5306** power-bank IC over I2C (coarse 25/50/75/100 % gauge, feeds the four-state icon); hardware power on/off is the IP5306's own red side button, so no software power-off. Speaker is a DAC path, not the I2S/ES8311 chime engine, so `sound.cpp` no-ops. Classic ESP32 has **no native USB** — Serial (and the `screenshot` command) run over UART0 through the on-board CH9102 bridge, which appears as `/dev/cu.usbserial-*` (macOS) / `/dev/ttyUSB*` (Linux), *not* `usbmodem*`. The `m5stack-fire` board JSON supplies PSRAM (+ the rev1 cache workaround), 16 MB flash and the 16 MB partitions, so the env stays minimal. 240px-tall → lands on the existing "small" (240×240) UI breakpoint; the extra 80px of width is unused headroom a future landscape breakpoint could claim.
// so input.cpp uses plain INPUT (not INPUT_PULLUP).
// A (left) → primary, HID Space (voice-mode PTT)
// C (right) → secondary, HID Shift+Tab (mode toggle)
// B (middle) → PWR-role, cycle screens / brightness / hold-to-pair
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants