Specimen Report · Elixir

nerves-fun-lab

phiat/nerves-fun-lab

conway's game of life in elixir on an ESP32-C3 - atomvm, a 72×40 pixel OLED, pure elixir on bare metal

Stars
★ 1
Forks
⑂ 0
Language
Elixir
Size
106 kB
Last Push
2mo ago
Forged
4mo ago
atomvmconways-game-of-lifeelixirembeddedesp32nervesoled
# JagCanal Elixir running on ESP32-C3 via AtomVM - exploring embedded Elixir development with Conway's Game of Life on a 0.42" OLED display. ![ESP32-C3 Supermini with OLED](https://www.espboards.dev/esp32/esp32-c3-oled-042/esp32-c3-042-oled-side.jpg) ## Features - **Conway's Game of Life** running at 2-8 FPS on ESP32-C3 @ 160MHz - **Pure Elixir I2C driver** for SSD1306 OLED (72×40 pixels) - **Integer-encoded positions** for 2x performance improvement - **Adaptive frame delay** for smooth animation (targets 8.3 FPS) - **Performance instrumentation** with real-time metrics via serial ## Quick Start ```bash # 1. Activate OTP-27 (required for AtomVM) eval "$(mise activate bash)" # 2. Install dependencies mix deps.get # 3. Flash AtomVM firmware (one-time) - use web flasher: # https://petermm.github.io/atomvm-web-tools/ # 4. Build and flash your app mix flash # 5. Monitor serial output minicom -D /dev/ttyACM1 -b 115200 ``` > **WSL Users**: Run `usbipd attach --wsl --busid <BUSID>` in PowerShell first. > See [docs/networking.md](docs/networking.md) for WSL USB setup. ## Hardware - **Board**: ESP32-C3 Supermini with integrated 0.42" OLED - **MCU**: ESP32-C3 @ 160MHz (single-core RISC-V) - **RAM**: ~320KB usable - **Display**: SSD1306 72×40 pixels, I2C @ 400kHz - **LED**: Built-in on GPIO 8 ### Pinout | Function | GPIO | |----------|------| | OLED SDA | 5 | | OLED SCL | 6 | | Built-in LED | 8 | | I2C Address | 0x3C | ## Performance **Current Performance** (32×16 resolution, integer encoding): | Cells | step() | render | flush | total | **FPS** | |-------|--------|--------|-------|-------|---------| | 18 | 41ms | 7ms | 45ms | 93ms | **8.3** | | 37 | 111ms | 18ms | 53ms | 182ms | **5.5** | | 75 | 323ms | 48ms | 57ms | 428ms | **2.3** | **Optimizations Applied:** - Integer position encoding (`x + y*32` vs `{x,y}` tuples): **2x speedup** - Map-based neighbor counting (vs merge sort): **2x speedup** - Adaptive frame delay: **Smooth 8.3 FPS** when population < 30 cells - Reduced resolution (32×16 vs 36×20): Less computation overhead **Bottleneck:** `step()` is 65% of frame time (neighbor counting + rule application) See [docs/oled.md](docs/oled.md) for detailed performance analysis. ## Development ### Prerequisites **1. Erlang/Elixir with OTP-27** AtomVM v0.6.6 requires OTP-27 (not OTP-28). Use mise to manage versions: ```bash mise use erlang@27.3.4.6 elixir@1.19.5-otp-27 eval "$(mise activate bash)" ``` **2. esptool** ```bash uv tool install esptool ``` **3. WSL USB Passthrough (Windows only)** ```powershell # In Windows PowerShell (admin): usbipd bind --busid <BUSID> usbipd attach --wsl --busid <BUSID> ``` ### Build & Flash ```bash # Ensure OTP-27 is active (CRITICAL for AtomVM!) eval "$(mise activate bash)" mise use erlang@27 # Clean build and flash mix clean && mix flash # Or manual steps: mix compile mix atomvm.packbeam esptool --chip esp32c3 --port /dev/ttyACM1 --baud 921600 \ write-flash 0x250000 jag_canal.avm ``` ### Performance Testing **Capture Real Device Metrics:** ```bash # Capture serial output with timing data minicom -b 115200 -D /dev/ttyACM1 -C perf_output.txt -o # Parse and analyze (after 30+ seconds of capture) ./scripts/parse_device_perf.exs perf_output.txt ``` **⚠️ Important:** Do NOT use `mix test` for performance benchmarks - it runs on the host CPU (x86) and gives misleading results. Always use real device metrics from serial output. ### Serial Debugging ```bash # Using minicom (recommended for capture) minicom -b 115200 -D /dev/ttyACM1 -C output.txt -o # Simple output (Ctrl-C to exit) timeout 30 cat /dev/ttyACM1 # Using screen screen /dev/ttyACM1 115200 ``` **⚠️ Never use `stty`** - minicom handles all serial setup automatically. Press RESET button on the board to see boot messages. ## Project Structure ``` jag-canal/ ├── lib/ │ ├── jag_canal.ex # Main application entry point │ ├── game_of_life.ex # Conway's Game of Life (32×16, integer encoding) │ ├── ssd1306.ex # Pure Elixir I2C driver for SSD1306 OLED │ └── mix/tasks/ │ ├── flash.ex # Mix task: flash to device │ └── setup.ex # Mix task: setup script ├── scripts/ │ ├── parse_device_perf.exs # Parse ESP32-C3 serial metrics │ └── analyze_perf.exs # CSV performance analysis ├── docs/ │ ├── oled.md # Display specs & performance analysis │ ├── networking.md # ESP32-C3 networking setup │ ├── flashing.md # Flashing guide │ ├── game-of-life-optimization-research.md │ ├── gol-32x16-optimizations.md │ ├── gol-hotloop.md # Hot loop optimization notes │ └── gol-nif.md # NIF optimization research ├── AGENTS.md # Claude Code agent instructions ├── mix.exs # Project config with AtomVM settings └── mise.toml # Erlang/Elixir version pinning (OTP-27!) ``` ## AtomVM Development Notes ### ⚠️ ALWAYS Verify with atomvm-guru Many Elixir stdlib functions crash **silently** on AtomVM: - ❌ `Enum.sort`, `Enum.take`, `:lists.sort`, `:lists.sublist`, `:lists.member` - ✅ Use manual implementations or `:maps` module functions **Before flashing new code**, always verify AtomVM compatibility: ```bash # Use atomvm-guru agent in Claude Code # Or manually check: https://doc.atomvm.org/main/apiguide.html ``` ### Performance Characteristics - **AtomVM interpreter overhead**: ~100-1000 cycles per operation - **Prefer Map operations** (C implementation) over recursive sorts - **Each function call** has pattern matching + dispatch overhead - **:counters module** is NOT available (no mutable arrays) - **Process dictionary** works but use sparingly ## Game of Life Implementation **Current Algorithm:** 1. **Integer position encoding**: Each cell is `x + y*32` (single integer) 2. **Map-based neighbor counting**: Direct counting (no sorting) 3. **Precomputed wrapping**: Inline modulo for edge wrap 4. **Adaptive frame delay**: Targets 120ms per frame (8.3 FPS) **Key Code Locations:** - `lib/game_of_life.ex:64-124` - Core `step()` algorithm - `lib/game_of_life.ex:145-195` - Main game loop with timing - `lib/ssd1306.ex:288-307` - Fast 2×2 block rendering ## Troubleshooting ### "Code compiled with OTP-28 is not supported" **CRITICAL:** AtomVM v0.6.6 only supports OTP-27. ```bash eval "$(mise activate bash)" mise use erlang@27 mix clean && mix flash ``` ### Silent crashes / No serial output Likely using unsupported stdlib function. Check: 1. Are you using `Enum.sort`, `Enum.take`, `:lists.sort`? 2. Did you verify with atomvm-guru agent? 3. Press RESET button to see boot messages ### "Failed to connect to ESP32-C3" - Use **direct USB connection** (not through a hub) - Re-attach USB in WSL: `usbipd attach --wsl --busid <BUSID>` - Try `/dev/ttyACM1` instead of `/dev/ttyACM0` - Check with `ls -la /dev/ttyACM*` ### Performance is slow or choppy 1. Capture real metrics: `minicom -b 115200 -D /dev/ttyACM1 -C output.txt -o` 2. Analyze: `./scripts/parse_device_perf.exs output.txt` 3. Check if step() time varies widely (population-dependent) 4. See remaining optimization tasks: `bd ready` (beads issue tracker) ## Resources - [AtomVM Documentation](https://doc.atomvm.org/) - [AtomVM GitHub](https://github.com/atomvm/AtomVM) - [ExAtomVM](https://github.com/atomvm/exatomvm) - [ESP32-C3 Supermini Pinout](https://www.espboards.dev/esp32/esp32-c3-oled-042/) - [SSD1306 Datasheet](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf) ## Contributing This project uses **beads (bd)** for issue tracking: ```bash bd ready # Find available work bd show <id> # View issue details bd update <id> --status in_progress # Claim work bd close <id> # Complete work ``` See [AGENTS.md](AGENTS.md) for Claude Code agent instructions and project-specific notes. ## License MIT
↗ GitHub