M5Stack Cardputer Zero · Volume 7

M5Stack Cardputer Zero Volume 7 — Programming Environments

Arduino + M5Cardputer library, PlatformIO Zero env, MicroPython, UiFlow, ESP-IDF — the developer paths

Contents

SectionTopic
1About this volume
2Arduino IDE + M5Cardputer library
3PlatformIO + Zero-specific env
4MicroPython
5UiFlow visual programming
6ESP-IDF (low-level)
7Common Zero-specific code patterns
8Resources

1. About this volume

Vol 7 covers programming environments for Cardputer Zero. The Cardputer family shares libraries (M5Cardputer, M5Unified) and the Zero is expected to be compatible once a Zero board target is added. Most developer workflows look identical to the ADV; the deltas are:

  • PlatformIO env: a Zero-specific env file is needed (or Zero target in upstream)
  • Pin maps: any code that references EXT pins or audio pins needs handling for Zero
  • Capability checks at compile time: code that uses Cap modules or audio needs conditional compilation

Cross-reference: ../../../M5Stack Cardputer ADV/03-outputs/Cardputer_ADV_Complete.html Vol 8 covers programming environments for the ADV. This volume captures the Zero deltas.


2. Arduino IDE + M5Cardputer library

2.1 Standard setup

1. Install Arduino IDE 2.x (or 1.8.x; both work)
2. Add ESP32 board package via Boards Manager:
   - Boards URL: https://espressif.github.io/arduino-esp32/package_esp32_index.json
   - Install "esp32 by Espressif Systems"
3. Install libraries:
   - M5Cardputer (search in Library Manager)
   - M5Unified (search in Library Manager)
4. Select board: "M5Stack-Cardputer" (until Zero target appears)
5. Set port to detected COM port
6. Compile + upload

2.2 Zero-specific considerations

Until M5Cardputer library includes a Zero target:

  • Pin mapping: code referencing M5.Cardputer pins works on Zero if pins are unchanged (presumed match for keyboard / display)
  • Cap module pins: code that uses Cap pins (EXT bus) will NOT work on Zero
  • Audio: code using M5.Speaker / M5.Microphone may behave differently or not at all
  • IMU: code calling BMI270 functions will fail (no sensor)

For Zero-specific sketches, until the library updates: hand-edit any pins, capability-check at compile time.

2.3 Minimal example

#include "M5Cardputer.h"

void setup() {
    auto cfg = M5.config();
    M5Cardputer.begin(cfg, true);
    M5Cardputer.Display.setTextSize(2);
    M5Cardputer.Display.println("Hello, Cardputer Zero!");
}

void loop() {
    M5Cardputer.update();
    if (M5Cardputer.Keyboard.isChange()) {
        // Read keyboard state
    }
}

This works on Zero if the M5Cardputer abstraction covers basic display + keyboard. Audio/IMU calls would need conditional checks.


3. PlatformIO + Zero-specific env

3.1 Until Zero is in mainline support

Add a Zero-specific env to platformio.ini:

[env:m5stack-cardputer-zero]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino

board_build.mcu = esp32s3
board_build.f_cpu = 240000000L
board_build.flash_size = 8MB
board_build.psram = no

board_upload.flash_size = 8MB
board_upload.maximum_size = 8388608
board_upload.use_1200bps_touch = no
board_upload.wait_for_upload_port = no

build_flags =
    -DBOARD_HAS_PSRAM=0
    -DARDUINO_USB_CDC_ON_BOOT=1
    -DCONFIG_FREERTOS_HZ=1000
    -DM5_CARDPUTER_ZERO  ; custom flag for code differentiation

lib_deps =
    m5stack/M5Cardputer
    m5stack/M5Unified

monitor_speed = 115200
upload_speed = 921600

3.2 Conditional compilation pattern

For code that should work on both ADV and Zero:

#include "M5Cardputer.h"

void doAudioOutput() {
    #if defined(M5_CARDPUTER_ZERO)
        // Zero: PWM-driven speaker only
        speaker_pwm_play(440, 100);
    #else
        // ADV: full audio codec available
        M5.Speaker.tone(440, 100);
    #endif
}

