| name | M5Stack Development |
| description | This skill should be used when the user asks to "build for M5Stack", "flash M5Stack", "M5StickS3 project", "create M5Stack firmware", "PlatformIO ESP32 M5", "upload to M5Stack", "M5Unified", "ESP32-S3 boot mode", "M5Stack IMU", "M5Stack display", or mentions M5Stack, M5StickS3, M5StickC, M5Core, or ESP32-S3 embedded development with PlatformIO. Provides hardware specs, PlatformIO configuration, upload workflow, and M5Unified library patterns.
|
| version | 0.1.0 |
M5Stack Development with PlatformIO
Guide for developing firmware for M5Stack devices using PlatformIO,
Arduino framework, and M5Unified library.
Supported Devices
The primary device documented is the M5StickS3, but M5Unified
auto-detects the connected M5Stack device at runtime. The same codebase
works across the M5Stack family with minimal changes.
For detailed hardware specs and pinouts, consult
references/m5sticks3-hardware.md.
Project Setup
Initialize a PlatformIO Project
pio project init --board esp32-s3-devkitc-1 --project-option "framework=arduino"
Configure platformio.ini
A working example configuration is available at examples/platformio.ini.
Key configuration points for ESP32-S3 based M5Stack devices:
- Board:
esp32-s3-devkitc-1 (no dedicated M5StickS3 board in PlatformIO yet)
- Memory type:
qio_opi for the N8R8 module (8MB Flash + 8MB PSRAM)
- USB CDC: Required for serial output over native USB — set
ARDUINO_USB_CDC_ON_BOOT=1
- PSRAM: Enable with
-DBOARD_HAS_PSRAM build flag
- Library:
m5stack/M5Unified (auto-detects device, includes M5GFX)
Identifying the Serial Port
ESP32-S3 native USB appears as /dev/cu.usbmodem* on macOS (not /dev/cu.usbserial*).
Detect with:
ls /dev/cu.usb*
Upload Workflow
ESP32-S3 with native USB requires manual boot mode entry before uploading:
- Hold the Boot button (labeled G0 or BOOT on the device side)
- While holding Boot, press and release Reset (RST button)
- Release Boot — screen goes blank, device is in download mode
- Run
pio run -t upload
- Device auto-resets after successful upload
Once firmware with USB CDC is running, subsequent uploads work automatically
without manual boot mode — the upload tool resets the device via DTR/RTS signals.
Manual boot mode is only needed for:
- The very first flash onto a blank/factory device
- Recovery after a firmware crash that prevents USB CDC from initializing
M5Unified Library Patterns
Initialization
#include <M5Unified.h>
void setup() {
auto cfg = M5.config();
cfg.internal_imu = true;
M5.begin(cfg);
M5.Display.setRotation(1);
}
void loop() {
M5.update();
}
Display (M5Canvas Double-Buffering)
For flicker-free drawing, use an off-screen sprite:
static M5Canvas canvas(&M5.Display);
void setup() {
canvas.createSprite(240, 135);
}
void loop() {
canvas.fillSprite(TFT_BLACK);
canvas.fillCircle(120, 67, 10, TFT_YELLOW);
canvas.pushSprite(0, 0);
}
Display in landscape rotation 1: 240 x 135 pixels (width x height).
IMU (Accelerometer + Gyroscope)
Critical: Call M5.Imu.update() explicitly each loop iteration before
reading data. Without this, getImuData() returns all zeros on StickS3.
M5.Imu.update();
auto imu = M5.Imu.getImuData();
float ax = imu.accel.x;
float ay = imu.accel.y;
float az = imu.accel.z;
float gx = imu.gyro.x;
IMU axis mapping in landscape rotation 1 (240x135):
accel.x maps to screen X axis (negate for natural "gravity" tilt feel)
accel.y maps to screen Y axis
Consult references/m5sticks3-hardware.md for axis orientation diagrams.
Device Orientation (Landscape Rotation 1)
B (side button, top edge) (MIC)
┌────────────────=====─────────.─┐
IR-TX │ ┌─────────────────────┐ │
│ │ │ ┌┐ │
SPK │ │ ▲Y │ ││ A = USB
│ │ │ │ ││ =
│ │ └───►X │ └┘ │
IR-RX │ └─────────────────────┘ │
└───────────────────────────===──┘
power
- A button: Right side of screen (front face)
- B button: Top edge (side button)
- USB: Right edge
- Screen: 240x135, origin top-left, X→right, Y→down
- IMU: negate
accel.x for natural gravity on screen X axis
IMU Best Practices
EMA low-pass filter to remove sensor jitter (alpha=0.25 is a good balance):
static float axSmooth = 0, aySmooth = 0;
M5.Imu.update();
auto imu = M5.Imu.getImuData();
float axRaw = constrain(-imu.accel.x, -1.0f, 1.0f);
float ayRaw = constrain(imu.accel.y, -1.0f, 1.0f);
axSmooth += 0.25f * (axRaw - axSmooth);
aySmooth += 0.25f * (ayRaw - aySmooth);
- Clamp to ±1g prevents extreme force spikes during shaking
- EMA alpha=0.25 removes jitter without feeling sluggish (0.08 is too laggy)
- Without smoothing, beads/balls "boil" in corners from sensor noise
Buttons
M5.update();
if (M5.BtnA.wasPressed()) { }
if (M5.BtnA.isPressed()) { }
if (M5.BtnA.wasReleased()) { }
if (M5.BtnB.wasPressed()) { }
Short press vs hold pattern (e.g., tap=action1, hold=action2):
static bool held = false;
static unsigned long downTime = 0;
if (M5.BtnA.wasPressed()) { downTime = millis(); held = false; }
if (M5.BtnA.isPressed() && millis() - downTime > 300) {
held = true;
}
if (M5.BtnA.wasReleased() && !held) {
}
Speaker
M5.Speaker.tone(880, 100);
M5.Speaker.setVolume(128);
Raw PCM playback for sound effects (clicks, impacts — much better than tone()):
static int16_t click[200];
for (int i = 0; i < 200; i++) {
float noise = ((float)((int32_t)(esp_random() >> 1)) / (float)(INT32_MAX / 2));
float envelope = expf(-(float)i / 200 * 5.0f);
click[i] = (int16_t)(noise * envelope * 30000.0f);
}
M5.Speaker.setChannelVolume(0, volume);
M5.Speaker.playRaw(click, 200, 16000, false, 1, 0, true);
Microphone
auto micCfg = M5.Mic.config();
micCfg.sample_rate = 16000;
micCfg.magnification = 4;
M5.Mic.config(micCfg);
M5.Mic.begin();
int16_t buf[256];
M5.Mic.record(buf, 256, 16000);
Critical: On StickS3, the mic and speaker share the ES8311 audio codec.
When switching from mic to speaker, fully reinitialize:
M5.Mic.end();
M5.Speaker.end();
delay(50);
M5.Speaker.begin();
delay(50);
Power Management
M5.Power.getBatteryLevel();
M5.Power.isCharging();
Build and Monitor Commands
pio run
pio run -t upload
pio device monitor
pio run -t upload && pio device monitor
Troubleshooting
| Problem | Solution |
|---|
| "Could not configure port" | Device not in download mode. boot first, then hold boot 2 seconds until you see flashing light. |
| No serial output | Ensure -DARDUINO_USB_CDC_ON_BOOT=1 in build flags |
| Display blank after upload | Check M5.Display.setRotation() value |
| IMU returns zeros | Call M5.Imu.update() explicitly each loop — required on StickS3 |
| Library not found | Run pio lib install "m5stack/M5Unified" |
| PSRAM not detected | Add -DBOARD_HAS_PSRAM and set memory_type = qio_opi |
Additional Resources
Reference Files
references/m5sticks3-hardware.md — Detailed hardware specs, chip info, peripheral details, and pin assignments for the M5StickS3
references/platformio-troubleshooting.md — Extended troubleshooting, ESP32-S3 USB quirks, partition schemes, and build flag reference
Example Files
examples/platformio.ini — Working PlatformIO configuration for M5StickS3