M5Stick S3 · Volume 7
M5Stack M5StickS3 Volume 7 — Programming Environments
Arduino + PlatformIO + MicroPython + UiFlow 2 + ESP-IDF — the five paths with M5StickS3-specific deltas
Contents
1. About this volume
Vol 7 covers the programming environments for writing custom code on M5StickS3. Same five paths as Cardputer ADV: Arduino IDE, PlatformIO, MicroPython, UiFlow 2, ESP-IDF. Tooling at the toolchain level is identical (pio run, arduino-cli, etc.) — the differences are in:
- PlatformIO
boardtarget name (esp32-s3-devkitc-1orm5stick-s3if published) - The OPI PSRAM build flag — mandatory for M5StickS3 (Cardputer ADV doesn’t need it; M5StickS3 does)
- M5Unified library APIs specific to M5StickS3 features (audio, IR RX, IMU model)
Cross-reference: the Cardputer ADV’s Vol 7 covers all five environments in depth. This volume focuses on the M5StickS3-specific deltas without re-authoring the shared content.
2. Arduino IDE 2.x
Setup:
- Install Arduino IDE 2.x from https://www.arduino.cc/en/software.
- Add ESP32 board manager URL: Preferences → Additional Board Manager URLs → add
https://espressif.github.io/arduino-esp32/package_esp32_index.json. - Install ESP32 board package: Tools → Board → Boards Manager → search “esp32” → install “esp32 by Espressif Systems” (v3.0.0 or newer).
- Install M5Unified library: Sketch → Include Library → Manage Libraries → search “M5Unified” → install. Pulls M5GFX as dependency.
Board settings (Tools menu) for M5StickS3:
| Setting | Value | Notes |
|---|---|---|
| Board | ”M5Stack-StickS3” if available, else “ESP32S3 Dev Module” | Future M5Stack updates may add native target |
| Upload Speed | 1500000 | Fast and reliable |
| USB CDC On Boot | Enabled | Mandatory for native USB-CDC |
| USB Mode | Hardware CDC and JTAG | |
| Flash Size | 8 MB | |
| Partition Scheme | ”8M with spiffs (3MB APP/1.5MB SPIFFS)“ | Or similar |
| PSRAM | OPI PSRAM | Mandatory — the #1 M5StickS3 setting that gets missed |
| Core Debug Level | None (production) or Info (dev) | |
| Port | Auto-detect from Tools → Port | /dev/ttyACM0 typical |
Hello-world sketch:
#include <M5Unified.h>
void setup() {
auto cfg = M5.config();
M5.begin(cfg);
M5.Display.setTextSize(2);
M5.Display.setCursor(10, 10);
M5.Display.println("Hello, M5StickS3!");
}
void loop() {
M5.update();
if (M5.BtnA.wasPressed()) {
M5.Speaker.tone(440, 100); // 440 Hz beep on button A
M5.Display.printf("BtnA pressed\n");
}
delay(10);
}
Upload, see “Hello, M5StickS3!” on screen. Press Button A — beep + log message.
3. PlatformIO (the recommended default)
Setup:
- CLI:
pip install -U platformio - IDE: VS Code + PlatformIO extension
Project skeleton for M5StickS3:
platformio.ini:
[env:m5sticks3]
platform = espressif32@6.7.0
board = esp32-s3-devkitc-1 ; Or "m5stick-s3" if M5Stack publishes
framework = arduino
upload_speed = 1500000
monitor_speed = 115200
board_build.partitions = default_8MB.csv
board_build.arduino.memory_type = qio_opi ; ← MANDATORY: OPI PSRAM on PICO-1-N8R8
build_flags =
-DBOARD_HAS_PSRAM
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
-DESP32S3
-DCORE_DEBUG_LEVEL=3
lib_deps =
https://github.com/m5stack/M5Unified.git
https://github.com/m5stack/M5GFX.git
src/main.cpp:
#include <Arduino.h>
#include <M5Unified.h>
void setup() {
auto cfg = M5.config();
M5.begin(cfg);
M5.Display.setTextSize(2);
M5.Display.setCursor(10, 10);
M5.Display.println("Hello PlatformIO");
}
void loop() {
M5.update();
delay(100);
}
Build + flash:
cd ~/m5sticks3-project
pio run -e m5sticks3 # Compile
pio run -e m5sticks3 -t upload # Compile + flash
pio device monitor # Serial monitor
Why PlatformIO over Arduino IDE for non-trivial projects:
- Dependency pinning —
lib_depslocks library versions - Multi-environment —
[env:m5sticks3]+[env:cardputer-adv]in the sameplatformio.inifor cross-platform builds - Better source tree —
src/+lib/matches how Bruce / Evil-M5Project source trees are laid out - Integrated debugger — JTAG / SWD via Espressif’s USB-JTAG bridge
- CI/CD friendly —
pio runworks in GitHub Actions
For modifying existing community firmwares (Evil-M5Project, Bruce, MicroHydra): they all use PlatformIO. Clone the repo, open in VS Code, build.
4. MicroPython + mpremote
Setup:
- Flash M5Stack-flavored MicroPython to M5StickS3 via M5Burner (UiFlow firmware = underlying MicroPython runtime).
- Install
mpremote:pip install -U mpremote - Access REPL:
mpremote connect /dev/ttyACM0 repl
Hello-world:
# At the REPL:
import M5
M5.begin()
# Display text
Lcd.setFont(Lcd.FONT_DejaVu24)
Lcd.print("Hello MicroPython", 10, 10)
# Read button
btn_state = M5.BtnA.read()
print(f"Button A: {btn_state}")
# Read audio
import audio
mic_buf = bytearray(256)
audio.record(mic_buf, 256)
# Play tone
M5.Speaker.tone(440, 100)
File operations:
# Upload a script
mpremote connect /dev/ttyACM0 cp myapp.py :/myapp.py
# Run a script
mpremote connect /dev/ttyACM0 exec "exec(open('/myapp.py').read())"
# List files
mpremote connect /dev/ttyACM0 ls /
# Delete a file
mpremote connect /dev/ttyACM0 rm /myapp.py
# Soft reset
mpremote connect /dev/ttyACM0 reset
Why MicroPython over Arduino/PlatformIO:
- No compile step — write Python, run immediately. Edit-test loop measured in seconds.
- REPL — interactive exploration of the hardware. Probe sensors, test display, experiment.
- Easier for non-C++ devs — Python syntax is more accessible.
Why NOT MicroPython:
- ~50% slower than compiled C++ for typical workloads. CPU-bound code (audio FFT, packet capture at rate) is materially slower.
- More RAM-hungry (interpreter overhead — but M5StickS3 has 8 MB PSRAM, so headroom is plentiful).
- Smaller library ecosystem than Arduino.
For M5StickS3: MicroPython is excellent for scripting + learning + custom MicroHydra apps + audio prototypes that benefit from REPL iteration.
5. UiFlow 2 (block coding)
Setup:
- Flash UiFlow 2 firmware to M5StickS3 via M5Burner.
- Open https://flow.m5stack.com in browser. Pair via USB-CDC (Web Serial).
- Drag blocks. Compile to MicroPython under the hood. Run.
Block categories:
- M5StickS3 blocks: display, buttons, IMU, IR, mic, speaker
- M5 Units / HATs / Caps: drag-and-drop blocks for every M5Stack peripheral with auto-detected pin assignments
- Logic blocks: if/else, loops, conditions
- Math blocks: arithmetic, comparison
- Variables blocks
- Functions blocks
- Audio blocks: recording, playback, FFT (the M5StickS3-distinctive blocks)
- Wi-Fi / BLE blocks
Why UiFlow over MicroPython / Arduino:
- Lowest barrier of any path. Education, kids, non-coders, very rapid prototyping.
- Visual debugging — see program flow as blocks light up during execution.
Power users typically graduate to MicroPython (via mpremote) once they hit UiFlow’s expressiveness ceiling — the underlying MicroPython is the natural next step (UiFlow generates MicroPython, so the transition is gradual).
6. ESP-IDF (low-level)
Setup:
# Clone ESP-IDF
git clone -b master --recursive https://github.com/espressif/esp-idf.git ~/esp-idf
cd ~/esp-idf
# Install toolchain for ESP32-S3
./install.sh esp32s3
# Source the environment (every shell session)
. ./export.sh
Hello-world project:
cd ~/projects
idf.py create-project m5sticks3_hello
cd m5sticks3_hello
idf.py set-target esp32s3
idf.py menuconfig # Configure: USB CDC on boot, flash 8 MB, etc.
idf.py build
idf.py -p /dev/ttyACM0 -b 1500000 flash monitor
Why ESP-IDF over Arduino + PlatformIO:
- Custom partition tables — Arduino’s partition options are predefined; ESP-IDF lets you build any layout
- Native FreeRTOS — direct task scheduling, custom IPC, priority management
- Audio framework
esp-adf— full integration with Espressif’s audio framework for advanced audio work esp-skainetintegration — wake-word + speech-command recognition; ESP-IDF is the cleanest path- Secure boot v2 — Arduino doesn’t expose the full toolchain
- Flash encryption — full ESP-IDF support
- 802.11 frame injection — works in Arduino too, but ESP-IDF has more reliable APIs
Why NOT ESP-IDF:
- Steeper learning curve
- More boilerplate per project
- Smaller M5Stack-specific community than Arduino + PlatformIO
For M5StickS3: reach for ESP-IDF when audio work needs esp_codec_dev direct access, when implementing wake-word detection via esp-skainet, or when custom partition / secure boot is required.
7. M5Unified library reference for M5StickS3
Key APIs from M5Unified specifically relevant to M5StickS3:
#include <M5Unified.h>
void setup() {
auto cfg = M5.config();
M5.begin(cfg);
// M5StickS3 is auto-detected via M5Unified runtime board-detection
// Display, mic, speaker, IMU, IR all initialized
}
void loop() {
M5.update(); // Polls buttons, IMU — call every loop iteration
// Display (M5GFX-derived)
M5.Display.setTextColor(WHITE, BLACK);
M5.Display.setTextSize(2);
M5.Display.setCursor(10, 20);
M5.Display.printf("Hello %d", millis());
M5.Display.drawLine(0, 0, 100, 100, RED);
// Buttons
if (M5.BtnA.wasPressed()) { /* Button A */ }
if (M5.BtnA.isHeld()) { /* Button A held */ }
if (M5.BtnB.wasPressed()) { /* Button B */ }
if (M5.BtnPWR.wasReleased()) { /* Power button */ }
// Audio — Mic
int16_t mic_buf[256];
M5.Mic.record(mic_buf, 256); // 16 kHz mono 16-bit default
// Audio — Speaker
M5.Speaker.tone(440, 100); // 440 Hz tone for 100 ms
M5.Speaker.playRaw(audio_buf, length); // Play raw PCM audio
M5.Speaker.setVolume(180); // 0-255 volume
// IMU (BMI270 or MPU6886 — M5Unified handles both)
auto accel = M5.Imu.getAccelData();
float ax = accel.x, ay = accel.y, az = accel.z;
auto gyro = M5.Imu.getGyroData();
// Battery
int batt_level = M5.Power.getBatteryLevel(); // 0-100
float batt_voltage = M5.Power.getBatteryVoltage();
// IR — use M5Unified IR helpers or IRremoteESP8266 library
}
M5Unified runtime board detection: M5Unified inspects the device at boot to determine if it’s a Cardputer ADV, M5StickS3, M5StickC Plus 2, etc. The same code can run on multiple M5Stack devices without source modification — M5Unified handles the pin/peripheral mapping internally.
Display-specific notes:
- M5StickS3’s ST7789P3 controller is slightly different from Cardputer ADV’s ST7789V2, but M5Unified handles both transparently. Application code is identical.
- Default rotation is portrait (135 wide × 240 tall). For landscape:
M5.Display.setRotation(1).
Audio-specific notes:
- ES8311 codec is initialized by
M5.begin(cfg)— applications don’t need to handle codec init manually. - For lower-level audio control (full-duplex mic+speaker, custom sample rates), use
esp_codec_devdirectly (Vol 5 § 3).
IMU-specific notes:
M5.Imu.getType()returns the detected IMU chip type (m5::imu_bmi270_torm5::imu_mpu6886_t).- Both have similar API surfaces; if accessing chip-specific features (BMI270’s on-chip step counter), check the type first.
8. Build flag essentials (OPI PSRAM is mandatory)
The OPI PSRAM build flag is the most-commonly-missed M5StickS3 setting:
board_build.arduino.memory_type = qio_opi
This translates to ESP-IDF as CONFIG_SPIRAM_MODE_OCT=y. Without it:
ESP.getFreePsram()returns 0- Any firmware that assumes PSRAM availability degrades silently (smaller buffers, fewer features)
- Some firmwares (esp-skainet wake-word, large audio recording) fail entirely
Critical: every M5StickS3 build must include this flag. PlatformIO’s platformio.ini has the directive; Arduino IDE has it under Tools → PSRAM → “OPI PSRAM”.
Other M5StickS3-specific build flags:
| Flag | Purpose | Required |
|---|---|---|
board_build.arduino.memory_type = qio_opi | OPI PSRAM | Yes — mandatory |
-DBOARD_HAS_PSRAM | Standard PSRAM identifier | Yes |
-DARDUINO_USB_CDC_ON_BOOT=1 | Native USB-CDC at boot | Yes for serial console |
-DARDUINO_USB_MODE=1 | Device mode (1) or Host OTG (0) | 1 for typical apps; 0 for USB-host work |
-DESP32S3 | Implicit but explicit for clarity | Optional |
-DCORE_DEBUG_LEVEL=N | Serial debug verbosity (0=none, 5=verbose) | 0 production, 3 dev |
| Display flags (TFT_*) | If using TFT_eSPI directly | Optional — M5GFX wraps |
9. When to use which environment
| Use case | Environment | Why |
|---|---|---|
| Learning / education | UiFlow 2 → MicroPython | Lowest barrier, visual feedback |
| Quick test / one-off sketch | Arduino IDE | Compile + flash in one click |
| Rapid scripting prototype | MicroPython + mpremote | REPL iteration |
| Multi-file production project | PlatformIO | Reproducible builds |
| Modifying community firmware (Evil-M5Project, Bruce, MicroHydra) | PlatformIO | All use PlatformIO |
| Custom partition table | PlatformIO or ESP-IDF | Arduino has predefined options only |
| Wake-word detection (esp-skainet) | ESP-IDF | Cleanest path |
| Custom audio (esp_codec_dev direct) | ESP-IDF | Full control |
| 802.11 frame injection | Arduino + PlatformIO | esp_wifi_set_promiscuous works |
| Secure boot v2 | ESP-IDF | Required toolchain access |
| BadUSB DuckyScript runner | PlatformIO + custom firmware | Compile a BadCard-class firmware |
| Voice memo recorder | PlatformIO + Arduino | M5Unified APIs sufficient |
The recommended default for tjscientist: Arduino IDE for first-time learning + PlatformIO for serious development.
10. Sketch / project skeletons
Three canonical project skeletons (would live in ../../04-templates/ once authored):
Arduino single-file .ino (one-off sketches):
#include <M5Unified.h>
void setup() {
auto cfg = M5.config();
M5.begin(cfg);
M5.Display.println("Hello");
}
void loop() {
M5.update();
// ... your code ...
delay(10);
}
PlatformIO multi-file project:
m5sticks3-project/
├── platformio.ini
├── src/
│ ├── main.cpp
│ └── my_module.cpp
├── include/
│ └── my_module.h
└── lib/ ; auto-fetched via lib_deps
MicroHydra app (.py on SD-via-Hat2 or internal flash at /apps/<AppName>/__init__.py):
from lib.hydra.app import App
from lib.display import Display
class HelloApp(App):
name = "HelloWorld"
icon = "rocket"
def main(self):
d = Display()
d.text("Hello", 10, 10)
while not self.exit_pressed():
self.tick()
11. Resources
Tools / IDEs
- Arduino IDE: https://www.arduino.cc/en/software
- PlatformIO: https://platformio.org/
- VS Code: https://code.visualstudio.com/
- ESP-IDF: https://github.com/espressif/esp-idf
- mpremote: https://docs.micropython.org/en/latest/reference/mpremote.html
M5Stack libraries
- M5Unified: https://github.com/m5stack/M5Unified
- M5GFX: https://github.com/m5stack/M5GFX
Espressif docs
- ESP32-S3 datasheet: https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf
- ESP-IDF programming guide: https://docs.espressif.com/projects/esp-idf/
- Arduino-ESP32 reference: https://docs.espressif.com/projects/arduino-esp32/
MicroPython
- MicroPython for ESP32: https://micropython.org/download/?port=esp32
- M5Stack MicroPython (UiFlow base): https://github.com/m5stack/UIFlow-Code
UiFlow 2
Cross-references
- Cardputer ADV Vol 7 (broader programming-environment coverage):
../../../M5Stack Cardputer ADV/03-outputs/Cardputer_ADV_Complete.html - Custom firmware development: Vol 10
- Audio APIs deep dive: Vol 5 § 3
This is Volume 7 of a twelve-volume series. Next: Vol 8 covers flashing and updating firmware — web flashers + M5Burner + esptool.py + native USB-CDC discovery + OTA + recovery.