The Neo6502 API
Everything the 65C02 asks of the RP2040 — screen, sound, files, keyboard, clock — goes through a small mailbox at $FF00. Here is how to use it, function by function, for the official firmware and for Trinity, with examples that were run and verified.
Download the specification (YAML)
Two processors, one mailbox
Olimex's Neo6502 pairs a real W65C02S, which runs the programs, with an RP2040, which does everything else: DVI video, sound, USB keyboard and mouse, files on a USB stick, clock. The 65C02 has 64 KB of RAM; to get a service from the RP2040, it writes a request into a 16-byte area at $FF00, the mailbox. The RP2040 firmware reads it, performs the service, then leaves the answer there.
| Address | Meaning |
|---|---|
$FF00 | function group; writing it last starts the call; the firmware sets it back to 0 when done |
$FF01 | function number within the group |
$FF02 | error: 0 if the call succeeded |
$FF03 | status: bit 7 is set when Escape is pressed |
$FF04–$FF0B | parameters P0 to P7, in and out |
A call, in four steps
- Wait until
$FF00is 0 (the mailbox is free). - Write the parameters at
$FF04onwards. - Write the function at
$FF01, then the group at$FF00: the call starts. - Wait until
$FF00is back to 0, then read$FF02(error) and the results.
This is exactly what the kernel routine WaitMessage ($FFF4), the llvm-mos C library and the Mad Pascal neo6502 unit do. The kernel also offers a few ready-made entry points: WriteCharacter ($FFF1), ReadCharacter ($FFEE), ReadLine ($FFEB), SendMessage ($FFF7)… Trinity adds tasks and semaphores ($FFC1 to $FFDC).
Conventions
- 16 and 32-bit numbers are stored low byte first (P0, then P1…).
- A string is length-prefixed (one byte), with no final zero; an address parameter gives its location in 65C02 RAM.
- When the firmware returns a string into a buffer, the first byte of the buffer must hold, before the call, the maximum length accepted; the firmware then writes the actual length there (checked in
DSPSetStdString). - A failing call puts a non-zero value at
$FF02.
In each language
- C (llvm-mos):
#include <neo/api.h>gives ready-made functions (neo_system_timer()…); for the others, theControlPortstructure ofneo6502.hgives direct access to the mailbox. Build:mos-neo6502-clang -Os -o prog.neo prog.c. - Mad Pascal: the
neo6502unit provides routines (NeoGetTimer…),NeoSendMessage(group, function)andNeoWaitMessage. Build:mp prog.pas -target:neo, then the MADS assembler.writelnitself goes through the mailbox: readNeoMessage.erroror a result into a variable before printing it. - NeoBASIC: about fifty functions have a keyword (
time(),key(),locale…). For the others, write to the mailbox withpoke. Beware: NeoBASIC itself uses the mailbox, for instance forprint. Write parameters, function and group on one line, and read the results into variables right after the wait, before any output and any computation — otherwise you read the answer of another call.*,/and the maths functions also go through the mailbox (group 4): inpeek($ff04) + 256 * peek($ff05) + 65536 * peek($ff06), the lastpeekreads a value already overwritten. - 65C02 assembly: write directly to
$FF00–$FF0Band callWaitMessage; the examples assemble with 64tass and load at$0800.
Official firmware and Trinity
The official firmware is Paul Robson's; Trinity is the collective's alternative firmware, built on it. Each function carries a label: Official (identical in both), Changed by Trinity or Trinity (only exists in Trinity). Function 1,11 gives the running firmware version.
How the examples are verified
Every published example was compiled then run in the Phosphoneo emulator, with the Trinity firmware. A test bench checks in the call log that the program does call the documented function, without error, then compares what it prints with what is expected; the observed output is shown under each example, except when it depends on hardware plugged into the board (UEXT I/O): the emulator only returns dummy values there. An example that did not pass this bench is not published. Code comments and messages in the examples are in French.
What we do not claim: the examples run under Trinity; those of the official functions were not replayed on the official firmware. On the board, a few functions behave differently than in the emulator (serial port, modem, flash): their entry says so.
The groups
| Group | Name | Origin | Functions | verified examples |
|---|---|---|---|---|
| Group 1 | System | Official | 25 functions | 97 |
| Group 2 | Console | Official | 24 functions | 96 |
| Group 3 | File I/O | Official | 30 functions | 120 |
| Group 4 | Mathematics | Official | 27 functions | 108 |
| Group 5 | Graphics | Official | 27 functions | 108 |
| Group 6 | Sprites | Official | 5 functions | 20 |
| Group 7 | Controller | Official | 3 functions | 12 |
| Group 8 | Sound | Official | 16 functions | 64 |
| Group 9 | Turtle Graphics | Official | 6 functions | 24 |
| Group 10 | UExt I/O | Official | 19 functions | 76 |
| Group 11 | Mouse | Official | 5 functions | 20 |
| Group 12 | Blitter | Official | 4 functions | 16 |
| Group 13 | Editor | Official | 2 functions | 8 |
| Group 14 | USB Serial (CDC) | Trinity | 6 functions | 24 |
| Group 32 | QuickDraw | Trinity | 19 functions | 76 |
| Group 33 | Event Manager | Trinity | 6 functions | 24 |
| Group 34 | Window Manager | Trinity | 12 functions | 48 |
| Group 35 | Menu Manager | Trinity | 10 functions | 40 |
| Group 36 | Control Manager | Trinity | 10 functions | 40 |
| Group 37 | Dialog Manager | Trinity | 6 functions | 24 |
| Group 38 | Resource Manager | Trinity | 8 functions | 32 |