RCX IR protocol
A ~38 kHz infrared carrier, and it runs both ways. Not a remote, a serial port made of light. The RCX talks BOTH ways at 2400 baud over infrared, so a computer can download a program into it and read a sensor back. Every other link here is one-way.
Link layer
- Near-IR (≈940 nm) serial link, 2400 baud (8 data bits, odd parity, 1 stop); some custom firmware runs a 4800 baud "fast" mode. It is a UART-over-IR data link, not a remote-control command protocol like PF / 76 kHz IR family. Low-level details: RCX Internals (Kekoa Proudfoot).
- Carrier: the physical layer is modulated on a ~38 kHz carrier — each serial bit's "space" is a 38 kHz burst, "mark" is dark, so a standard 38 kHz demodulator (TSOP38238) recovers the UART stream. Any tower emulator has to modulate at 38 kHz for the same reason. The link is still noise-prone vs modern PF because it's a bare 2400-baud UART with no packet-level FEC, not because it lacks a carrier.
- The tower echoes what it sends (IR is reflected back), so software discards the echo.
Packet format
0x55 0xFF 0x00 header
then, for every data byte: <byte> <~byte> (byte followed by its bitwise complement)
payload = opcode, args...
trailer = checksum, ~checksum (checksum = sum of payload bytes, low 8 bits)
The reply uses the same framing. Opcodes have a toggle bit that flips between consecutive commands so the RCX can ignore duplicates.
What you can send
- Direct/immediate commands (motor on/off/dir, play sound, poll a sensor/variable).
- Program download (5 program slots) and firmware download (
nqc -firmware firm0332.lgo). - Messages (1 byte) for RCX-to-RCX or PC coordination.
Authoritative references
- Kekoa Proudfoot, RCX Internals — the canonical opcode list and packet spec.
- Full command list: RCX opcode table.
- Tooling: NQC · BrickCC, LegoRcxPy, BrickLogo.
The RCX also has a light sensor input (reads reflected/ambient brightness) — same physics VLL exploits, but here it's used for sensing, not comms.
Sources for this block
The handheld remote's frame
LEGO Remote Control (9738) rides the same link. Its payload is one opcode and a big-endian 16-bit mask of which keys are down:
55 FF 00 D2 2D hi ~hi lo ~lo csum ~csum
checksum = (D2 + hi + lo) & 0xFF, sent with its complement. Motor A forward, key 0x0008, comes out as 55 FF 00 D2 2D 00 FF 08 F7 DA 25, which matches a capture of the real remote byte for byte.
No toggle bit. Command opcodes flip bit 0x08 so the brick can reject repeats; the remote sends a fixed 0xD2, and holding a button re-sends the identical packet. The brick answers nothing.
Only the six motor bits combine. The firmware reads those independently, so 0x0018 is A and B forward and 0x0038 is all three. Every other key is dispatched by exact match on the whole 16-bit word, so 0x4008 triggers neither Stop nor motor A. Messages, program slots, Stop and Sound have to travel alone.
Five rules for an emitter: send the combined mask rather than one key per frame; repeat while held, every 50 to 80 ms; on release send 0x0000 and not Stop; send non-motor keys alone; and hold the opcode at 0xD2.
One firmware bug. In firm0309, shipped with RIS 1.0 and 1.5, the test for C-reverse reads the byte after the mask, which is the checksum. Since csum = (0xD2 + hi + lo) & 0xFF, its bit 0 is bit 0 of hi XOR bit 0 of lo, which is C-down XOR Message 1. A lone C-down works by coincidence, and some unrelated masks drive C backwards — Message 1, 0x0001, has an odd checksum and does exactly that. Fixed in firm0328 and firm0332. On a Scout it never applied, because C is the light output there rather than a motor.