Skip to content

TheRealHaoLiu/cardputer-adv

Repository files navigation

Cardputer ADV MicroPython Demos

A learning-focused project for the M5Stack Cardputer ADV. Every file is heavily commented to teach MicroPython and embedded development concepts.

Learning Approach

The code IS the documentation. Each file contains extensive comments explaining:

  • What each section does and WHY
  • Common patterns and best practices
  • API references and quick lookups
  • Gotchas and important notes

Start with apps/hello_world.py - it's the template with the most detailed explanations.

Hardware

M5Stack Cardputer ADV - ESP32-S3 with built-in 240x135 LCD and keyboard.

Firmware

Uses a custom MicroPython firmware based on uiflow-micropython.

Custom firmware repo: https://github.com/TheRealHaoLiu/cardputer-adv-micropython (branch: custom-firmware)

Local convention: Clone alongside this repo at ../cardputer-adv-micropython

Why custom firmware?

  • Based on M5Stack's UIFlow2 with hardware libraries (Lcd, Widgets, Speaker, etc.)
  • Customizations specific to this project's needs
  • Hardware abstraction for Cardputer ADV's display, keyboard, speaker

Available modules: See MODULES.md in the firmware repo for the full list of available imports including M5, hardware drivers, sensors, networking, and unit classes.

Firmware version: Check in Settings > About, or programmatically:

import firmware_info
print(firmware_info.CUSTOM_VERSION)   # "2.4.1+therealhaoliu.1"
print(firmware_info.UPSTREAM_VERSION) # "2.4.1"

Initial Setup

1. Flash the custom firmware

Clone the firmware repo alongside this one:

# From parent directory of cardputer-adv
git clone -b custom-firmware https://github.com/TheRealHaoLiu/cardputer-adv-micropython
# Follow build instructions in that repo

Flash to device using esptool or the build system's flash target.

2. USB Connection Note

If mpremote can't connect, try: turn off the device, then plug in USB to power it on. Not sure why this helps, but it does.

Development Setup

# Install dependencies
uv sync

# Install pre-commit hooks (required for contributors)
uv run pre-commit install

This installs git hooks that run gitleaks (secret scanning) and ruff (linting/formatting) on every commit.

Development Workflow

Running Apps

Run the launcher (menu to select apps):

uv run poe run

Run a specific app directly (skip the menu):

uv run poe run apps/hello_world.py
uv run poe run apps/demo_anim.py
uv run poe run apps/notepad.py
# etc.

Each app has standalone support via if __name__ == "__main__" block, so you can test individual apps without going through the launcher menu.

How It Works

poe run mounts your local directory to /remote/ on the device and executes the file. All files are loaded from your local machine - including lib/ (framework.py, app_base.py) and apps/. Your local edits are immediately available - no copying needed!

  • poe run (no args) → runs main.py → shows launcher menu
  • poe run apps/foo.py → runs that app directly with hardware init

Hot-reloading: In remote mode, press 'r' in the launcher to reload all app modules. ESC now returns instantly without reloading.

Press ESC to exit any app and return to the launcher (or REPL if running standalone).

Deploy to Flash (Standalone)

uv run poe deploy

Copies all files to device flash. The device runs independently after this - no computer needed.

WARNING: Deploy replaces all of the following on the device:

  • /flash/main.py
  • /flash/lib/* (framework.py, app_base.py, etc.)
  • /flash/apps/* (all app files)

Any changes made directly on the device will be lost!

Direct REPL Access

uv run mpremote connect /dev/tty.usbmodem* repl

For interactive debugging. Exit with Ctrl+].

All Available Tasks

uv run poe --help           # List all tasks
uv run poe run [file]       # Run file (default: main.py)
uv run poe deploy           # Copy files to flash
uv run poe ls [path]        # List files on device
uv run poe cat <path>       # Show file contents from device
uv run poe reset            # Reset device
uv run poe firmware-download [version]  # Download firmware (default: pyproject.toml version)
uv run poe lint             # Check code for errors
uv run poe format           # Format code with ruff

Available Apps/Demos

Each demo teaches specific concepts. Run them with uv run poe run apps/<name>.py.

App Concepts Taught
hello_world START HERE - App structure, keyboard callbacks, main loop pattern
notepad Text editing, cursor management, incremental screen updates
demo_anim Double buffering - The key to smooth animation (canvas vs direct draw)
demo_text Fonts, colors (Lcd.COLOR.*), text alignment, scrolling marquee
demo_lcd Shape drawing, brightness control, QR codes, display info
demo_keyboard Key events, modifier detection, FN combinations, matrix layout
demo_sound Tones, musical notes, volume control, sound effects
demo_widgets High-level UI components (Label), text alignment, UI patterns
demo_nvs Persistent storage, saving settings, understanding boot options

Creating New Apps

  1. Copy apps/hello_world.py to your new app file
  2. Rename the class (must inherit from AppBase)
  3. Update the if __name__ == "__main__" block to use your class
  4. Register in manifest.json - add your app to the appropriate manifest:
// apps/manifest.json (top-level menu)
{
    "hello_world": "Hello World",
    "my_app": "My App Name"
}

// apps/demo/manifest.json (Demo submenu)
{
    "sound_demo": "Sound Demo",
    ...
}

Directory structure = menu hierarchy:

  • apps/*.py + apps/manifest.json → top-level menu items
  • apps/demo/*.py + apps/demo/manifest.json → Demo/ submenu
  • Create new subdirectories with manifest.json for more submenus

Hot-reload in dev mode: Press 'r' in the launcher to reload all apps.

See apps/hello_world.py for the required structure and keyboard handling patterns.

Key Concepts Quick Reference

The Main Loop Pattern

Every app needs this structure:

while not exit_flag:
    M5.update()      # REQUIRED - processes hardware events
    # Your logic here
    time.sleep(0.02) # Prevent busy-waiting

Keyboard Callback Pattern

Never do slow operations in callbacks! Use flags:

exit_flag = False

def on_key(keyboard):
    nonlocal exit_flag
    while keyboard._keyevents:
        event = keyboard._keyevents.pop(0)
        if event.keycode == 0x1B:  # ESC
            exit_flag = True  # Just set flag, don't do heavy work

kb.set_keyevent_callback(on_key)

Double Buffering (Smooth Animation)

canvas = Lcd.newCanvas(240, 135)  # Create off-screen buffer
canvas.fillScreen(Lcd.COLOR.BLACK)  # Draw to canvas (invisible)
canvas.fillRect(x, y, w, h, color)
canvas.push(0, 0)                   # Copy to screen (atomic, no flicker!)
canvas.delete()                     # Free memory when done

Color Constants (Lcd.COLOR.*)

Use built-in color constants for cleaner code:

Lcd.COLOR.BLACK, Lcd.COLOR.WHITE
Lcd.COLOR.RED, Lcd.COLOR.GREEN, Lcd.COLOR.BLUE
Lcd.COLOR.YELLOW, Lcd.COLOR.MAGENTA, Lcd.COLOR.CYAN
Lcd.COLOR.ORANGE, Lcd.COLOR.PINK, Lcd.COLOR.PURPLE

Troubleshooting

See TROUBLESHOOTING.md for common issues like:

  • mpremote can't connect (launcher blocking)
  • REPL modes (raw vs normal)
  • Boot option values
  • NVS initialization

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •