picocalc-ops

This skill should be used when the user reports PicoCalc issues (boot failures, SD card not mounting, display blank, keyboard not responding), wants to flash firmware, deploy scripts to SD card, debug errors, or manage the PicoCalc device. Activate when the user mentions "PicoCalc not booting", "boot loop", "SD card error", "display not working", "flash firmware", "UF2", "BOOTSEL", "deploy to PicoCalc", "copy to SD card", "PicoCalc error", or "debug PicoCalc".

PicoCalc Operations: Debug and Deploy

Diagnose and fix PicoCalc issues. Deploy firmware and scripts to the device.

Boot Sequence

  1. Power on -> Pico 2W runs boot.py from internal flash
  2. Initialize PicoDisplay(320, 320) and PicoKeyboard()
  3. Setup USB debug output (sys.stdout)
  4. gc.collect() + 500ms delay for stability
  5. SD card init: SPI0 at 1MHz, mount to /sd
  6. VT100 terminal: vt.vt(display, keyboard, sd=sd)
  7. Register on picocalc module: display, keyboard, terminal, sd
  8. os.dupterm(terminal) redirects REPL to LCD+keyboard
  9. boot.py finishes -> REPL becomes available over USB
  10. main.py runs -> py_run.main_menu() launches script selection

IMPORTANT: main_menu() MUST be in main.py, NOT boot.py. If it's in boot.py, the REPL never starts and USB tools (Thonny, mpremote) cannot connect. Ctrl+C can interrupt main.py but not boot.py.

Common Failures

Boot Loop on Battery

Symptom: Device power-cycles repeatedly on battery power. Cause: Keyboard MCU at 0x1F doubles as power management controller. Sending reset register (0x08) during cold boot cuts power. Fix: Never call keyboard.reset() during initialization. Current firmware guards against this.

SD Card Mount Failure

Symptom: OSError during SD init, missing /sd directory, apps not found. Cause: SPI0 timing sensitive at cold start. Fix: Ensure 900ms delay before initsd(), use baudrate=1000000 (not higher), verify GPIO 16/17/18/19. Diagnose: os.listdir('/sd') from USB REPL.

Display Blank

Symptom: Screen dark, but USB REPL responds. Cause: Display refresh runs on Core 1. Flash write without lockout crashes Core 1. Fix: Ensure firmware has multicore_lockout_victim_init() in core1_main().

Keyboard I2C Errors

Symptom: Intermittent OSError during keyboard reads. Cause: I2C bus at 10kHz produces transient errors. Fix: Wrap reads in try/except. Run i2c.scan() to verify 0x1F responds.

Flash Write Crash

Symptom: Hard fault when saving files to internal flash while display running. Cause: Core 1 runs XIP code; flash write locks XIP. Fix: multicore_lockout_victim_init() on Core 1 (included in current firmware).

USB REPL Not Connecting

Symptom: Device works (display, keyboard, menu) but no USB serial port appears on Mac, or Thonny/mpremote can't connect. Causes & Fixes:

  1. No USB serial device at all — Firmware build issue. Check with ls /dev/tty.usbmodem*. If nothing appears:
    • Verify firmware was built with BOARD=RPI_PICO2_W (not RPI_PICO or RPI_PICO2)
    • Verify MicroPython version is pinned to known-good (v1.25.0-preview). MicroPython master can have USB regressions on RP2350.
    • Verify Dockerfile includes lib/cyw43-driver in pico-sdk submodule init
    • Compare .uf2 file sizes: known-good is ~2.1MB. If new build is ~1.7MB, CYW43/WiFi stack is missing
    • Use strings firmware.uf2 | grep BUILTIN_CDC to verify USB CDC is compiled in
  2. Port appears but can't get REPL promptmain_menu() is blocking. Use mpremote resume to connect without soft-reset, or press ESC on PicoCalc keyboard first.
  3. Thonny "raw paste" erroros.dupterm(pc_terminal) keyboard data interferes with Thonny's raw REPL protocol. Ensure main_menu() is in main.py not boot.py. Diagnose: ioreg -p IOUSB | grep -i pico to check if device enumerates at all.

UF2 Comparison

Compare a known-good .uf2 with a new build:

# Size comparison (known-good ~2.1MB for Pico 2W with WiFi)
wc -c firmware.uf2
# Feature check
strings firmware.uf2 | grep -E "BUILTIN_CDC|CYW43|RPI_PICO|MicroPython-|beginDraw"
# Family ID (e48bff57 = RP2350)
xxd -s 28 -l 4 firmware.uf2

Deployment

Dashboard (Recommended)

python3 MicroPython/tools/dashboard.py

Opens a web UI at http://localhost:8265 for visual file management, deploy, diff, edit, and REPL. Auto-installs mpremote if needed.

Connecting to Device via Dashboard API

When the dashboard is running and the PicoCalc is connected via USB, you can execute code on the device directly using the dashboard's REST API. The dashboard port may vary -- check the terminal output when it starts (default 8265).

Execute code on device:

curl -s -X POST http://localhost:8265/api/exec \
  -H 'Content-Type: application/json' \
  -d '{"code": "print(\"hello from PicoCalc\")"}'

Returns JSON: {"rc": 0, "out": "hello from PicoCalc\n", "err": ""}

Check device status:

curl -s http://localhost:8265/api/device

Push a file to device:

curl -s -X POST http://localhost:8265/api/push \
  -H 'Content-Type: application/json' \
  -d '{"file": "sd/py_scripts/PicoKeyboard.py"}'

Key notes:

  • The exec endpoint has a 30-second timeout -- don't use it to launch apps with main loops
  • Large scripts (>1000 lines) may fail via exec due to RAM -- launch from device menu instead
  • The dashboard uses mpremote internally, so only one operation can run at a time
  • Check the dashboard port in the terminal output -- user may run on a non-default port

Flash UF2 Firmware

  1. Power off PicoCalc completely
  2. Hold BOOTSEL on Pico 2W, connect USB cable
  3. Device appears as RPI-RP2 or RP2350 removable drive
  4. Copy MicroPython/firmware/picocalc_micropython_pico2w.uf2 to drive
  5. Device reboots automatically

File Placement

Device /             <- boot.py, main.py
Device /modules/     <- all MicroPython/modules/*.py
Device /sd/py_scripts/ <- all MicroPython/sd/py_scripts/*.py (on SD card)

Deploy via mpremote

# Use 'resume' to avoid re-triggering main_menu on soft reset
mpremote resume cp MicroPython/boot.py :boot.py + cp MicroPython/main.py :main.py
mpremote resume cp MicroPython/modules/picocalc.py :/modules/picocalc.py

SD Card Layout

/sd/
├── py_scripts/     # App scripts (auto-discovered by menu)
├── logs/           # Log files (created by apps)
└── wifi.json       # WiFi credentials (created by brad.py)

USB Serial Debug

Connect USB, open serial at 115200 baud. usb_debug() writes to sys.stdout. Ctrl+C interrupts main.py and drops to REPL.

Additional Resources