RIA¶
RP6502 Interface Adapter
Introduction¶
The RP6502 Interface Adapter (RIA) is the boundary between a WDC W65C02S microprocessor and the host. A RIA is 32 registers at $FFE0-$FFFF, and it controls RESB and PHI2, the reset and the clock of the 6502. Those are the only hard requirements. What is behind the registers depends on the implementation.
Every call of the API is made through these registers. This page describes the registers themselves, the second 64 KB of memory that they reach, the extended RAM (XRAM), and the devices that use XRAM: keyboard, mouse, tablet and gamepad input, and the PSG and OPL2 sound generators.
Implementations¶
RP6502-PICO — The RIA software fits entirely on a Raspberry Pi Pico 2. Registers are implemented in PIO for connecting to a 65C02.
RP6502-FPGA — The same software on a Hazard3 RISC-V soft CPU. The 65C02, 65C22, and registers are in fabric.
RP6502-EMU — The RIA software runs natively on the host CPU along with a 65C02 and 65C22 software emulator.
RP6502-WEB — The RIA software and the software 65C02 and 65C22, built as WebAssembly and run in a browser.
A Picocomputer always has a companion CPU and operating system. For example, one RP6502-EMU runs on Linux with an ARM processor. The RP6502-PICO is special because it hosts itself. The RIA runs an operating system, and a 6502 program calls it through the API. That operating system is an abstraction on all other hosts, but it is the native operating system on the RP6502-PICO.
One other special feature of the RP6502-PICO is its monitor. Every
load, reset, set, and status command on this page applies
only to an RP6502-PICO. The RP6502-FPGA uses the Pocket’s own menus
instead, the RP6502-EMU takes command-line arguments, and the RP6502-WEB
takes settings in its page.
Reset¶
Think of reset as two states rather than a pulse on RESB. While reset is low, the 6502 is stopped. On an RP6502-PICO, the monitor is connected to the console while in reset. On the Pocket, the system waits for a new ROM to be loaded from the settings menu. On the RP6502-EMU, some hosts wait for a new ROM to load while others exit the host process.
Reset is mostly handled automatically and this works well for all hosts
except the RP6502-PICO. Here we need a way to stop a wedged 6502. The monitor
also provides two commands that will bring reset high.
Either load a ROM that has a reset vector, or use the reset command
if you’ve prepared RAM some other way.
To drop reset from high to low and return to the monitor, even from a
wedged 6502, use any terminal on the
console manifold:
Press Ctrl-Alt-Del from a USB keyboard.
Send a break from a serial terminal.
Send a break from a telnet terminal.
Registers¶
The RIA registers are mapped into 32 bytes of the 6502’s address space at $FFE0-$FFFF. The last six are the 6502’s own vectors; which present as RAM.
Address |
Name |
Description |
|---|---|---|
$FFE0 |
READY |
Flow control for UART FIFO.
|
$FFE1 |
TX |
Write bytes to the UART. |
$FFE2 |
RX |
Read bytes from the UART. |
$FFE3 |
VSYNC |
Increments every 1/60 second when PIX VGA device 1 is connected. |
$FFE4 |
RW0 |
Read or write the Extended RAM (XRAM) referenced by ADDR0. |
$FFE5 |
STEP0 |
Signed byte added to ADDR0 after every access to RW0. |
$FFE6 -
$FFE7
|
ADDR0 |
Address of Extended RAM (XRAM) for RW0. |
$FFE8 |
RW1 |
Read or write the Extended RAM (XRAM) referenced by ADDR1. |
$FFE9 |
STEP1 |
Signed byte added to ADDR1 after every access to RW1. |
$FFEA -
$FFEB
|
ADDR1 |
Address of Extended RAM (XRAM) for RW1. |
$FFEC |
XSTACK |
512 bytes for Extended Stack (XSTACK). |
$FFED |
ERRNO_LO |
Low byte of errno. All errors fit in this byte. |
$FFEE |
ERRNO_HI |
Ensures errno is optionally a 16-bit int. |
$FFEF |
OP |
Write an API op here to begin an API call. |
$FFF0 |
IRQ |
Interrupt enable mask. Reading returns the triggered signals as bits and clears them. Writing sets the enable mask and also clears any triggered signals.
|
$FFF1 |
SPIN |
Always $80 (the BRA opcode). JSR here to spin-wait for an API call. The CPU loops on this BRA until BUSY clears, then falls through to LDA and LDX below. |
$FFF2 |
BUSY |
Bit 7 high while an API call is running. |
$FFF3 |
LDA |
Always $A9 (the LDA immediate opcode). Part of the spin-loop return sequence. |
$FFF4 |
A |
API call register A. |
$FFF5 |
LDX |
Always $A2 (the LDX immediate opcode). Part of the spin-loop return sequence. |
$FFF6 |
X |
API call register X. |
$FFF7 |
RTS |
Always $60 (the RTS opcode). Ends the spin-loop return sequence, returning to the caller with A and X loaded. |
$FFF8 -
$FFF9
|
SREG |
32-bit extension to AX - AXSREG. |
$FFFA -
$FFFB
|
NMIB |
6502 vector. |
$FFFC -
$FFFD
|
RESB |
6502 vector. |
$FFFE -
$FFFF
|
BRK/IRQB |
6502 vector. |
UART¶
A program uses the UART directly through the registers at $FFE0-$FFE2, and the ready flags on bits 6-7 can be tested with the BIT operator. Use these or the stdio functions of the API, but not both at once. Driving the UART directly while a stdio API call is in progress is undefined behavior.
XRAM Portals¶
The two XRAM portals are independent pointers into the same 64 KB of XRAM. Portal 0 consists of the registers RW0, STEP0 and ADDR0, and portal 1 of RW1, STEP1 and ADDR1. A read or write of RW0 accesses the XRAM byte at ADDR0, and then STEP0 is added to ADDR0. Portal 1 works the same way. A single portal would make moving XRAM slow, since data would have to be buffered in 6502 RAM. Ideally you won’t move XRAM at all and can use the pair for smarter optimizations.
STEP0 and STEP1 default to 1 after reset. Both are signed, so negative values step through XRAM in reverse. These auto-increment adders make sequential access very fast — more than enough to offset the slightly slower random access compared to 6502 system RAM.
Assembly programs use these registers directly, with the names from
rp6502.inc. In this example, $12 is written to $1000 and $34 to $1001
through portal 0:
lda #<$1000
sta RIA_ADDR0
lda #>$1000
sta RIA_ADDR0+1
lda #1
sta RIA_STEP0
lda #$12
sta RIA_RW0 ; $1000
lda #$34
sta RIA_RW0 ; $1001
lda #<$1000
sta RIA_ADDR0
lda #>$1000
sta RIA_ADDR0+1
lda #1
sta RIA_STEP0
lda #$12
sta RIA_RW0 ; $1000
lda #$34
sta RIA_RW0 ; $1001
C programs use the xram0_ and xram1_ functions of the API,
such as xram0_read() and xram0_poke16(). The 0 or 1 in a
function name selects the portal. This is the same write in C, with the
value stored low byte first:
xram0_poke16(0x1000, 0x3412);
When an interrupt handler and the main program use the same portal, the ADDR and STEP registers of that portal must be saved when the handler starts and restored before it returns. When the two use different portals, no registers have to be saved.
Extended Stack (XSTACK)¶
This is a 512-byte, top-down, last-in-first-out stack used by the fastcall mechanism of the API. Reading past the end is guaranteed to return zeros. Write to push, read to pull.
Peripheral Information Exchange (PIX)¶
Keyboards, mice and gamepads are connected over USB and Bluetooth, and the video and sound are made by modern hardware. A 6502 cannot use any of these directly, so each one is given a simple interface of bytes in extended RAM (XRAM). The RIA writes keyboard, mouse and gamepad input into XRAM for a program to read, and the sound generators and the video system read settings and data that a program writes into XRAM. This arrangement is called Peripheral Information Exchange (PIX). A program installs each device at an XRAM address by setting an extended register (XREG). Until then, the device is off and has no XRAM address.
Extended RAM (XRAM)¶
XRAM is 64 KB of memory outside the 6502’s address space. A program reads and writes it through the XRAM Portals, and every device uses the same 64 KB.
Extended Registers (XREG)¶
Extended registers are how devices are installed into XRAM. A program picks an XRAM address for a device and writes it to the extended register for that device, and the device registers are then at that address. Setting the keyboard extended register to $1000 puts the 32 keyboard bytes at $1000-$101F. Setting the MODE register of the VGA installs a video mode whose registers are the configuration structure at the given address.
Extended registers are outside the 6502’s address space, so a load or a
store cannot access one, and no extended register can be read back. A C
program sets them with the xreg() API call, which is made
through the RIA registers like every other API call, and an assembly
program with the xreg macro in rp6502.inc. Both take the device,
the channel, the address, and then one or more 16-bit values, which are
set starting at that address.
Each extended register holds a 16-bit value at an address written $device:$channel:register. Device 0 is the RIA, device 1 is the VGA, and devices 2-6 are open for expansion. Each device has 16 channels of 256 registers.
The extended registers of device 0 are in the table below, so every
address begins with $0. Each one maps a device into XRAM at the address
written to it. Writing $FFFF turns the device off and always
succeeds. Any other address that is invalid for the device also turns it
off, and the call fails with EINVAL.
Address |
Name |
Description |
|---|---|---|
$0:0:00 |
KEYBOARD |
See Keyboard section |
$0:0:01 |
MOUSE |
See Mouse section |
$0:0:02 |
GAMEPADS |
See Gamepads section |
$0:0:03 |
TABLET |
See Tablet section |
$0:1:00 |
PSG |
See Programmable Sound Generator section |
$0:1:01 |
OPL |
See Yamaha OPL2 FM Sound Generator section |
Keyboard¶
The RIA can provide applications direct access to keyboard data, which is what you want when you need key-up and key-down events or the modifier keys. If you don’t need that, the UART or stdin works just as well.
Enable and disable direct keyboard access by mapping it to an address in XRAM.
xreg(0, 0, 0x00, xaddr); // enable
xreg(0, 0, 0x00, 0xFFFF); // disable
xreg_ria_keyboard(xaddr); // macro shortcut
The RIA continuously updates XRAM with a bit array of USB HID keyboard keycodes, which are not PS/2 scancodes. Each keycode is one bit in the array: bit N is 1 while the key with HID keycode N is pressed. The first four keycodes are special:
0 - No key pressed
1 - Num Lock on
2 - Caps Lock on
3 - Scroll Lock on
#define KEYBOARD_NO_KEY 0
#define KEYBOARD_NUM_LOCK 1
#define KEYBOARD_CAPS_LOCK 2
#define KEYBOARD_SCROLL_LOCK 3
#define KEYBOARD_PRESSED(keys, code) ((keys)[(code) >> 3] & (1 << ((code) & 7)))
#define xreg_ria_keyboard(...) xreg(0, 0, 0, __VA_ARGS__)
typedef struct
{
uint8_t keys[32];
} keyboard_t; /* layout */
KEYBOARD_NO_KEY = 0
KEYBOARD_NUM_LOCK = 1
KEYBOARD_CAPS_LOCK = 2
KEYBOARD_SCROLL_LOCK = 3
.macro xreg_ria_keyboard addr
xreg 0, 0, 0, addr
.endmacro
.struct keyboard_t
keys .res 32
.endstruct
KEYBOARD_NO_KEY = 0
KEYBOARD_NUM_LOCK = 1
KEYBOARD_CAPS_LOCK = 2
KEYBOARD_SCROLL_LOCK = 3
.macro xreg_ria_keyboard addr
xreg 0, 0, 0, \addr
.endm
KEYBOARD_KEYS = 0
KEYBOARD_SIZE = 32
The keycodes are listed by name below, from the Keyboard/Keypad page of
the USB HID Usage Tables. Copy the ones you need or the whole block into
a file of its own, hidkeys.h or hidkeys.inc.
#ifndef HIDKEYS_H
#define HIDKEYS_H
#define HID_KEY_A 0x04
#define HID_KEY_B 0x05
#define HID_KEY_C 0x06
#define HID_KEY_D 0x07
#define HID_KEY_E 0x08
#define HID_KEY_F 0x09
#define HID_KEY_G 0x0A
#define HID_KEY_H 0x0B
#define HID_KEY_I 0x0C
#define HID_KEY_J 0x0D
#define HID_KEY_K 0x0E
#define HID_KEY_L 0x0F
#define HID_KEY_M 0x10
#define HID_KEY_N 0x11
#define HID_KEY_O 0x12
#define HID_KEY_P 0x13
#define HID_KEY_Q 0x14
#define HID_KEY_R 0x15
#define HID_KEY_S 0x16
#define HID_KEY_T 0x17
#define HID_KEY_U 0x18
#define HID_KEY_V 0x19
#define HID_KEY_W 0x1A
#define HID_KEY_X 0x1B
#define HID_KEY_Y 0x1C
#define HID_KEY_Z 0x1D
#define HID_KEY_1 0x1E
#define HID_KEY_2 0x1F
#define HID_KEY_3 0x20
#define HID_KEY_4 0x21
#define HID_KEY_5 0x22
#define HID_KEY_6 0x23
#define HID_KEY_7 0x24
#define HID_KEY_8 0x25
#define HID_KEY_9 0x26
#define HID_KEY_0 0x27
#define HID_KEY_ENTER 0x28
#define HID_KEY_ESCAPE 0x29
#define HID_KEY_BACKSPACE 0x2A
#define HID_KEY_TAB 0x2B
#define HID_KEY_SPACE 0x2C
#define HID_KEY_MINUS 0x2D
#define HID_KEY_EQUAL 0x2E
#define HID_KEY_BRACKET_LEFT 0x2F
#define HID_KEY_BRACKET_RIGHT 0x30
#define HID_KEY_BACKSLASH 0x31
#define HID_KEY_EUROPE_1 0x32
#define HID_KEY_SEMICOLON 0x33
#define HID_KEY_APOSTROPHE 0x34
#define HID_KEY_GRAVE 0x35
#define HID_KEY_COMMA 0x36
#define HID_KEY_PERIOD 0x37
#define HID_KEY_SLASH 0x38
#define HID_KEY_CAPS_LOCK 0x39
#define HID_KEY_F1 0x3A
#define HID_KEY_F2 0x3B
#define HID_KEY_F3 0x3C
#define HID_KEY_F4 0x3D
#define HID_KEY_F5 0x3E
#define HID_KEY_F6 0x3F
#define HID_KEY_F7 0x40
#define HID_KEY_F8 0x41
#define HID_KEY_F9 0x42
#define HID_KEY_F10 0x43
#define HID_KEY_F11 0x44
#define HID_KEY_F12 0x45
#define HID_KEY_PRINT_SCREEN 0x46
#define HID_KEY_SCROLL_LOCK 0x47
#define HID_KEY_PAUSE 0x48
#define HID_KEY_INSERT 0x49
#define HID_KEY_HOME 0x4A
#define HID_KEY_PAGE_UP 0x4B
#define HID_KEY_DELETE 0x4C
#define HID_KEY_END 0x4D
#define HID_KEY_PAGE_DOWN 0x4E
#define HID_KEY_ARROW_RIGHT 0x4F
#define HID_KEY_ARROW_LEFT 0x50
#define HID_KEY_ARROW_DOWN 0x51
#define HID_KEY_ARROW_UP 0x52
#define HID_KEY_NUM_LOCK 0x53
#define HID_KEY_KEYPAD_DIVIDE 0x54
#define HID_KEY_KEYPAD_MULTIPLY 0x55
#define HID_KEY_KEYPAD_SUBTRACT 0x56
#define HID_KEY_KEYPAD_ADD 0x57
#define HID_KEY_KEYPAD_ENTER 0x58
#define HID_KEY_KEYPAD_1 0x59
#define HID_KEY_KEYPAD_2 0x5A
#define HID_KEY_KEYPAD_3 0x5B
#define HID_KEY_KEYPAD_4 0x5C
#define HID_KEY_KEYPAD_5 0x5D
#define HID_KEY_KEYPAD_6 0x5E
#define HID_KEY_KEYPAD_7 0x5F
#define HID_KEY_KEYPAD_8 0x60
#define HID_KEY_KEYPAD_9 0x61
#define HID_KEY_KEYPAD_0 0x62
#define HID_KEY_KEYPAD_DECIMAL 0x63
#define HID_KEY_EUROPE_2 0x64
#define HID_KEY_APPLICATION 0x65
#define HID_KEY_POWER 0x66
#define HID_KEY_KEYPAD_EQUAL 0x67
#define HID_KEY_F13 0x68
#define HID_KEY_F14 0x69
#define HID_KEY_F15 0x6A
#define HID_KEY_F16 0x6B
#define HID_KEY_F17 0x6C
#define HID_KEY_F18 0x6D
#define HID_KEY_F19 0x6E
#define HID_KEY_F20 0x6F
#define HID_KEY_F21 0x70
#define HID_KEY_F22 0x71
#define HID_KEY_F23 0x72
#define HID_KEY_F24 0x73
#define HID_KEY_EXECUTE 0x74
#define HID_KEY_HELP 0x75
#define HID_KEY_MENU 0x76
#define HID_KEY_SELECT 0x77
#define HID_KEY_STOP 0x78
#define HID_KEY_AGAIN 0x79
#define HID_KEY_UNDO 0x7A
#define HID_KEY_CUT 0x7B
#define HID_KEY_COPY 0x7C
#define HID_KEY_PASTE 0x7D
#define HID_KEY_FIND 0x7E
#define HID_KEY_MUTE 0x7F
#define HID_KEY_VOLUME_UP 0x80
#define HID_KEY_VOLUME_DOWN 0x81
#define HID_KEY_LOCKING_CAPS_LOCK 0x82
#define HID_KEY_LOCKING_NUM_LOCK 0x83
#define HID_KEY_LOCKING_SCROLL_LOCK 0x84
#define HID_KEY_KEYPAD_COMMA 0x85
#define HID_KEY_KEYPAD_EQUAL_SIGN 0x86
#define HID_KEY_KANJI1 0x87
#define HID_KEY_KANJI2 0x88
#define HID_KEY_KANJI3 0x89
#define HID_KEY_KANJI4 0x8A
#define HID_KEY_KANJI5 0x8B
#define HID_KEY_KANJI6 0x8C
#define HID_KEY_KANJI7 0x8D
#define HID_KEY_KANJI8 0x8E
#define HID_KEY_KANJI9 0x8F
#define HID_KEY_LANG1 0x90
#define HID_KEY_LANG2 0x91
#define HID_KEY_LANG3 0x92
#define HID_KEY_LANG4 0x93
#define HID_KEY_LANG5 0x94
#define HID_KEY_LANG6 0x95
#define HID_KEY_LANG7 0x96
#define HID_KEY_LANG8 0x97
#define HID_KEY_LANG9 0x98
#define HID_KEY_ALTERNATE_ERASE 0x99
#define HID_KEY_SYSREQ_ATTENTION 0x9A
#define HID_KEY_CANCEL 0x9B
#define HID_KEY_CLEAR 0x9C
#define HID_KEY_PRIOR 0x9D
#define HID_KEY_RETURN 0x9E
#define HID_KEY_SEPARATOR 0x9F
#define HID_KEY_OUT 0xA0
#define HID_KEY_OPER 0xA1
#define HID_KEY_CLEAR_AGAIN 0xA2
#define HID_KEY_CRSEL_PROPS 0xA3
#define HID_KEY_EXSEL 0xA4
#define HID_KEY_KEYPAD_00 0xB0
#define HID_KEY_KEYPAD_000 0xB1
#define HID_KEY_THOUSANDS_SEPARATOR 0xB2
#define HID_KEY_DECIMAL_SEPARATOR 0xB3
#define HID_KEY_CURRENCY_UNIT 0xB4
#define HID_KEY_CURRENCY_SUBUNIT 0xB5
#define HID_KEY_KEYPAD_LEFT_PARENTHESIS 0xB6
#define HID_KEY_KEYPAD_RIGHT_PARENTHESIS 0xB7
#define HID_KEY_KEYPAD_LEFT_BRACE 0xB8
#define HID_KEY_KEYPAD_RIGHT_BRACE 0xB9
#define HID_KEY_KEYPAD_TAB 0xBA
#define HID_KEY_KEYPAD_BACKSPACE 0xBB
#define HID_KEY_KEYPAD_A 0xBC
#define HID_KEY_KEYPAD_B 0xBD
#define HID_KEY_KEYPAD_C 0xBE
#define HID_KEY_KEYPAD_D 0xBF
#define HID_KEY_KEYPAD_E 0xC0
#define HID_KEY_KEYPAD_F 0xC1
#define HID_KEY_KEYPAD_XOR 0xC2
#define HID_KEY_KEYPAD_CARET 0xC3
#define HID_KEY_KEYPAD_PERCENT 0xC4
#define HID_KEY_KEYPAD_LESS_THAN 0xC5
#define HID_KEY_KEYPAD_GREATER_THAN 0xC6
#define HID_KEY_KEYPAD_AMPERSAND 0xC7
#define HID_KEY_KEYPAD_DOUBLE_AMPERSAND 0xC8
#define HID_KEY_KEYPAD_VERTICAL_BAR 0xC9
#define HID_KEY_KEYPAD_DOUBLE_VERTICAL_BAR 0xCA
#define HID_KEY_KEYPAD_COLON 0xCB
#define HID_KEY_KEYPAD_HASH 0xCC
#define HID_KEY_KEYPAD_SPACE 0xCD
#define HID_KEY_KEYPAD_AT 0xCE
#define HID_KEY_KEYPAD_EXCLAMATION 0xCF
#define HID_KEY_KEYPAD_MEMORY_STORE 0xD0
#define HID_KEY_KEYPAD_MEMORY_RECALL 0xD1
#define HID_KEY_KEYPAD_MEMORY_CLEAR 0xD2
#define HID_KEY_KEYPAD_MEMORY_ADD 0xD3
#define HID_KEY_KEYPAD_MEMORY_SUBTRACT 0xD4
#define HID_KEY_KEYPAD_MEMORY_MULTIPLY 0xD5
#define HID_KEY_KEYPAD_MEMORY_DIVIDE 0xD6
#define HID_KEY_KEYPAD_PLUS_MINUS 0xD7
#define HID_KEY_KEYPAD_CLEAR 0xD8
#define HID_KEY_KEYPAD_CLEAR_ENTRY 0xD9
#define HID_KEY_KEYPAD_BINARY 0xDA
#define HID_KEY_KEYPAD_OCTAL 0xDB
#define HID_KEY_KEYPAD_DECIMAL_2 0xDC
#define HID_KEY_KEYPAD_HEXADECIMAL 0xDD
#define HID_KEY_CONTROL_LEFT 0xE0
#define HID_KEY_SHIFT_LEFT 0xE1
#define HID_KEY_ALT_LEFT 0xE2
#define HID_KEY_GUI_LEFT 0xE3
#define HID_KEY_CONTROL_RIGHT 0xE4
#define HID_KEY_SHIFT_RIGHT 0xE5
#define HID_KEY_ALT_RIGHT 0xE6
#define HID_KEY_GUI_RIGHT 0xE7
#endif
.ifndef HIDKEYS_INC
HIDKEYS_INC = 1
HID_KEY_A = $04
HID_KEY_B = $05
HID_KEY_C = $06
HID_KEY_D = $07
HID_KEY_E = $08
HID_KEY_F = $09
HID_KEY_G = $0A
HID_KEY_H = $0B
HID_KEY_I = $0C
HID_KEY_J = $0D
HID_KEY_K = $0E
HID_KEY_L = $0F
HID_KEY_M = $10
HID_KEY_N = $11
HID_KEY_O = $12
HID_KEY_P = $13
HID_KEY_Q = $14
HID_KEY_R = $15
HID_KEY_S = $16
HID_KEY_T = $17
HID_KEY_U = $18
HID_KEY_V = $19
HID_KEY_W = $1A
HID_KEY_X = $1B
HID_KEY_Y = $1C
HID_KEY_Z = $1D
HID_KEY_1 = $1E
HID_KEY_2 = $1F
HID_KEY_3 = $20
HID_KEY_4 = $21
HID_KEY_5 = $22
HID_KEY_6 = $23
HID_KEY_7 = $24
HID_KEY_8 = $25
HID_KEY_9 = $26
HID_KEY_0 = $27
HID_KEY_ENTER = $28
HID_KEY_ESCAPE = $29
HID_KEY_BACKSPACE = $2A
HID_KEY_TAB = $2B
HID_KEY_SPACE = $2C
HID_KEY_MINUS = $2D
HID_KEY_EQUAL = $2E
HID_KEY_BRACKET_LEFT = $2F
HID_KEY_BRACKET_RIGHT = $30
HID_KEY_BACKSLASH = $31
HID_KEY_EUROPE_1 = $32
HID_KEY_SEMICOLON = $33
HID_KEY_APOSTROPHE = $34
HID_KEY_GRAVE = $35
HID_KEY_COMMA = $36
HID_KEY_PERIOD = $37
HID_KEY_SLASH = $38
HID_KEY_CAPS_LOCK = $39
HID_KEY_F1 = $3A
HID_KEY_F2 = $3B
HID_KEY_F3 = $3C
HID_KEY_F4 = $3D
HID_KEY_F5 = $3E
HID_KEY_F6 = $3F
HID_KEY_F7 = $40
HID_KEY_F8 = $41
HID_KEY_F9 = $42
HID_KEY_F10 = $43
HID_KEY_F11 = $44
HID_KEY_F12 = $45
HID_KEY_PRINT_SCREEN = $46
HID_KEY_SCROLL_LOCK = $47
HID_KEY_PAUSE = $48
HID_KEY_INSERT = $49
HID_KEY_HOME = $4A
HID_KEY_PAGE_UP = $4B
HID_KEY_DELETE = $4C
HID_KEY_END = $4D
HID_KEY_PAGE_DOWN = $4E
HID_KEY_ARROW_RIGHT = $4F
HID_KEY_ARROW_LEFT = $50
HID_KEY_ARROW_DOWN = $51
HID_KEY_ARROW_UP = $52
HID_KEY_NUM_LOCK = $53
HID_KEY_KEYPAD_DIVIDE = $54
HID_KEY_KEYPAD_MULTIPLY = $55
HID_KEY_KEYPAD_SUBTRACT = $56
HID_KEY_KEYPAD_ADD = $57
HID_KEY_KEYPAD_ENTER = $58
HID_KEY_KEYPAD_1 = $59
HID_KEY_KEYPAD_2 = $5A
HID_KEY_KEYPAD_3 = $5B
HID_KEY_KEYPAD_4 = $5C
HID_KEY_KEYPAD_5 = $5D
HID_KEY_KEYPAD_6 = $5E
HID_KEY_KEYPAD_7 = $5F
HID_KEY_KEYPAD_8 = $60
HID_KEY_KEYPAD_9 = $61
HID_KEY_KEYPAD_0 = $62
HID_KEY_KEYPAD_DECIMAL = $63
HID_KEY_EUROPE_2 = $64
HID_KEY_APPLICATION = $65
HID_KEY_POWER = $66
HID_KEY_KEYPAD_EQUAL = $67
HID_KEY_F13 = $68
HID_KEY_F14 = $69
HID_KEY_F15 = $6A
HID_KEY_F16 = $6B
HID_KEY_F17 = $6C
HID_KEY_F18 = $6D
HID_KEY_F19 = $6E
HID_KEY_F20 = $6F
HID_KEY_F21 = $70
HID_KEY_F22 = $71
HID_KEY_F23 = $72
HID_KEY_F24 = $73
HID_KEY_EXECUTE = $74
HID_KEY_HELP = $75
HID_KEY_MENU = $76
HID_KEY_SELECT = $77
HID_KEY_STOP = $78
HID_KEY_AGAIN = $79
HID_KEY_UNDO = $7A
HID_KEY_CUT = $7B
HID_KEY_COPY = $7C
HID_KEY_PASTE = $7D
HID_KEY_FIND = $7E
HID_KEY_MUTE = $7F
HID_KEY_VOLUME_UP = $80
HID_KEY_VOLUME_DOWN = $81
HID_KEY_LOCKING_CAPS_LOCK = $82
HID_KEY_LOCKING_NUM_LOCK = $83
HID_KEY_LOCKING_SCROLL_LOCK = $84
HID_KEY_KEYPAD_COMMA = $85
HID_KEY_KEYPAD_EQUAL_SIGN = $86
HID_KEY_KANJI1 = $87
HID_KEY_KANJI2 = $88
HID_KEY_KANJI3 = $89
HID_KEY_KANJI4 = $8A
HID_KEY_KANJI5 = $8B
HID_KEY_KANJI6 = $8C
HID_KEY_KANJI7 = $8D
HID_KEY_KANJI8 = $8E
HID_KEY_KANJI9 = $8F
HID_KEY_LANG1 = $90
HID_KEY_LANG2 = $91
HID_KEY_LANG3 = $92
HID_KEY_LANG4 = $93
HID_KEY_LANG5 = $94
HID_KEY_LANG6 = $95
HID_KEY_LANG7 = $96
HID_KEY_LANG8 = $97
HID_KEY_LANG9 = $98
HID_KEY_ALTERNATE_ERASE = $99
HID_KEY_SYSREQ_ATTENTION = $9A
HID_KEY_CANCEL = $9B
HID_KEY_CLEAR = $9C
HID_KEY_PRIOR = $9D
HID_KEY_RETURN = $9E
HID_KEY_SEPARATOR = $9F
HID_KEY_OUT = $A0
HID_KEY_OPER = $A1
HID_KEY_CLEAR_AGAIN = $A2
HID_KEY_CRSEL_PROPS = $A3
HID_KEY_EXSEL = $A4
HID_KEY_KEYPAD_00 = $B0
HID_KEY_KEYPAD_000 = $B1
HID_KEY_THOUSANDS_SEPARATOR = $B2
HID_KEY_DECIMAL_SEPARATOR = $B3
HID_KEY_CURRENCY_UNIT = $B4
HID_KEY_CURRENCY_SUBUNIT = $B5
HID_KEY_KEYPAD_LEFT_PARENTHESIS = $B6
HID_KEY_KEYPAD_RIGHT_PARENTHESIS = $B7
HID_KEY_KEYPAD_LEFT_BRACE = $B8
HID_KEY_KEYPAD_RIGHT_BRACE = $B9
HID_KEY_KEYPAD_TAB = $BA
HID_KEY_KEYPAD_BACKSPACE = $BB
HID_KEY_KEYPAD_A = $BC
HID_KEY_KEYPAD_B = $BD
HID_KEY_KEYPAD_C = $BE
HID_KEY_KEYPAD_D = $BF
HID_KEY_KEYPAD_E = $C0
HID_KEY_KEYPAD_F = $C1
HID_KEY_KEYPAD_XOR = $C2
HID_KEY_KEYPAD_CARET = $C3
HID_KEY_KEYPAD_PERCENT = $C4
HID_KEY_KEYPAD_LESS_THAN = $C5
HID_KEY_KEYPAD_GREATER_THAN = $C6
HID_KEY_KEYPAD_AMPERSAND = $C7
HID_KEY_KEYPAD_DOUBLE_AMPERSAND = $C8
HID_KEY_KEYPAD_VERTICAL_BAR = $C9
HID_KEY_KEYPAD_DOUBLE_VERTICAL_BAR = $CA
HID_KEY_KEYPAD_COLON = $CB
HID_KEY_KEYPAD_HASH = $CC
HID_KEY_KEYPAD_SPACE = $CD
HID_KEY_KEYPAD_AT = $CE
HID_KEY_KEYPAD_EXCLAMATION = $CF
HID_KEY_KEYPAD_MEMORY_STORE = $D0
HID_KEY_KEYPAD_MEMORY_RECALL = $D1
HID_KEY_KEYPAD_MEMORY_CLEAR = $D2
HID_KEY_KEYPAD_MEMORY_ADD = $D3
HID_KEY_KEYPAD_MEMORY_SUBTRACT = $D4
HID_KEY_KEYPAD_MEMORY_MULTIPLY = $D5
HID_KEY_KEYPAD_MEMORY_DIVIDE = $D6
HID_KEY_KEYPAD_PLUS_MINUS = $D7
HID_KEY_KEYPAD_CLEAR = $D8
HID_KEY_KEYPAD_CLEAR_ENTRY = $D9
HID_KEY_KEYPAD_BINARY = $DA
HID_KEY_KEYPAD_OCTAL = $DB
HID_KEY_KEYPAD_DECIMAL_2 = $DC
HID_KEY_KEYPAD_HEXADECIMAL = $DD
HID_KEY_CONTROL_LEFT = $E0
HID_KEY_SHIFT_LEFT = $E1
HID_KEY_ALT_LEFT = $E2
HID_KEY_GUI_LEFT = $E3
HID_KEY_CONTROL_RIGHT = $E4
HID_KEY_SHIFT_RIGHT = $E5
HID_KEY_ALT_RIGHT = $E6
HID_KEY_GUI_RIGHT = $E7
.endif
.ifndef HIDKEYS_INC
HIDKEYS_INC = 1
HID_KEY_A = $04
HID_KEY_B = $05
HID_KEY_C = $06
HID_KEY_D = $07
HID_KEY_E = $08
HID_KEY_F = $09
HID_KEY_G = $0A
HID_KEY_H = $0B
HID_KEY_I = $0C
HID_KEY_J = $0D
HID_KEY_K = $0E
HID_KEY_L = $0F
HID_KEY_M = $10
HID_KEY_N = $11
HID_KEY_O = $12
HID_KEY_P = $13
HID_KEY_Q = $14
HID_KEY_R = $15
HID_KEY_S = $16
HID_KEY_T = $17
HID_KEY_U = $18
HID_KEY_V = $19
HID_KEY_W = $1A
HID_KEY_X = $1B
HID_KEY_Y = $1C
HID_KEY_Z = $1D
HID_KEY_1 = $1E
HID_KEY_2 = $1F
HID_KEY_3 = $20
HID_KEY_4 = $21
HID_KEY_5 = $22
HID_KEY_6 = $23
HID_KEY_7 = $24
HID_KEY_8 = $25
HID_KEY_9 = $26
HID_KEY_0 = $27
HID_KEY_ENTER = $28
HID_KEY_ESCAPE = $29
HID_KEY_BACKSPACE = $2A
HID_KEY_TAB = $2B
HID_KEY_SPACE = $2C
HID_KEY_MINUS = $2D
HID_KEY_EQUAL = $2E
HID_KEY_BRACKET_LEFT = $2F
HID_KEY_BRACKET_RIGHT = $30
HID_KEY_BACKSLASH = $31
HID_KEY_EUROPE_1 = $32
HID_KEY_SEMICOLON = $33
HID_KEY_APOSTROPHE = $34
HID_KEY_GRAVE = $35
HID_KEY_COMMA = $36
HID_KEY_PERIOD = $37
HID_KEY_SLASH = $38
HID_KEY_CAPS_LOCK = $39
HID_KEY_F1 = $3A
HID_KEY_F2 = $3B
HID_KEY_F3 = $3C
HID_KEY_F4 = $3D
HID_KEY_F5 = $3E
HID_KEY_F6 = $3F
HID_KEY_F7 = $40
HID_KEY_F8 = $41
HID_KEY_F9 = $42
HID_KEY_F10 = $43
HID_KEY_F11 = $44
HID_KEY_F12 = $45
HID_KEY_PRINT_SCREEN = $46
HID_KEY_SCROLL_LOCK = $47
HID_KEY_PAUSE = $48
HID_KEY_INSERT = $49
HID_KEY_HOME = $4A
HID_KEY_PAGE_UP = $4B
HID_KEY_DELETE = $4C
HID_KEY_END = $4D
HID_KEY_PAGE_DOWN = $4E
HID_KEY_ARROW_RIGHT = $4F
HID_KEY_ARROW_LEFT = $50
HID_KEY_ARROW_DOWN = $51
HID_KEY_ARROW_UP = $52
HID_KEY_NUM_LOCK = $53
HID_KEY_KEYPAD_DIVIDE = $54
HID_KEY_KEYPAD_MULTIPLY = $55
HID_KEY_KEYPAD_SUBTRACT = $56
HID_KEY_KEYPAD_ADD = $57
HID_KEY_KEYPAD_ENTER = $58
HID_KEY_KEYPAD_1 = $59
HID_KEY_KEYPAD_2 = $5A
HID_KEY_KEYPAD_3 = $5B
HID_KEY_KEYPAD_4 = $5C
HID_KEY_KEYPAD_5 = $5D
HID_KEY_KEYPAD_6 = $5E
HID_KEY_KEYPAD_7 = $5F
HID_KEY_KEYPAD_8 = $60
HID_KEY_KEYPAD_9 = $61
HID_KEY_KEYPAD_0 = $62
HID_KEY_KEYPAD_DECIMAL = $63
HID_KEY_EUROPE_2 = $64
HID_KEY_APPLICATION = $65
HID_KEY_POWER = $66
HID_KEY_KEYPAD_EQUAL = $67
HID_KEY_F13 = $68
HID_KEY_F14 = $69
HID_KEY_F15 = $6A
HID_KEY_F16 = $6B
HID_KEY_F17 = $6C
HID_KEY_F18 = $6D
HID_KEY_F19 = $6E
HID_KEY_F20 = $6F
HID_KEY_F21 = $70
HID_KEY_F22 = $71
HID_KEY_F23 = $72
HID_KEY_F24 = $73
HID_KEY_EXECUTE = $74
HID_KEY_HELP = $75
HID_KEY_MENU = $76
HID_KEY_SELECT = $77
HID_KEY_STOP = $78
HID_KEY_AGAIN = $79
HID_KEY_UNDO = $7A
HID_KEY_CUT = $7B
HID_KEY_COPY = $7C
HID_KEY_PASTE = $7D
HID_KEY_FIND = $7E
HID_KEY_MUTE = $7F
HID_KEY_VOLUME_UP = $80
HID_KEY_VOLUME_DOWN = $81
HID_KEY_LOCKING_CAPS_LOCK = $82
HID_KEY_LOCKING_NUM_LOCK = $83
HID_KEY_LOCKING_SCROLL_LOCK = $84
HID_KEY_KEYPAD_COMMA = $85
HID_KEY_KEYPAD_EQUAL_SIGN = $86
HID_KEY_KANJI1 = $87
HID_KEY_KANJI2 = $88
HID_KEY_KANJI3 = $89
HID_KEY_KANJI4 = $8A
HID_KEY_KANJI5 = $8B
HID_KEY_KANJI6 = $8C
HID_KEY_KANJI7 = $8D
HID_KEY_KANJI8 = $8E
HID_KEY_KANJI9 = $8F
HID_KEY_LANG1 = $90
HID_KEY_LANG2 = $91
HID_KEY_LANG3 = $92
HID_KEY_LANG4 = $93
HID_KEY_LANG5 = $94
HID_KEY_LANG6 = $95
HID_KEY_LANG7 = $96
HID_KEY_LANG8 = $97
HID_KEY_LANG9 = $98
HID_KEY_ALTERNATE_ERASE = $99
HID_KEY_SYSREQ_ATTENTION = $9A
HID_KEY_CANCEL = $9B
HID_KEY_CLEAR = $9C
HID_KEY_PRIOR = $9D
HID_KEY_RETURN = $9E
HID_KEY_SEPARATOR = $9F
HID_KEY_OUT = $A0
HID_KEY_OPER = $A1
HID_KEY_CLEAR_AGAIN = $A2
HID_KEY_CRSEL_PROPS = $A3
HID_KEY_EXSEL = $A4
HID_KEY_KEYPAD_00 = $B0
HID_KEY_KEYPAD_000 = $B1
HID_KEY_THOUSANDS_SEPARATOR = $B2
HID_KEY_DECIMAL_SEPARATOR = $B3
HID_KEY_CURRENCY_UNIT = $B4
HID_KEY_CURRENCY_SUBUNIT = $B5
HID_KEY_KEYPAD_LEFT_PARENTHESIS = $B6
HID_KEY_KEYPAD_RIGHT_PARENTHESIS = $B7
HID_KEY_KEYPAD_LEFT_BRACE = $B8
HID_KEY_KEYPAD_RIGHT_BRACE = $B9
HID_KEY_KEYPAD_TAB = $BA
HID_KEY_KEYPAD_BACKSPACE = $BB
HID_KEY_KEYPAD_A = $BC
HID_KEY_KEYPAD_B = $BD
HID_KEY_KEYPAD_C = $BE
HID_KEY_KEYPAD_D = $BF
HID_KEY_KEYPAD_E = $C0
HID_KEY_KEYPAD_F = $C1
HID_KEY_KEYPAD_XOR = $C2
HID_KEY_KEYPAD_CARET = $C3
HID_KEY_KEYPAD_PERCENT = $C4
HID_KEY_KEYPAD_LESS_THAN = $C5
HID_KEY_KEYPAD_GREATER_THAN = $C6
HID_KEY_KEYPAD_AMPERSAND = $C7
HID_KEY_KEYPAD_DOUBLE_AMPERSAND = $C8
HID_KEY_KEYPAD_VERTICAL_BAR = $C9
HID_KEY_KEYPAD_DOUBLE_VERTICAL_BAR = $CA
HID_KEY_KEYPAD_COLON = $CB
HID_KEY_KEYPAD_HASH = $CC
HID_KEY_KEYPAD_SPACE = $CD
HID_KEY_KEYPAD_AT = $CE
HID_KEY_KEYPAD_EXCLAMATION = $CF
HID_KEY_KEYPAD_MEMORY_STORE = $D0
HID_KEY_KEYPAD_MEMORY_RECALL = $D1
HID_KEY_KEYPAD_MEMORY_CLEAR = $D2
HID_KEY_KEYPAD_MEMORY_ADD = $D3
HID_KEY_KEYPAD_MEMORY_SUBTRACT = $D4
HID_KEY_KEYPAD_MEMORY_MULTIPLY = $D5
HID_KEY_KEYPAD_MEMORY_DIVIDE = $D6
HID_KEY_KEYPAD_PLUS_MINUS = $D7
HID_KEY_KEYPAD_CLEAR = $D8
HID_KEY_KEYPAD_CLEAR_ENTRY = $D9
HID_KEY_KEYPAD_BINARY = $DA
HID_KEY_KEYPAD_OCTAL = $DB
HID_KEY_KEYPAD_DECIMAL_2 = $DC
HID_KEY_KEYPAD_HEXADECIMAL = $DD
HID_KEY_CONTROL_LEFT = $E0
HID_KEY_SHIFT_LEFT = $E1
HID_KEY_ALT_LEFT = $E2
HID_KEY_GUI_LEFT = $E3
HID_KEY_CONTROL_RIGHT = $E4
HID_KEY_SHIFT_RIGHT = $E5
HID_KEY_ALT_RIGHT = $E6
HID_KEY_GUI_RIGHT = $E7
.endif
Mouse¶
Note
The Tablet interface is almost always the better choice. It gives a canvas pixel position for a mouse, pen, or touchscreen. Raw mouse input is still useful for devices that act like a mouse but are not a pointer, such as spinners.
The RIA can give applications direct access to mouse data. Enable and disable it by mapping it to an address in XRAM.
xreg(0, 0, 0x01, xaddr); // enable
xreg(0, 0, 0x01, 0xFFFF); // disable
xreg_ria_mouse(xaddr); // macro shortcut
This sets the XRAM address of a structure holding the live mouse input.
Compute movement by subtracting the previous value from the current one. VSYNC timing (60 Hz) is period-correct but slow by modern standards. For precise mouse input, poll from an ISR at 8 ms or faster (125 Hz).
Account for canvas resolution when interpreting movement. At 640x480 and 640x360, one unit equals one pixel; at 320x240 and 320x180, two units equal one pixel.
int8_t delta_x = current_x - prev_x;
int8_t delta_y = current_y - prev_y;
Mouse buttons are a bitfield:
0 - LEFT
1 - RIGHT
2 - MIDDLE
3 - BACKWARD
4 - FORWARD
#define MOUSE_BUTTON_LEFT 0x01
#define MOUSE_BUTTON_RIGHT 0x02
#define MOUSE_BUTTON_MIDDLE 0x04
#define MOUSE_BUTTON_BACKWARD 0x08
#define MOUSE_BUTTON_FORWARD 0x10
#define xreg_ria_mouse(...) xreg(0, 0, 1, __VA_ARGS__)
typedef struct
{
uint8_t buttons;
uint8_t x;
uint8_t y;
uint8_t wheel;
uint8_t pan;
uint8_t pad; // alignment, unused
} mouse_t; /* layout */
MOUSE_BUTTON_LEFT = $01
MOUSE_BUTTON_RIGHT = $02
MOUSE_BUTTON_MIDDLE = $04
MOUSE_BUTTON_BACKWARD = $08
MOUSE_BUTTON_FORWARD = $10
.macro xreg_ria_mouse addr
xreg 0, 0, 1, addr
.endmacro
.struct mouse_t
buttons .byte
x_pos .byte
y_pos .byte
wheel .byte
pan .byte
pad .byte
.endstruct
MOUSE_BUTTON_LEFT = $01
MOUSE_BUTTON_RIGHT = $02
MOUSE_BUTTON_MIDDLE = $04
MOUSE_BUTTON_BACKWARD = $08
MOUSE_BUTTON_FORWARD = $10
.macro xreg_ria_mouse addr
xreg 0, 0, 1, \addr
.endm
MOUSE_BUTTONS = 0
MOUSE_X = 1
MOUSE_Y = 2
MOUSE_WHEEL = 3
MOUSE_PAN = 4
MOUSE_PAD = 5
MOUSE_SIZE = 6
Tablet¶
The RIA can give applications an absolute pointer — a “tablet” — instead of the relative mouse. It reports a canvas pixel position directly: a plain mouse is integrated and clamped to the canvas, and an absolute digitizer or touchscreen is scaled to it. Enable and disable it by mapping it to an address in XRAM.
xreg(0, 0, 0x03, xaddr); // enable
xreg(0, 0, 0x03, 0xFFFF); // disable
xreg_ria_tablet(xaddr); // macro shortcut
The block is a four-byte header followed by eight contact records for multi-touch; a mouse or pen uses only the first.
wheel and pan are scroll counters in the same format as the
mouse’s: subtract the previous reading to get the change. Reading them
once per VSYNC is enough for normal use.
Each axis is a set of single-byte windows: exactly one is non-zero, and it alone carries the value. Decode by taking the first non-zero byte. This unusual decode is because XRAM is atomic for 8-bits only. The single retry is enough to guarantee safety because updates are 1ms or more apart while the retry happens in a few microseconds. A read that overlaps an update can also return one axis from the previous update and the other axis from the new one. That is imperceptible, so it needs no handling.
if (c.x0) x = c.x0 - 1;
else if (c.x1) x = c.x1 + 254;
else if (c.x2) x = c.x2 + 509;
else { /* read the contact once more, then keep the previous X */ }
if (c.y0) y = c.y0 - 1;
else if (c.y1) y = c.y1 + 254;
else { /* read the contact once more, then keep the previous Y */ }
Contact flags are a bitfield:
0 - LEFT / tip
1 - RIGHT
2 - MIDDLE
3 - BACKWARD
4 - FORWARD
7 - HOVER
HOVER is set when the contact tracks a position without a press. It is always set for a mouse, set for a pen while the pen is in range, and clear for a touchscreen.
The application and the RIA exchange pointer preferences through the header.
status bit 0 (host cursor) is set only for a mouse on the RP6502-EMU
or on the RP6502-WEB, where the host can draw a cursor for the
application. The bit is always clear on the RP6502-PICO and the
RP6502-FPGA, in RetroArch, and for touch input.
control selects the host cursor shape, or hides the cursor so the
application can draw its own. Mapping the tablet sets it to ARROW.
0 - OFF (host cursor hidden; the application draws its own pointer)
1 - ARROW
2 - CROSSHAIR
3 - IBEAM
4 - HAND
5 - RESIZE_EW
6 - RESIZE_NS
When the host cursor bit is clear the application must draw its own
pointer, and control has no effect.
#define TABLET_CONTACTS 8
#define TABLET_STATUS_HOST_CURSOR 0x01
#define TABLET_FLAG_LEFT 0x01
#define TABLET_FLAG_RIGHT 0x02
#define TABLET_FLAG_MIDDLE 0x04
#define TABLET_FLAG_BACKWARD 0x08
#define TABLET_FLAG_FORWARD 0x10
#define TABLET_FLAG_HOVER 0x80
#define TABLET_CURSOR_OFF 0
#define TABLET_CURSOR_ARROW 1
#define TABLET_CURSOR_CROSSHAIR 2
#define TABLET_CURSOR_IBEAM 3
#define TABLET_CURSOR_HAND 4
#define TABLET_CURSOR_RESIZE_EW 5
#define TABLET_CURSOR_RESIZE_NS 6
#define xreg_ria_tablet(...) xreg(0, 0, 3, __VA_ARGS__)
typedef struct
{
uint8_t flags;
uint8_t x0, x1, x2;
uint8_t y0, y1;
} tablet_contact_t;
typedef struct
{
uint8_t control;
uint8_t status;
uint8_t wheel;
uint8_t pan;
tablet_contact_t contact[TABLET_CONTACTS];
} tablet_t; /* layout */
TABLET_CONTACTS = 8
TABLET_STATUS_HOST_CURSOR = $01
TABLET_FLAG_LEFT = $01
TABLET_FLAG_RIGHT = $02
TABLET_FLAG_MIDDLE = $04
TABLET_FLAG_BACKWARD = $08
TABLET_FLAG_FORWARD = $10
TABLET_FLAG_HOVER = $80
TABLET_CURSOR_OFF = 0
TABLET_CURSOR_ARROW = 1
TABLET_CURSOR_CROSSHAIR = 2
TABLET_CURSOR_IBEAM = 3
TABLET_CURSOR_HAND = 4
TABLET_CURSOR_RESIZE_EW = 5
TABLET_CURSOR_RESIZE_NS = 6
.macro xreg_ria_tablet addr
xreg 0, 0, 3, addr
.endmacro
.struct tablet_t
control .byte
status .byte
wheel .byte
pan .byte
contact .struct
flags .byte
x0 .byte
x1 .byte
x2 .byte
y0 .byte
y1 .byte
.endstruct
.res (::TABLET_CONTACTS - 1) * .sizeof(contact)
.endstruct
TABLET_CONTACTS = 8
TABLET_STATUS_HOST_CURSOR = $01
TABLET_FLAG_LEFT = $01
TABLET_FLAG_RIGHT = $02
TABLET_FLAG_MIDDLE = $04
TABLET_FLAG_BACKWARD = $08
TABLET_FLAG_FORWARD = $10
TABLET_FLAG_HOVER = $80
TABLET_CURSOR_OFF = 0
TABLET_CURSOR_ARROW = 1
TABLET_CURSOR_CROSSHAIR = 2
TABLET_CURSOR_IBEAM = 3
TABLET_CURSOR_HAND = 4
TABLET_CURSOR_RESIZE_EW = 5
TABLET_CURSOR_RESIZE_NS = 6
.macro xreg_ria_tablet addr
xreg 0, 0, 3, \addr
.endm
TABLET_CONTROL = 0
TABLET_STATUS = 1
TABLET_WHEEL = 2
TABLET_PAN = 3
TABLET_CONTACT = 4
TABLET_CONTACT_FLAGS = 0
TABLET_CONTACT_X0 = 1
TABLET_CONTACT_X1 = 2
TABLET_CONTACT_X2 = 3
TABLET_CONTACT_Y0 = 4
TABLET_CONTACT_Y1 = 5
TABLET_CONTACT_SIZE = 6
TABLET_SIZE = TABLET_CONTACT + TABLET_CONTACTS * TABLET_CONTACT_SIZE
Gamepads¶
The RIA supports up to four gamepads. RP6502-PICO firmware includes drivers for Generic HID, XInput, and PlayStation controllers. Where the layout comes from, and how a game works with nearly every gamepad, is covered in the Gamepads section of PORT.
Enable and disable the RIA gamepad data by setting its extended register. The register value is the XRAM start address of the gamepad data.
xreg(0, 0, 2, xaddr); // enable
xreg(0, 0, 2, 0xFFFF); // disable
xreg_ria_gamepad(xaddr); // macro shortcut
The RIA continuously updates extended memory with gamepad state. The 10-byte structure below repeats four times — 40 bytes total, one block per gamepad.
The upper bits of the DPAD register report readiness and type. The connected bit is high when a gamepad occupies that player slot.
Type |
BTN0 bit 0 |
BTN0 bit 1 |
BTN0 bit 3 |
BTN0 bit 4 |
|---|---|---|---|---|
1 Western AB |
A, south |
B, east |
X, west |
Y, north |
2 Eastern BA |
A, east |
B, south |
X, north |
Y, west |
3 PlayStation |
Cross, south |
Circle, east |
Square, west |
Triangle, north |
The sticks bit is high when the gamepad has both analog sticks. Some retro-style gamepads indicate they have sticks when they do not. They may also map buttons in unusual ways. The RIA does the best it can with the provided metadata.
Both digital and analog values are available for the sticks and the L2/R2 triggers.
Some gamepads report only digital data; in that case, code that uses L2 and R2 should expect analog values of just 0 or 255.
Offset |
Name |
Description |
|---|---|---|
0 |
DPAD |
|
1 |
STICKS |
|
2 |
BTN0 |
|
3 |
BTN1 |
|
4 |
LX |
Left analog stick X position. -128=left, 0=center, 127=right |
5 |
LY |
Left analog stick Y position. -128=up, 0=center, 127=down |
6 |
RX |
Right analog stick X position. -128=left, 0=center, 127=right |
7 |
RY |
Right analog stick Y position. -128=up, 0=center, 127=down |
8 |
L2 |
Left analog trigger position. 0-255 |
9 |
R2 |
Right analog trigger position. 0-255 |
#define GAMEPAD_PLAYERS 4
#define GAMEPAD_DPAD_UP 0x01
#define GAMEPAD_DPAD_DOWN 0x02
#define GAMEPAD_DPAD_LEFT 0x04
#define GAMEPAD_DPAD_RIGHT 0x08
#define GAMEPAD_FEAT_TYPE_MASK 0x30
#define GAMEPAD_TYPE_UNKNOWN 0x00
#define GAMEPAD_TYPE_WESTERN 0x10
#define GAMEPAD_TYPE_EASTERN 0x20
#define GAMEPAD_TYPE_PLAYSTATION 0x30
#define GAMEPAD_FEAT_STICKS 0x40
#define GAMEPAD_FEAT_CONNECTED 0x80
#define GAMEPAD_LSTICK_UP 0x01
#define GAMEPAD_LSTICK_DOWN 0x02
#define GAMEPAD_LSTICK_LEFT 0x04
#define GAMEPAD_LSTICK_RIGHT 0x08
#define GAMEPAD_RSTICK_UP 0x10
#define GAMEPAD_RSTICK_DOWN 0x20
#define GAMEPAD_RSTICK_LEFT 0x40
#define GAMEPAD_RSTICK_RIGHT 0x80
#define GAMEPAD_BTN0_A 0x01
#define GAMEPAD_BTN0_B 0x02
#define GAMEPAD_BTN0_C 0x04
#define GAMEPAD_BTN0_X 0x08
#define GAMEPAD_BTN0_Y 0x10
#define GAMEPAD_BTN0_Z 0x20
#define GAMEPAD_BTN0_L1 0x40
#define GAMEPAD_BTN0_R1 0x80
#define GAMEPAD_BTN1_L2 0x01
#define GAMEPAD_BTN1_R2 0x02
#define GAMEPAD_BTN1_SELECT 0x04
#define GAMEPAD_BTN1_START 0x08
#define GAMEPAD_BTN1_HOME 0x10
#define GAMEPAD_BTN1_L3 0x20
#define GAMEPAD_BTN1_R3 0x40
#define xreg_ria_gamepad(...) xreg(0, 0, 2, __VA_ARGS__)
typedef struct
{
uint8_t dpad;
uint8_t sticks;
uint8_t btn0;
uint8_t btn1;
int8_t lx;
int8_t ly;
int8_t rx;
int8_t ry;
uint8_t l2;
uint8_t r2;
} gamepad_player_t;
typedef struct
{
gamepad_player_t player[GAMEPAD_PLAYERS];
} gamepad_t; /* layout */
GAMEPAD_PLAYERS = 4
GAMEPAD_DPAD_UP = $01
GAMEPAD_DPAD_DOWN = $02
GAMEPAD_DPAD_LEFT = $04
GAMEPAD_DPAD_RIGHT = $08
GAMEPAD_FEAT_TYPE_MASK = $30
GAMEPAD_TYPE_UNKNOWN = $00
GAMEPAD_TYPE_WESTERN = $10
GAMEPAD_TYPE_EASTERN = $20
GAMEPAD_TYPE_PLAYSTATION = $30
GAMEPAD_FEAT_STICKS = $40
GAMEPAD_FEAT_CONNECTED = $80
GAMEPAD_LSTICK_UP = $01
GAMEPAD_LSTICK_DOWN = $02
GAMEPAD_LSTICK_LEFT = $04
GAMEPAD_LSTICK_RIGHT = $08
GAMEPAD_RSTICK_UP = $10
GAMEPAD_RSTICK_DOWN = $20
GAMEPAD_RSTICK_LEFT = $40
GAMEPAD_RSTICK_RIGHT = $80
GAMEPAD_BTN0_A = $01
GAMEPAD_BTN0_B = $02
GAMEPAD_BTN0_C = $04
GAMEPAD_BTN0_X = $08
GAMEPAD_BTN0_Y = $10
GAMEPAD_BTN0_Z = $20
GAMEPAD_BTN0_L1 = $40
GAMEPAD_BTN0_R1 = $80
GAMEPAD_BTN1_L2 = $01
GAMEPAD_BTN1_R2 = $02
GAMEPAD_BTN1_SELECT = $04
GAMEPAD_BTN1_START = $08
GAMEPAD_BTN1_HOME = $10
GAMEPAD_BTN1_L3 = $20
GAMEPAD_BTN1_R3 = $40
.macro xreg_ria_gamepad addr
xreg 0, 0, 2, addr
.endmacro
.struct gamepad_t
player .struct
dpad .byte
sticks .byte
btn0 .byte
btn1 .byte
lx .byte
ly .byte
rx .byte
ry .byte
l2 .byte
r2 .byte
.endstruct
.res (::GAMEPAD_PLAYERS - 1) * .sizeof(player)
.endstruct
GAMEPAD_PLAYERS = 4
GAMEPAD_DPAD_UP = $01
GAMEPAD_DPAD_DOWN = $02
GAMEPAD_DPAD_LEFT = $04
GAMEPAD_DPAD_RIGHT = $08
GAMEPAD_FEAT_TYPE_MASK = $30
GAMEPAD_TYPE_UNKNOWN = $00
GAMEPAD_TYPE_WESTERN = $10
GAMEPAD_TYPE_EASTERN = $20
GAMEPAD_TYPE_PLAYSTATION = $30
GAMEPAD_FEAT_STICKS = $40
GAMEPAD_FEAT_CONNECTED = $80
GAMEPAD_LSTICK_UP = $01
GAMEPAD_LSTICK_DOWN = $02
GAMEPAD_LSTICK_LEFT = $04
GAMEPAD_LSTICK_RIGHT = $08
GAMEPAD_RSTICK_UP = $10
GAMEPAD_RSTICK_DOWN = $20
GAMEPAD_RSTICK_LEFT = $40
GAMEPAD_RSTICK_RIGHT = $80
GAMEPAD_BTN0_A = $01
GAMEPAD_BTN0_B = $02
GAMEPAD_BTN0_C = $04
GAMEPAD_BTN0_X = $08
GAMEPAD_BTN0_Y = $10
GAMEPAD_BTN0_Z = $20
GAMEPAD_BTN0_L1 = $40
GAMEPAD_BTN0_R1 = $80
GAMEPAD_BTN1_L2 = $01
GAMEPAD_BTN1_R2 = $02
GAMEPAD_BTN1_SELECT = $04
GAMEPAD_BTN1_START = $08
GAMEPAD_BTN1_HOME = $10
GAMEPAD_BTN1_L3 = $20
GAMEPAD_BTN1_R3 = $40
.macro xreg_ria_gamepad addr
xreg 0, 0, 2, \addr
.endm
GAMEPAD_PLAYER = 0
GAMEPAD_PLAYER_DPAD = 0
GAMEPAD_PLAYER_STICKS = 1
GAMEPAD_PLAYER_BTN0 = 2
GAMEPAD_PLAYER_BTN1 = 3
GAMEPAD_PLAYER_LX = 4
GAMEPAD_PLAYER_LY = 5
GAMEPAD_PLAYER_RX = 6
GAMEPAD_PLAYER_RY = 7
GAMEPAD_PLAYER_L2 = 8
GAMEPAD_PLAYER_R2 = 9
GAMEPAD_PLAYER_SIZE = 10
GAMEPAD_SIZE = GAMEPAD_PLAYER + GAMEPAD_PLAYERS * GAMEPAD_PLAYER_SIZE
Programmable Sound Generator¶
The RIA includes a Programmable Sound Generator (PSG), configured through extended register device 0, channel 1, address 0x00.
Eight 16-bit oscillator channels.
Five waveforms: Sine, Square, Sawtooth, Triangle, Noise.
ADSR envelope: Attack, Decay, Sustain, Release.
Stereo panning.
PWM for all waveforms.
Each of the eight oscillators uses eight bytes of XRAM for configuration. The structure size is a power of two, so indexing into the oscillator array is a bit shift.
Enable and disable the PSG by setting its extended register. The value is the XRAM start address for the 64 bytes of config; it must be int-aligned and must not cross a page boundary. Only one sound generator plays at a time, so enabling the PSG disables the OPL2.
xreg(0, 1, 0x00, xaddr); // enable
xreg(0, 1, 0x00, 0xFFFF); // disable
xreg_ria_psg(xaddr); // macro shortcut
Configuration changes take effect immediately, which opens the door to panning, slide instruments, and other CPU-driven shenanigans.
Name |
Description |
|---|---|
freq |
0-65535 Oscillator frequency as Hertz * 3. This results in a resolution of 1/3 Hz. |
duty |
0-255 (0-100%) Duty cycle of oscillator. This affects all waveforms. |
attack |
Attack phase volume and rate.
|
decay |
Decay phase volume and rate.
|
release_wave |
Waveform and release rate.
|
pan_gate |
Stereo pan and gate.
|
Value table. ADR rates are the time it takes for a full volume change. Volume attenuation is logarithmic.
Value |
Attack |
Decay/Release |
Attenuation Multiplier |
|---|---|---|---|
0 |
2ms |
6ms |
256/256 (loud) |
1 |
8ms |
24ms |
204/256 |
2 |
16ms |
48ms |
168/256 |
3 |
24ms |
72ms |
142/256 |
4 |
38ms |
114ms |
120/256 |
5 |
56ms |
168ms |
102/256 |
6 |
68ms |
204ms |
86/256 |
7 |
80ms |
240ms |
73/256 |
8 |
100ms |
300ms |
61/256 |
9 |
250ms |
750ms |
50/256 |
10 |
500ms |
1.5s |
40/256 |
11 |
800ms |
2.4s |
31/256 |
12 |
1s |
3s |
22/256 |
13 |
3s |
9s |
14/256 |
14 |
5s |
15s |
7/256 |
15 |
8s |
24s |
0/256 (silent) |
#define PSG_CHANNELS 8
#define PSG_WAVE_SINE 0x00
#define PSG_WAVE_SQUARE 0x10
#define PSG_WAVE_SAWTOOTH 0x20
#define PSG_WAVE_TRIANGLE 0x30
#define PSG_WAVE_NOISE 0x40
#define PSG_GATE 0x01
#define PSG_FREQ_HZ(hz) ((hz) * 3u)
#define PSG_PAN(pan) ((uint8_t)((pan) * 2))
#define xreg_ria_psg(...) xreg(0, 1, 0, __VA_ARGS__)
typedef struct
{
uint16_t freq;
uint8_t duty;
uint8_t attack;
uint8_t decay;
uint8_t release_wave;
uint8_t pan_gate;
uint8_t reserved;
} psg_channel_t;
typedef struct
{
psg_channel_t channel[PSG_CHANNELS];
} psg_t; /* layout */
/* After the XRAM_ names: PSG_PAGE_CHECK(XRAM_PSG); */
#define PSG_PAGE_CHECK(addr) \
_Static_assert((addr) % 256 + sizeof(psg_t) <= 256, #addr " crosses a page.")
PSG_CHANNELS = 8
PSG_WAVE_SINE = $00
PSG_WAVE_SQUARE = $10
PSG_WAVE_SAWTOOTH = $20
PSG_WAVE_TRIANGLE = $30
PSG_WAVE_NOISE = $40
PSG_GATE = $01
.macro xreg_ria_psg addr
xreg 0, 1, 0, addr
.endmacro
.struct psg_t
channel .struct
freq .word
duty .byte
attack .byte
decay .byte
release_wave .byte
pan_gate .byte
reserved .byte
.endstruct
.res (::PSG_CHANNELS - 1) * .sizeof(channel)
.endstruct
PSG_CHANNELS = 8
PSG_WAVE_SINE = $00
PSG_WAVE_SQUARE = $10
PSG_WAVE_SAWTOOTH = $20
PSG_WAVE_TRIANGLE = $30
PSG_WAVE_NOISE = $40
PSG_GATE = $01
.macro xreg_ria_psg addr
xreg 0, 1, 0, \addr
.endm
PSG_CHANNEL = 0
PSG_CHANNEL_FREQ = 0
PSG_CHANNEL_DUTY = 2
PSG_CHANNEL_ATTACK = 3
PSG_CHANNEL_DECAY = 4
PSG_CHANNEL_RELEASE_WAVE = 5
PSG_CHANNEL_PAN_GATE = 6
PSG_CHANNEL_RESERVED = 7
PSG_CHANNEL_SIZE = 8
PSG_SIZE = PSG_CHANNEL + PSG_CHANNELS * PSG_CHANNEL_SIZE
Yamaha OPL2 FM Sound Generator¶
The RIA includes a YM3812 FM Sound Generator (OPL2), configured through extended register device 0, channel 1, address 0x01.
Enable and disable the OPL2 by setting its extended register. The value is the XRAM start address for the 256 OPL2 registers, which must begin on a page boundary. So if xaddr is 0x4200, the 256 OPL2 registers map into XRAM from 0x4200 to 0x42FF. Enabling the OPL2 disables the PSG.
xreg(0, 1, 0x01, xaddr); // enable
xreg(0, 1, 0x01, 0xFFFF); // disable
xreg_ria_opl(xaddr); // macro shortcut
Timers, interrupts, and the status register are not supported. Those features existed mainly to cost-reduce consumer devices; computers of the era had their own timers and rarely used the chip’s.
#define xreg_ria_opl(...) xreg(0, 1, 1, __VA_ARGS__)
typedef struct
{
uint8_t reg[256];
} opl_t; /* layout */
/* After the XRAM_ names: OPL_PAGE_CHECK(XRAM_OPL); */
#define OPL_PAGE_CHECK(addr) \
_Static_assert((addr) % 256 == 0, #addr " does not start a page.")
.macro xreg_ria_opl addr
xreg 0, 1, 1, addr
.endmacro
.struct opl_t
reg .res 256
.endstruct
.macro xreg_ria_opl addr
xreg 0, 1, 1, \addr
.endm
OPL_REG = 0
OPL_SIZE = 256
Console¶
The system console is the terminal connected to the RIA, and the
UART registers above are its rawest form. The OS wraps that same port as
stdin, stdout, stderr, and the CON: and TTY: device
names. See TERM for cooked and raw reads, the non-blocking
variants, and the line editor.
Virtual COM Port¶
If you need serial ports beyond the console UART, USB adapters are available for CMOS/TTL, RS-232, RS-422, and RS-485, and each one appears as a Virtual COM Port (VCP). RP6502-PICO firmware includes drivers for FTDI, CP210X, CH34X, PL2303, and CDC ACM.
The status command lists any connected VCP devices. Open one like a
file, using a special name. By default "VCP0:" opens at 115200 bps
with 8 data bits, no parity, and 1 stop bit. Set the baud rate with
"VCP0:115200", or the full bit configuration with
"VCP0:115200,8N1". The file won’t open if your hardware can’t
support the requested configuration, and the open flags are ignored.
open("VCP0:1200,7E2", 0);
// then read and write
Generous FIFO buffers serve both directions, and both reads and writes are non-blocking. Reads can return 0 bytes, and writes may send less than you asked for — resubmit any remaining bytes on a later call.
MIDI¶
MIDI instruments attached to the machine appear as devices, and the
status command lists them. RP6502-PICO firmware includes a USB MIDI
host driver, so a USB instrument plugs right in. Each virtual cable is
its own device — "MIDI0:" onward, assigned in the order cables
appear, up to four at a time. A simple keyboard is one cable (1X1); a
multi-port interface is several. Open one like a file.
A cable opens in one of two modes, chosen by the open name. Bare
"MIDI0:" is raw — reads and writes are plain wire MIDI, the same
bytes a 5-pin DIN cable carries, with no timing and nothing added or
removed. Give a division instead, "MIDI0:480", and the cable is
timed: the RIA handles timing for you using the event format from
Standard MIDI Files, prefixing every message with a variable length
quantity delta time measured in ticks. The rest of this section is the
timed format.
In timed mode, time starts at the open. The first byte in either direction is a delta measuring from the open itself, and a delta of zero means right now. Writes are scheduled — the RIA holds each message and sends it to the instrument exactly on time, so your program only needs to keep the buffer fed. Reads are a recording — incoming messages arrive with delta times measuring when they actually happened, ready to store in a file or play back later.
The division is ticks per quarter note, the value an SMF carries in its header. It accepts 1 to 32767 and is fixed while open; reopen between songs to change it. The open flags are ignored. A cable can be input, output, or both; reading an output-only cable or writing an input-only one returns an error.
Tempo changes on the fly with the standard SMF Set Tempo meta event,
which the RIA consumes locally and never forwards to the instrument. The
event is FF, a type, a length, then that many data bytes:
Control event |
Effect |
|---|---|
|
Set tempo in microseconds per quarter note — the standard SMF event. The tick rate becomes tempo × 1000 ÷ division. |
|
A wire System Reset. The doubled escape is the whole event, with no length byte. Unlike the others, it is sent to the instrument. |
Tempo defaults to 500000 µs per quarter note — 120 BPM, a 1041667 ns
tick at 480 PPQN. Every other FF event, including the rest of the
SMF meta set, is swallowed without effect, so a Standard MIDI File track
plays through nearly verbatim — division from the file header, tempo
events straight from the track:
open("MIDI0:96", 0); // division from the MThd header
// FF 51 03 07 A1 20 tempo = 500000 (120 BPM at 96 PPQN)
// then delta-timed events; the RIA paces them and tracks tempo changes
The RIA echoes every tempo event onto the read stream at the moment it
takes effect, so a recording is self-describing. A rejected event is
malformed, or carries a value of zero or out of range. The RIA echoes it
with its value zeroed and the tempo unchanged; zero is never a valid tempo,
so it unambiguously marks an event that didn’t apply. Your read parser must
handle FF: a second FF is a System Reset, and anything else is
a meta type and length to skip.
The stream carries raw wire MIDI messages after each delta time: channel
voice messages (running status accepted on writes), system common, and
single-byte real-time messages F8-FE. System Reset travels as the
FF FF escape in both directions: write a delta then FF FF to
send one, and a reset from the instrument is recorded the same way. The
undefined bytes F4 and F5 are quietly dropped.
System Exclusive, or sysex, is how instruments move the big stuff, like
patch banks and sample dumps, in one long message: F0, any number of
data bytes, then F7 to finish. Only the opening F0 takes a delta
time; the data bytes flow without timing until the F7, on writes and
recordings alike. Real MIDI lets real-time messages like clock barge
into the middle of a sysex — the RIA passes them through in place, so
expect the occasional F8-FE byte inside a recorded dump. And if a tempo
echo comes due during a dump, the recording closes the sysex early and
reopens it after — everything arrives, just split into two
F0 … F7 fragments.
Delta times measure from the previous event, so timing stays exact over any song length. Events are anchored to an absolute tick count, and ticks are kept internally in nanoseconds, holding arithmetic rounding below one part per million. The error left over comes from the machine’s clock and transport. Where the RIA is paced by a crystal-driven microsecond timer and delivers over USB full speed, the crystal drifts single-digit milliseconds over a several-minute song and framing sets the moment-to-moment jitter near one millisecond — the same pace as the classic MIDI wire itself.
If your program stops feeding the buffer and resumes, messages already past due play immediately and the timeline continues from there. If you stop reading, the recording drops whole messages rather than backing up, and the timing of everything that survives stays exact. Reads and writes are non-blocking with the same short read/write rules as other non-blocking devices.
Closing a timed output cable blocks until its buffered tail has played
out on schedule. The final notes reach the instrument before close returns,
along with the note-offs that end them, and nothing is left ringing.
sync does the same without closing. It waits for the schedule to catch
up between songs. Both follow the timeline, so a far-future delta
still in the buffer makes them wait that long. If a sysex is still open
when a timed cable closes, the RIA sends its F7 so the instrument is
not left waiting mid-dump. A raw cable has no schedule, so close and
sync flush what is buffered, and they inject no F7.
Near Field Communications (NFC)¶
NFC cards have become a popular media replacement in the retro community, and they map neatly onto the RP6502’s use of “ROM files” in place of “ROM cartridges”. In 1983 you might have grabbed a cartridge with colorful stickers to home in on the exact dopamine hit you were after. NFC cards are cheap and just as easy to decorate, whether with stickers or direct printing. Grab a card, tap it on the reader, and the ROM you want loads instantly.
You’ll need a PN532 card reader with a USB interface. It’s the only reader RIA firmware drives, and it’s cheap — around $10 USD. You’ll also want a card (or fob, or sticker) for each ROM you plan to support. New to NFC? Buy a pack of NTAG215 cards and a sharpie.
Do not buy a kit of separate USB-to-UART and PN532 boards unless you want an unsupported project on your hands. Buy a single board with everything already engineered and ready to use.
With the reader plugged in, run the monitor command SET NFC 2 to
start USB detection. It may probe your other VCP devices with PN532 data
along the way; that’s normal. You’ll hear an error buzz, or two beeps for
success. You can also run status to see whether (NFC) is listed
next to one of your VCP devices.
SET NFC 1 turns the reader on and SET NFC 0 turns it off; the
choice persists across reboots (a successful SET NFC 2 also leaves it
on). SET NFC 86 forgets the paired reader so a later SET NFC 2 can
pair a different one.
From now on, scanning a card produces one of three sounds: an error buzz if something went wrong, two beeps for success, or a single beep for a partial success.
Program each card with the filename and arguments of the ROM to launch.
If you’d load the ROM with LOAD /jigsaw.rp6502, put an NDEF TEXT
record on the card holding just /jigsaw.rp6502 — no load command. A
leading / is implied if you leave it off, and the current working
directory is ignored.
Paths with spaces need quotes, and you can include arguments:
"/My Games/jigsaw.rp6502" cat.bmp
When a card is read, every mounted drive is scanned for the ROM file. On a match, you get two beeps, the 6502 stops, the current drive and directory switch to the ROM’s location, and the new ROM starts loading. If that ROM is already running, you get a single beep and nothing else happens.
To search just one drive, name it in the text record:
MSC0:/encabulator.rp6502
A card may also name an installed ROM, :NAME. That ROM launches the
same way, with the same beeps, except that no drive is scanned and the
current drive and directory stay where they are. A name that is not
installed gives the error buzz.
NFC Device API¶
Applications can take over the NFC reader for advanced uses, or to help
program NFC tags. While the "NFC:" device is open, automatic ROM
launching is suppressed.
int fd = open("NFC:", O_RDWR);
The PN532 reader runs autonomously on the RIA. The 6502 arms operations
with write() and polls results with read(): NFC_CMD_READ
returns the current tag data immediately, NFC_CMD_WRITE arms a write,
and NFC_CMD_CANCEL disarms a pending one. State changes and write
completions are posted to read() automatically.
write() – Commands¶
write() is non-blocking and streaming. A call may consume less than
you passed; resubmit the remaining bytes on a later call.
Byte |
Command |
|---|---|
|
Arm a write |
|
Disarm pending write |
|
Return current tag data |
|
Play success tone 1 |
|
Play success tone 2 |
|
Play error tone |
The NFC_CMD_WRITE payload starts with the start page, a two-byte
length, then the tag data. page is the NTAG page to begin writing at
(page 4 is the start of user data). Data is written in 4-byte pages, and
the final page is zero-padded if the payload isn’t a multiple of 4. The
write arms once the full payload arrives and runs on the current card or
the next one presented. A second NFC_CMD_WRITE overwrites the first.
The payload may be at most 888 bytes; a longer length is rejected with the error tone and never armed. A write also fails (error tone) if it would run past the card’s NDEF data area or target a page below 4, and a zero-length payload completes immediately as a no-op.
NFC_CMD_READ always returns NFC_RESP_READ on the next read();
if no card data is available, the length is zero.
read() – Responses¶
read() is non-blocking and streaming. It returns 0 bytes when there’s
nothing new. Responses may be split across multiple calls, so callers
must buffer and reassemble them. State changes are sent once per change,
including once right after open(). Only the latest state is tracked, so
a rapid transition (such as NFC_RESP_CARD_INSERTED immediately followed
by NFC_RESP_CARD_READY) may be coalesced to the later state if you don’t
read() in between.
Byte |
Meaning |
|---|---|
|
Read result |
|
Armed write complete |
|
State: no reader attached |
|
State: no card present |
|
State: card present, tag data not ready |
|
State: card present, tag data ready |
The NFC_RESP_READ payload is a two-byte length followed by raw tag
data starting at page 0, and it may span multiple read() calls.
In the page layout, pages 0-2 are UID/lock bytes, page 3 is the Capability
Container (CC[2] * 8 = max NDEF bytes), and pages 4+ are user data
(TLV-wrapped NDEF records terminated with 0xFE).
After NFC_RESP_READ or NFC_RESP_WRITE, send one or more tone
commands or play your own sounds. Typically you request reads on
NFC_RESP_CARD_READY and arm writes on NFC_RESP_NO_CARD, but you
can also arm a write after reading and verifying a card. The cached
tag image is not refreshed by a write, so re-present the card before the
next NFC_CMD_READ if you want to read back what you wrote.
PIX Physical Layer¶
None of this is needed to program the machine. What follows is the bus itself, for anyone building a device to put on it.
On the RP6502-PICO, PIX is a physical bus between the RP6502-RIA and the RP6502-VGA. High-bandwidth devices like video systems need a bus of their own. PIX is that bus: an addressable broadcast system that any number of devices can receive, narrow enough to fit the GPIO budget of a Raspberry Pi Pico, wide enough to move data as fast as the 6502 writes.
The signals are PHI2 and PIX0-3. This is a double-data-rate bus. It shifts PIX0-3 left on both transitions of PHI2, so a 32-bit frame is sent in just 4 PHI2 cycles. A PIO block on a Raspberry Pi Pico decodes it, since PIO is essentially a shift register.
Bit 28 (0x10000000) is the framing bit, set in every message. When the bus is idle, an all-zero payload repeats on device ID 7. A receiver synchronizes by checking that PIX0 is high on a falling transition of PHI2; if it isn’t, stall until the next clock cycle.
Bits 31-29 (0xE0000000) hold the device ID of a message:
Device 0 — XRAM broadcasts only. The extended registers of device 0 are handled inside the RIA and are never sent on the bus.
Device 1 — the VGA.
Devices 2-6 — open for user expansion.
Device 7 — synchronization. (0xF0000000 is hard to miss on test equipment.)
For devices 1-6, the remaining bits address an extended register:
Bits 27-24 (0x0F000000) — the channel ID; each device can have 16 channels.
Bits 23-16 (0x00FF0000) — the register address within that channel.
Bits 15-0 (0x0000FFFF) — the value to store in the register.
The RIA broadcasts every change to XRAM as a device 0 message, with the XRAM address in bits 15-0 and the XRAM data in bits 23-16. Each device keeps a local replica of the XRAM it uses, typically all 64 KB.