void readIMU() {
    #if defined(M5_CARDPUTER_ZERO)
        // Zero: no internal IMU; fail silently or read Grove IMU
    #else
        // ADV: BMI270 internal
        M5.Imu.update();
    #endif
}

This pattern is essential for libraries that support both variants. Maintain it carefully as Zero-specific code paths land.


4. MicroPython

Standard MicroPython for ESP32-S3 works on Zero unchanged. Flashing follows the standard pattern (Vol 8 § 4 for esptool.py).

4.1 Hello world

from machine import Pin
from time import sleep

# Built-in LED (if Zero has one)
led = Pin(2, Pin.OUT)
while True:
    led.toggle()
    sleep(0.5)

4.2 Display + keyboard via MicroHydra

MicroHydra (github.com/echo-lalia/MicroHydra) provides a high-level Cardputer-aware MicroPython API:

from microhydra import Display, Keyboard

d = Display()
k = Keyboard()

d.clear()
d.print("Press any key:")
while True:
    if k.is_pressed():
        d.print(f"Got: {k.get_char()}")

MicroHydra works on Zero if it adds Zero target; otherwise build from source.


5. UiFlow visual programming

UiFlow (flow.m5stack.com) — Scratch-style visual programming for M5Stack devices. Zero compatibility:

  • Block library: ESP32-S3 family supported
  • Cardputer Zero target: TBD when product ships
  • Drag-and-drop: keyboard input + display output blocks
  • Code generation: outputs Python (UiFlow 2) or block-driven runtime

For non-programmers / classroom use: this is the right entry point.


6. ESP-IDF (low-level)

ESP-IDF (Espressif IoT Development Framework) is the low-level C/C++ framework underneath Arduino-ESP32. For tjscientist’s expected use, Arduino + PlatformIO covers everything; ESP-IDF only matters when you need:

  • Lower-level access to silicon features
  • Larger projects (no Arduino sketch size limit)
  • Real-time scheduling (Arduino is single-thread-ish)
  • BLE GATT server with multiple services (deep integration)
  • Custom partition tables (over-the-air, dual-bank)

Standard ESP-IDF setup applies for Zero (ESP32-S3 target). Cross-ref ESP-IDF docs.


7. Common Zero-specific code patterns

7.1 Detect Zero at runtime (without conditional compilation)

Some firmware might want to detect the device at runtime:

// Hypothetical detection — verify on hardware
bool isCardputerZero() {
    // Method 1: Check for IMU presence
    Wire.beginTransmission(0x68);  // BMI270 default address
    return Wire.endTransmission() != 0;  // True if no IMU = Zero
}

// Method 2: Check ESP-IDF efuse for chip variant
bool isS3FN8() {
    // Read efuse; ESP32-S3 PICO vs S3FN8 may differ
    // (implementation depends on actual chip)
}

7.2 Graceful degradation pattern

void scanForFeatures() {
    bool hasIMU = false;
    bool hasAudioCodec = false;
    bool hasMicrophone = false;

    // Try IMU
    Wire.beginTransmission(0x68);
    hasIMU = (Wire.endTransmission() == 0);

    // Try audio codec
    Wire.beginTransmission(0x18);  // ES8311 default
    hasAudioCodec = (Wire.endTransmission() == 0);

    // Adjust UI / features accordingly
    if (!hasIMU) showMessage("No IMU; using compass workaround");
    if (!hasAudioCodec) disableAudioMenu();
}

This pattern makes a single firmware build work on both ADV and Zero.

7.3 Grove peripheral handling

// Grove I2C scanner
void scanGroveI2C() {
    Wire.setPins(GROVE_SDA, GROVE_SCL);
    Wire.begin();
    for (int addr = 1; addr < 128; addr++) {
        Wire.beginTransmission(addr);
        if (Wire.endTransmission() == 0) {
            Serial.printf("Found Grove device at 0x%02X\n", addr);
        }
    }
}

8. Resources

End of Vol 7. Next: Vol 8 covers firmware flashing workflows — M5Burner, web flashers, esptool.py manual flash, factory backup.