Project_SylDa/docs/superpowers/specs/2026-07-28-esp32-pico-d4-port-design.md

5.5 KiB

ESP32-PICO-D4 (M5Stack Atom Lite) Port — Design Spec

Goal: Port the Sylvia Touch Relay project from ESP32-S3-Pico (Atom VoiceS3R) to ESP32-PICO-D4 (Atom Lite), update the Android app for CDC-class USB detection, compile, bundle, and deploy via ADB wireless.

Current State

The project targets the M5Stack Atom VoiceS3R with ESP32-S3-PICO-1-N8R8 (8MB Flash, 8MB PSRAM). The Android app filters USB devices by Espressif vendor ID (0x303A) for key exchange and firmware flashing. The esp32-flash-lib handles DTR/RTS via the SerialTransport interface implemented by UsbSerialTransport.

Target Hardware

M5Stack Atom Lite with ESP32-PICO-D4:

  • 4MB Flash (no PSRAM)
  • FTDI FT2232C/D USB-to-UART bridge (VID 0x0403) — appears as /dev/ttyUSB0
  • SK6812 addressable RGB LED on GPIO27
  • Relay on GPIO32 (G32)
  • Touch sensor on GPIO33 (G33) — open-drain type, needs pullup

Design Decisions

1. Side Branch & Git Worktree

Create branch esp32picod4 via git worktree at .worktrees/esp32picod4 to isolate the port from the main S3 variant.

2. Firmware (platformio.ini + src/main.cpp)

New PlatformIO environment m5stack-atom:

Setting Old (S3) New (PICO-D4) Reason
board esp32-s3-devkitc-1 m5stack-atom PlatformIO board ID for M5Stack Atom Lite
board_build.arduino.memory_type qio_opi removed Not needed for PICO-D4
-DESP32S3 present -DESP32 Correct chip family
-DBOARD_HAS_PSRAM present removed Atom Lite has no PSRAM
-mfix-esp32-psram-cache-issue present removed Only needed with PSRAM
-DARDUINO_USB_CDC_ON_BOOT=1 present removed No native USB on PICO-D4
-DARDUINO_USB_MODE=1 present removed No native USB
upload_port /dev/ttyACM0 /dev/ttyUSB0 FTDI serial device
lib_deps (empty) fastled/FastLED@^3.7 SK6812 Neopixel library

Pin definitions:

Constant Old New Notes
SENSOR_PIN 38 33 G33, open-drain, INPUT_PULLUP unchanged
RELAY_PIN 1 32 G32
LED_PIN 27 27 SK6812 Neopixel — replaces digitalWrite with FastLED API

BLE device name: "SyldaServer" (was "AtomS3R-Relay")

Serial banner: "Atom Lite BLE Relay" (was "Atom VoiceS3R BLE Relay")

LED code change: Replace digitalWrite(LED_PIN, HIGH/LOW) with FastLED:

  • Include FastLED.h
  • Define #define NUM_LEDS 1
  • CRGB leds[NUM_LEDS]
  • FastLED.addLeds<SK6812, LED_PIN, GRB>(leds, NUM_LEDS) in setup
  • Startup blink: 3x green (leds[0] = CRGB::Green; FastLED.show())

No other firmware logic changes — HMAC key exchange, BLE relay control, touch sensor logic remain identical.

3. Android USB Detection

Problem: The Atom Lite's FTDI bridge presents VID 0x0403, not Espressif's 0x303A. The app currently filters on VID and won't detect the device.

Solution: Replace VID (0x303A) filtering with CDC-ACM class filtering (interface class 0x0A).

Changes in UsbUtils.kt:

  • Add hasCdcDataInterface(device: UsbDevice): Boolean — iterates interfaces, returns true if any has interfaceClass == 0x0A

Changes in SettingsActivity.kt:

  • Remove ESP_VENDOR_ID constant
  • Replace all device?.vendorId == ESP_VENDOR_ID checks with UsbUtils.hasCdcDataInterface(device)

This detects all CDC-ACM serial devices including:

  • FTDI FT2232C/D (Atom Lite)
  • CP210x (some Atom Lite revisions)
  • ESP32-S3 native USB (current hardware)
  • CH340/CH9102 (other Atom variants)

4. DTR/RTS — No Changes Needed

Research findings:

The esp32-flash-lib's EspFlasherProtocol.enterBootLoader() implements the standard DTR/RTS download-mode sequence:

setControlLines(true, false)   // DTR=High → EN released, RTS=Low → GPIO0=Low (download mode)
delayMS(100)
setControlLines(false, true)   // DTR=Low → reset pulse via capacitor, RTS=High → GPIO0=High
delayMS(100)
setControlLines(true, false)   // DTR=High → release reset, RTS=Low → GPIO0=Low → boot in download mode

The UsbSerialTransport.setControlLines() implementation sends CDC_SET_CONTROL_LINE_STATE control transfers with correct DTR/RTS bit flags. This works for FTDI, CP2104, and native USB CDC-ACM.

The SettingsActivity.setupCdcControl() sets DTR|RTS both HIGH for normal serial operation (key exchange), which is correct for all bridge types.

Verdict: No changes to EspFlasher.kt, UsbSerialTransport.kt, or key exchange logic.

5. Build, Bundle & Deploy

  1. pio run -e m5stack-atom to compile firmware
  2. tools/copy_firmware.sh to copy binaries to android/app/src/main/assets/firmware/ (environment dir is now m5stack-atom)
  3. ./gradlew assembleDebug in android/ to build APK
  4. ADB wireless to connect phone (adb-wireless skill)
  5. adb install -r app-debug.apk to deploy

Files Changed

File Change
platformio.ini New environment m5stack-atom replacing S3 config
src/main.cpp New pin definitions, BLE name, FastLED LED code
android/.../UsbUtils.kt Add hasCdcDataInterface()
android/.../SettingsActivity.kt VID filter → CDC class filter
tools/copy_firmware.sh Environment name m5stack-atoms3r → m5stack-atom

Files Not Changed

File Reason
EspFlasher.kt DTR/RTS handled by library
UsbSerialTransport.kt Correct implementation
MainActivity.kt No BLE changes beyond device name (auto-discovered)
PrefsHelper.kt No changes
AndroidManifest.xml No changes
build.gradle No dependency changes