P72 proved Erlang could call chip-local platform NIFs. P73 uses that bridge for the thing we wanted next: AtomVM renders frames into chip memory and the host viewer turns those bytes into an animation.
Headline: Erlang now renders eight 128x96 RGB565 frames on the chip, pulses the frame-ready bridge, and produces host-viewable framebuffer artifacts.
AtomVM on P73 (framebuffer graphics demo)
Starting AtomVM revision 0.8.0-dev+git.b7503e6
Found startup beam: graphics.beam
P73 AtomVM framebuffer starting
framebuffer base: 15728640
rendered frames: 8
render delta ms: 588
P73 AtomVM framebuffer PASS
Return value: ok
AtomVM exited result=0
What changed
P73 adds one practical graphics NIF on top of the P72 chip I/O surface:
| NIF | effect |
|---|---|
chip:hline/4 | writes a horizontal RGB565 span into the framebuffer |
The Erlang renderer in erlang/graphics.erl computes the scene one
scanline at a time. The C NIF performs the tight memory write, then
Erlang pulses chip:frame_ready/1 after each finished frame. The host
harness dumps eight 24576 byte frame files from the scratch
framebuffer window at 0x00f00000.
The frame content is intentionally simple: a gradient background, a moving sprite, and a progress meter. The important part is ownership: the image is decided by AtomVM-running Erlang, not by a C framebuffer program.
Verification
cd projects/73_atomvm_framebuffer/test
make all
make verilator-run-fb
uv run --with pillow ../viewer.py --gif captured/atomvm-framebuffer.gif captured/frames 128 96 --scale 4 --fps 8
For live viewing while Verilator is running:
cd projects/73_atomvm_framebuffer/test
make view-live
That starts a pygame window first, clears captured/frames/, then
runs the Verilator harness. Each chip:frame_ready/1 pulse writes a
new frame file, and the viewer displays it immediately. The live viewer
also supports pause, frame scrubbing, follow-latest mode, and PNG
snapshots.
Harness result:
[harness] frame 0 dumped (24576 bytes)
[harness] frame 1 dumped (24576 bytes)
[harness] frame 2 dumped (24576 bytes)
[harness] frame 3 dumped (24576 bytes)
[harness] frame 4 dumped (24576 bytes)
[harness] frame 5 dumped (24576 bytes)
[harness] frame 6 dumped (24576 bytes)
[harness] frame 7 dumped (24576 bytes)
[harness] === run ended after 24288424 post-load cycles ===
[harness] halted=1, halt_code=0x00000001
The viewer wrote atomvm-framebuffer.gif and
atomvm-framebuffer-final.png under test/captured/.
What is not proven
F/D architectural compliance was NOT RUN. LibreLane hardening was
NOT RUN. The framebuffer still uses a demo scratch window at
0x00f00000; if this becomes a reusable graphics API, the memory map
should graduate from convention to documented platform contract.
What just happened?
The framebuffer path now belongs to AtomVM. Erlang computes a frame, the chip writes it into RAM, Verilator dumps it when Erlang pulses frame-ready, and the host viewer turns the dumps into an animation.