VLL — Visible Light Link
No carrier at all, just light on and off, and it runs one way. A brick with a light sensor counts flashes. Bright and dark alternate, and the RATIO of the two says which bit it was, so the sender's clock can drift by a third and the brick still reads it. That tolerance is why a phone torch works. The Micro Scout, Code Pilot and Scout share one code set.
Frame
One command = 10 bits = 3-bit checksum + 7-bit data, wrapped in init/start/stop:
- Init / preamble — light ON ≈ 400 ms (some impls use 1000 ms, see Implementations below) before anything.
- Start — OFF 20 ms.
- Checksum — 3 bits, MSB first (see Checksum below).
- Data — 7 bits, MSB first.
- Stop — ON 20 ms, OFF 60 ms, ON 120 ms (some impls: just ON 20 / OFF 60).
The frame transmits [checksum:3 bits][data:7 bits], both MSB first.
Bit encoding (unit = 20 ms)
| Bit | Light ON | Light OFF |
|---|---|---|
| 0 | 40 ms (2 units) | 20 ms (1 unit) |
| 1 | 20 ms (1 unit) | 40 ms (2 units) |
Total per bit = 60 ms either way (only the ON/OFF ratio differs).
Checksum
A 3-bit checksum is prepended (MSB first) to each 7-bit data value:
function checksum(n) { // n = 7-bit data value
return 7 - ((n + (n >> 2) + (n >> 4)) & 7);
}
Verified against all 27 commands in rcxvll.nqh (pbrick) and used verbatim by Flash Force, lego-blockly and elecbrick (Doug Eaton).
| Command | data n | checksum |
|---|---|---|
| Motor forward | 0 | 7 |
| Motor reverse | 1 | 6 |
| Beep 1 | 4 | 2 |
| Beep 5 | 8 | 5 |
| Run program | 33 | 4 |
| Delete program | 34 | 3 |
Direct commands
The 7-bit data values themselves. The Micro Scout shares this codebase with Code Pilot and Scout.
| Code | Binary | Action |
|---|---|---|
| 0 | 0000000 | Motor forward |
| 1 | 0000001 | Motor reverse |
| 10 | 0001010 | Stop motors (see note) |
| 4 | 0000100 | Beep 1 |
| 5 | 0000101 | Beep 2 |
| 6 | 0000110 | Beep 3 |
| 7 | 0000111 | Beep 4 |
| 8 | 0001000 | Beep 5 |
| 33 | 0100001 | Run stored program |
| 34 | 0100010 | Delete stored program |
| 70 | 1000110 | "Next" (official spec; no effect on device) |
| 71 | 1000111 | Reset (official spec; no effect on device) |
Script commands
| Code | Action | Code | Action | |
|---|---|---|---|---|
| 16 | Motor fwd 0.5 s | 24 | Beep 1 | |
| 17 | Motor fwd 1 s | 25 | Beep 2 | |
| 18 | Motor fwd 2 s | 26 | Beep 3 | |
| 19 | Motor fwd 5 s | 27 | Beep 4 | |
| 20 | Motor rev 0.5 s | 28 | Beep 5 | |
| 21 | Motor rev 1 s | 29 | Wait for light | |
| 22 | Motor rev 2 s | 30 | Seek light | |
| 23 | Motor rev 5 s | 31 | Code (talk to another brick) | |
| 32 | Keep alive |
Build a program = Delete (34), then script steps 16–32, then Run (33).
Notes and gaps
- Stop motors: the official Scout SDK and rcxvll.nqh both define code 10 = Motor Stop (Direct). So 10 is the officially-listed stop code (per those two sources) — but it did nothing on a test Micro Scout (firmware/timing-dependent?). The elecbrick "15" (also used by adriansieber) is not in any official list. In practice no direct stop code worked reliably.
- 70 (Next) / 71 (Reset) are real, documented MicroScout codes (Scout SDK + barcode card), but likewise produced no effect on-device. They stay on this page because they are documented.
- Codes 2, 3, 9, 11–14 are unused/unknown for the MicroScout. (On Code Pilot, 9 = Sound Mute, 11 = Motor & Sound, 12–15 = fixed programs, 96–127 = touch/tones/numbers/speed/tacho — see the SDK table.)
- Device-confirmed working: motor 0/1, beeps 4–8, Run 33, Delete 34, and script builder 16–32.
Implementations
Four independent implementations agree on the core protocol:
| Source | Checksum | bit0/bit1 | Preamble | Emitter |
|---|---|---|---|---|
| Flash Force | yes | 40/20 · 20/40 | 400 ms | phone torch |
| rcxvll.nqh (pbrick) | yes | same | 400 ms | LEGO 9V light brick on RCX |
| lego-blockly | yes | same | 1000 ms | LED on a serial control line |
| elecbrick (Doug Eaton) | yes | same | — | opcode generator |
The protocol is identical everywhere; the tunable parts are preamble length and stop-bit tail (reliability knobs).
Practical
- Timing tolerance: Flash Force works at scale 1.05–1.35× (default 1.2×).
- Needs strong ON/OFF contrast; hold the emitter close (1–3 cm), a dim room helps.
- Works with any light the sensor sees — visible or IR (see IR LED (invisible VLL)).
Invisible light works too
Confirmed on hardware, 19 July 2026: a full frame over invisible infrared programmed a Micro Scout at 30 to 40 cm on direct aim, from the bridge's own infrared LED. Better range than the visible path, and nothing to watch — the brick just obeys.
The sensor is silicon, so it answers to near-infrared from 700 to 1100 nm, and it carries no infrared-cut filter. A 940 nm television remote sets it off, which is the one-minute proof: run a light-reactive program, Seek Light or Light Control, and blast the sensor with a remote. If it reacts, infrared gets through, and 850 nm will do better.
850 nm gives the strongest response and only a faint dark-red glow. 940 nm is fully invisible, but weaker, so go brighter or closer.
⚠️ LEGO's own tower sent VLL on a VISIBLE red LED, and its 940 nm emitter was the RCX serial channel instead. Read the precedent the wrong way round and you conclude infrared cannot carry VLL.
Driving the light
A pin, a resistor, the diode: resistor between pin and anode, cathode to ground. A 5 mm LED at 10 to 15 mA is inside what a pin will give, and the sensor is millimetres away, so nothing needs driving hard. About 220 ohm suits a visible LED, about 150 ohm a red or infrared one, whose forward voltage is lower — which also matters at 3.3 V, where a white LED comes out dim. The sensor is red-biased, so red wins twice.
⚠️ DRIVE IT BASEBAND — plain on and off, no carrier. A 38 kHz modulated LED is never decoded, and that was the real bug behind an old "the onboard infrared is too weak" conclusion: a stray carrier, not brightness. Power Functions (IR) and the 76 kHz IR family need the light modulated; this needs it bare.
The trade-off is aim: VLL wants point-blank and straight on, which suits something held in the hand.