# VT1200 keyboard & mouse serial protocol — distilled specification Everything here is attributed to a source in `SOURCES.md`, and each fact is tagged **[confirmed]**, **[probable]** or **[unknown]**. ## 0. Bottom line There is **no VT1200-specific protocol document**. What exists is documentation for the **LK201 keyboard** (and its successor the **LK401**) and for the **VSXXX-AA/-GA mouse / VSXXX-AB tablet**. The evidence that these *are* the VT1200's parts — and therefore that their protocols apply — is strong and is laid out in §1. The two facts the level-1 Keyboard/Mouse tests need (the self-test command and its reply) **are** covered by public sources: it is `FD` → `01 00 00 00` for the keyboard and `T` → a 4-byte report for the mouse. See §3.5 and §4.4. Status of the question in the brief: **closest-part-only**. The VT1200's own manuals never describe the wire protocol. --- ## 1. Which parts the VT1200 uses — the evidence | Fact | Value | Confidence | Source | |---|---|---|---| | Keyboard family | **LK400 series**; `LK401-XX` standard, `LK402-XX` word-processing | **[confirmed]** | `EK-V1000-SV-002_service-guide.txt` §1.1.3 | | Keyboard, ROM side | Terminal firmware source file is named **`LK401.C`**; ROM strings *"The keyboard ROM reported a fault at power-up"*, *"A key was held down during power-up"* | **[confirmed]** | `../rom-strings.txt` (offsets 0x0E4AB9, 0x0E4B2A, 0x0E4B58) | | Keyboard, model designation | `LK401 Keyboard` appears as a heading in the VT1200 user guide | **[confirmed]** | `../../docs/EK-V1200-UG-001_installing-VT1200.txt` (TOC §"The Keyboard") | | LK401 electrical/firmware relation to LK201 | "not intended to be a direct replacement for the LK201, but is **electrically and firmware functionally compatible**" | **[confirmed]** | `vt100net-lk401.txt` line 239 | | LK401 host protocol = LK201 protocol | LK401 spec says the terminal→keyboard protocol "can be found in the LK400 Keyboard Firmware Requirements"; `lkkbd.c` and `elkay` implement one driver for both, and `lkkbd.c` enables extra LK401 keys with one added command | **[probable]** | `vt100net-lk401.txt` line 678; `linux-lkkbd.c` lines 39–46; `elkay/docs/protocol.md` | | Mouse / tablet | 3-button pointing device; service guide calls the device test "mouse **or tablet**"; a `VSXXX-AB` tablet is the optional alternative | **[confirmed]** device type / **[probable]** exact model `VSXXX-AA` | `EK-V1000-SV-002_service-guide.txt`; `vcb02-az-glgab-mn-feb86-text.txt` App. C/D | So: **keyboard = LK401, which speaks the LK201 serial protocol; mouse = VSXXX-AA (hockey-puck), with the VSXXX-AB tablet as the alternate the firmware also supports.** --- ## 2. Topology — separate ports, NOT daisy-chained This is the one place the VT1200 differs from the common LK201/VSXXX picture (where the mouse hangs off the keyboard). * The terminal ROM's own test list names **two separate DUART2 channels**: `H - DUART2, CHANNEL A (KEYBOARD)` and `I - DUART2, CHANNEL B (MOUSE)`. **[confirmed]** (`../rom-strings.txt`, offsets 0x179795 / nearby; `../level1-test-sequence.md`). * The service guide tells the technician to "Disconnect the keyboard, mouse, or printer **from the rear of the system box**"; the VT1200 user guide says "The keyboard cable connects to the system box" and "The mouse connects to the system box". **[confirmed]** * The service guide provides a **separate mouse-port loopback connector** (12-25628-01) and a distinct *Mouse Port Test* (19) vs *Keyboard Port Test* (18). **[confirmed]** * The VCB02 (the architectural ancestor, same Q-bus/DUART design) likewise keeps the keyboard on DUART **channel A** and the pointing device on **channel B**, with "the other port … for any other serial device, probably a mouse or a tablet". **[confirmed]** (`vcb02-az-glgab-mn-feb86-text.txt` lines ~9635, and the DUART reset-default table.) **Conclusion [confirmed]: on the VT1200 the keyboard is on DUART2-A and the mouse is on DUART2-B, as two independent serial links. The mouse is NOT daisy-chained through the keyboard.** This matches the already-wired MAME SCN2681 at both DUART windows. --- ## 3. Keyboard (LK401, LK201 protocol) ### 3.1 Wire parameters | Parameter | Value | Confidence | Source | |---|---|---|---| | Baud | **4800** | **[confirmed]** | `netbsd-lk201-interface.html`; `vt100net-lk401.txt` §3.5.4 "4800 baud only"; MAME `lk201.cpp` `set_rate(4800)` | | Data bits | **8** | **[confirmed]** | LK401 §3.5.5 "start bit, 8 data bits, stop bit"; `vt100net-lk401.txt` | | Parity | **none** | **[confirmed]** | LK401 §3.5.5 has no parity element (8N1); `netbsd-lk201-interface.html` ("8N1"); MAME `set_data_frame(1, 8, PARITY_NONE, STOP_BITS_1)` | | Stop bits | **1** | **[confirmed]** | as above | | Levels | **EIA RS-423**, ±5 V, powered from +12 V | **[confirmed]** | `netbsd-lk201-interface.html`; `elkay/docs/protocol.md` | | Connector | 4P4C modular: 1=host TX→kbd, 2=GND, 3=+12 V, 4=kbd→host RX | **[probable]** (pin order differs between write-ups; verify at the cable) | `linux-lkkbd.c` header; `elkay/docs/protocol.md` | ### 3.2 Framing / command encoding Host→keyboard: a command is one or more bytes. **Bit 7 of the last byte of every command is set** (the "PARAMS" flag); bit 7 of a non-final byte is clear, and byte bit 0 is the "TYPE" flag (0 = transmission command, 1 = peripheral command). **[confirmed]** (`vcb02-az-glgab-mn-feb86-text.txt` §B.5.5.1–B.5.5.2.) Keyboard→host: single bytes. ### 3.3 Host → keyboard command set (VCB02 Table B-7, cross-checked against drivers) | Bytes | Meaning | Conf. | Source | |---|---|---|---| | `FD` | **Jump to Power-Up / Reinitiate Keyboard** — keyboard runs its self-test and sends the 4-byte power-up sequence (§3.5) | **[confirmed]** | VCB02 B.5.5.3 "The jump to power-up command is FD (hexadecimal)"; `linux-lkkbd.c` `LK_CMD_POWERCYCLE_RESET 0xfd`; `vt100net-lk201-keyboard.html` trace | | `AB` | Request keyboard ID — returns the 2-byte ID, does not jump to power-up | **[confirmed]** | VCB02 Table B-7; `linux-lkkbd.c`; `netbsd-lk201-interface.html` | | `D3` | Reinstate defaults | **[confirmed]** | VCB02 Table B-7; `linux-lkkbd.c` | | `CB` | Jump to test mode (factory) → replies `B8` | **[confirmed]** | VCB02 Table B-7; `netbsd-lk201-interface.html` | | `13 p` / `11 p` | LEDs on / off; `p` bitmask: `01` Wait, `02` Compose, `04` Lock, `08` Hold Screen (parameter high bit set, e.g. `8F` = all) | **[confirmed]** | VCB02 Table B-7 + Fig. B-11; `netbsd-lk201-interface.html` | | `99` | Disable keyclick | **[confirmed]** | VCB02 Table B-7 | | `1B v` | Enable keyclick, volume `v` (`0x80`\|0–7, 0 = loudest) | **[confirmed]** | VCB02 Table B-7 + Fig. B-13 | | `B9` | Disable Ctrl-key click | **[confirmed]** | VCB02 Table B-7 | | `BB v` | Enable Ctrl-key click | **[confirmed]** | VCB02 Table B-7 | | `9F` | Sound a keyclick | **[confirmed]** | VCB02 Table B-7 | | `A1` | Disable bell | **[confirmed]** | VCB02 Table B-7 | | `23 v` | Enable bell, volume | **[confirmed]** | VCB02 Table B-7 | | `A7` | Sound bell | **[confirmed]** | VCB02 Table B-7 | | `D1` | Temporary auto-repeat inhibit | **[confirmed]** | VCB02 Table B-7 | | `E3` / `E1` | Enable / disable auto-repeat across keyboard | **[confirmed]** | VCB02 Table B-7 | | `D9` | Change all auto-repeat to down-only | **[confirmed]** | VCB02 Table B-7 | | `8B` | Resume keyboard transmission | **[confirmed]** | VCB02 Table B-7 | | `89` (VCB02 OCR) / `B9` (NetBSD) | Inhibit keyboard transmission | **[unknown]** — the two sources disagree (`89` vs `B9`); treat as unresolved | VCB02 Table B-7; `netbsd-lk201-interface.html` | | `0x86 \| d<<3` | Set division *d* (1–14) to a mode: low bits `00` down, `10` auto-down, `11` down/up | **[confirmed]** | VCB02 B.5.5.4; `netbsd-lk201-interface.html`; `elkay/docs/protocol.md` | | `E9` | **LK401 mode**: enables the extra keys (LAlt, RAlt, RCompose, RShift). LK401-only. | **[confirmed]** | `linux-lkkbd.c` `LK_CMD_ENABLE_LK401`; `elkay/docs/protocol.md` | > ⚠ The level-0 I/O data-path anomaly recorded in `../level1-test-sequence.md` says the > firmware writes `0xE1` to DUART2-A and expects a **10-byte** reply. `0xE1` is documented as > "disable auto-repeat across keyboard", which has **no reply** in any source here. So either > that level-0 write is not what it looks like, or `0xE1` is a transcription error. **[unknown]** > — flag it for the ROM/Ghidra side; it is not the keyboard *self-test*. ### 3.4 Keyboard → host special codes (single bytes) `B3` All-Up (last down/up key released) · `B4` Metronome/auto-repeat tick · `B5` Output Error (buffer overflow) · `B6` Input Error (bad command) · `B7` KBD-locked ACK · `B8` Test-mode ACK · `B9` Prefix-to-keys-down · `BA` Mode-change ACK · `7F` reserved. **[confirmed]** (VCB02 Table B-4; `netbsd-lk201-interface.html`; `linux-lkkbd.c`.) Ordinary keycodes are single bytes `> 0x40`; every key has one unique code, no shift/ctrl variants. **[confirmed]** Full keycode table: `netbsd-lk201-keycodes.html` (14 divisions). ### 3.5 Power-up / self-test — **what the level-1 Keyboard Test (error 27) exercises** On power-up, **or on receiving `FD`**, the keyboard runs a self-test in ≤70 ms and sends a **4-byte** result. **[confirmed]** (VCB02 §B.5.4.3; NetBSD; LK401 §3.3.4.6; and the captured VT320 trace in `vt100net-lk201-keyboard.html`.) | Byte | Meaning | Value | |---|---|---| | 1 | Keyboard ID — firmware | `01` (`lkkbd.c` also accepts `02` = LK401 from the `AB` reply) | | 2 | Keyboard ID — hardware | `00` | | 3 | **Error code** | `00` = OK · `3D` = key held down at power-up · `3E` = ROM/RAM self-test failure | | 4 | Keycode of the key held down, else `00` | e.g. `AE` in the stuck-shift trace | * Healthy reply: **`01 00 00 00`**. Captured live on a VT320/LK201 in `vt100net-lk201-keyboard.html`; emulated identically by `lk201emu` (`resetkb()` sends `1,0,0,0`) and expected by `linux-lkkbd.c`. **[confirmed]** * On a stuck key the reply becomes `01 00 3D ` and the terminal re-issues `FD`. **[confirmed]** (`vt100net-lk201-keyboard.html`.) * "If the self-test fails and the error is fatal, the keyboard is unable to transmit." **[confirmed]** (VCB02 §B.5.4.3.) **⇒ Implement the device-test model to: on receiving `FD`, reply `01 00 00 00`.** The terminal's own display of the error code (message *"Keyboard Error - 27"*) comes from its interpretation of byte 3; "any error code" from the device is what the service guide means by "display any error code". ### 3.6 Wire parameters under test — does the terminal's test override them? No source shows the Keyboard/Mouse tests changing baud/parity. The self-test is a normal command on the already-configured port. **[probable]** — the VCB02 DUART reset-default table sets channel A to "8 data, parity Off, 4800" and channel B to "8 data, parity **Odd**, 4800" (`vcb02-az-glgab-mn-feb86-text.txt` §5.5.2.5, Table 5-9). Treat 4800/8N1 (kbd) and 4800/8O1 (mouse) as the port defaults; the VT1200's SCN2681 should be programmed the same way. **[probable]** --- ## 4. Mouse / tablet (VSXXX-AA / VSXXX-AB) ### 4.1 Wire parameters | Parameter | Value | Confidence | Source | |---|---|---|---| | Baud | **4800** | **[confirmed]** | VCB02 §C.4; `linux-vsxxxaa.c` | | Data bits | **8** | **[confirmed]** | VCB02 §C.6.1 | | Parity | **ODD** | **[confirmed]** | VCB02 §C.6.1; `allpinouts-dec-7pin.html`; `linux-vsxxxaa.c` ("4800, 8o1") | | Stop bits | **1** | **[confirmed]** | VCB02 §C.6.1 | | Levels | RS-232-compatible (mark < −6 V, space > +4.6 V) | **[confirmed]** | VCB02 §C.5 | | Connector | 7-pin micro-DIN: 1 GND, 2 TxD(mouse out), 3 RxD(into mouse), 4 −12 V, 5 +5 V, 6 n/c, 7 device-present→pin1 | **[confirmed]** | VCB02 Table C-1; `vaxarchive-vsxxx.html` | | Duplex | half-duplex; a host byte during a mouse transmission aborts it | **[confirmed]** | VCB02 §C.6.1 | ### 4.2 Host → mouse commands (printable ASCII) `R` (0x52) incremental stream mode · `D` (0x44) prompt mode (power-up default) · `P` (0x50) request position (also enters prompt mode) · **`T` (0x54) self-test & identify** · `S` (0x53) select report format · `Z` (0x5A) reserved. Invalid commands are ignored. **[confirmed]** (VCB02 Table C-2; `linux-vsxxxaa.c`; `vaxarchive-vsxxx.html`.) ### 4.3 Mouse → host position report (3 bytes, incremental mode) ``` bit 7 6 5 4 3 2 1 0 1 0 0 SX SY L M R byte 1 (header 100) 0 X6 X5 X4 X3 X2 X1 X0 byte 2 0 Y6 Y5 Y4 Y3 Y2 Y1 Y0 byte 3 ``` `SX/SY` sign (1 = positive), `L/M/R` buttons (1 = depressed), X/Y in resolution units since last report. **[confirmed]** (VCB02 Fig. C-4; `linux-vsxxxaa.c`; `vaxarchive-vsxxx.html`.) ### 4.4 Power-up / self-test — **what the level-1 Mouse Test (error 26) exercises** On power-up, **or on receiving `T`**, the device sends a **4-byte self-test report**, starting bits `101` (0xA0). **[confirmed]** (VCB02 §C.6.5 and Fig. C-5; `linux-vsxxxaa.c`; captured in `vaxarchive-vsxxx.html`.) ``` byte 1: 1 0 1 0 R3 R2 R1 R0 revision byte 2: 0 M2 M1 M0 D3 D2 D1 D0 M = manufacturer loc.; device code D: 0010 = mouse, 0100 = tablet byte 3: 0 E6 E5 E4 E3 E2 E1 E0 error code byte 4: 0 0 0 0 0 L M R button state, or the failed button if byte 3 = 3D ``` * Byte-3 error codes: `00` = OK; **`3E` = ROM/RAM checksum error**; **`3D` = button error**; **codes ≥ 0x20 are fatal, < 0x20 non-fatal**. **[confirmed]** (VCB02 §C.6.5; `vsxxxaa.c` treats ≤0x1f as "no serious error".) * Self-test leaves the device in the reset/power-up state; data sent during self-test is ignored. Self-test completes within 500 ms (power-up within 1 s). **[confirmed]** (VCB02 §C.6.4/§C.6.7.) * Recommended host init (VCB02 §C.7.1): configure port for `4800 8O1`; read the power-up self-test report; if none within 1 s, send `T`; then `S`; then `R` (or `D`). **[confirmed]** **⇒ Implement the device-test model to: on receiving `T`, reply a well-formed self-test report (e.g. header `0xA0|rev`, device byte `0x02`, error `00`, buttons `00`).** This is exactly "firmware requests that the mouse do its self-test and display any error code". ### 4.5 Tablet (VSXXX-AB) — for completeness Same connector, same serial parameters, same command set plus extras; power-up self-test differs only in device code (`0100`) and a 5-byte absolute-position report (header `110`/0xC0). **[confirmed]** (VCB02 App. D, §C.7.4; `linux-vsxxxaa.c` ABS branch.) Not needed for the level-1 mouse test unless a tablet is what's attached. **Coordinate range:** the absolute position spans **`0..2199`** over the active area (VCB02 App. D.1). **[confirmed]** **How the terminal chooses** — there is no separate command and no setup record. The **device code in the `T` reply is the whole discriminator**, and the host then frames **3-byte relative** reports for a mouse (`0010`) or **5-byte absolute** ones for a tablet (`0100`). **[confirmed]** against the running OS in the harness: it sends `T`, reads `A0 04 00 00` from a tablet, and then reads RHRB in **groups of 5** — groups of 3 with a mouse. --- ## 5. How a device failure is reported back (both devices) The terminal displays an error code it derived from the device's self-test reply: keyboard error byte 3 (`3E`/`3D`), mouse error byte 3 (`3E`/`3D`, or ≥0x20 fatal). The service guide's troubleshooting table then says, for **error 27**, "Replace the keyboard"; for **error 26**, "run the Mouse Port Test with external loopback … otherwise replace the mouse". **[confirmed]** (`EK-V1000-SV-002_service-guide.txt` Table 3-1.) --- ## 6. MAME relevance (drop-in candidates) * **Keyboard: MAME already has a DEC LK201 device.** `DECLARE_DEVICE_TYPE(LK201, lk201_device)`, device name `"lk201"`, "DEC LK201 Keyboard", in `src/mame/dec/lk201.h` / `src/mame/dec/lk201.cpp`. It is a `device_t + device_serial_interface`, i.e. **it expects a DUART-style serial channel** — it would attach to the same SCN2681 channel A we already wired. It models the 68HC05 keyboard MCU and **executes the real keyboard firmware ROM** (`23-001s9-00.bin`), so the protocol behaviour (including the `FD` self-test reply) comes from that ROM, not from C. It wires as `m_lk201->tx_handler()...`/`m_lk201->rx_w()` (see `src/mame/dec/vt240.cpp`, which places it behind an i8251). **It does NOT model the mouse.** * **Mouse: no DEC mouse device exists in MAME** (checked `src/mame/dec/` and the tree; no `vsxxx*`). We would need a small HLE model, but §4 gives the complete protocol, so this is low-risk. * **Caveat / gap:** MAME's `lk201` wants the **6805** LK201 ROM `23-001s9-00.bin`, which is **not** in the bitsavers zip we have (that is the **8051** `23-004M2`). Running MAME's device therefore needs that ROM sourced first; otherwise implement a HLE keyboard that answers `FD` with `01 00 00 00` (and optionally honours `AB`, `13/11`, mode-set `BA` ACKs). For merely passing level-1 tests 26/27, an HLE keyboard + HLE mouse driven off DUART2-A/B is the smallest sufficient model and needs no ROMs at all. --- ## 7. Confidence summary | Item | Status | |---|---| | VT1200 uses LK401 keyboard / VSXXX-AA mouse | **confirmed** (service guide + ROM) | | Keyboard LK401 speaks LK201 protocol | **probable** (LK401 spec: "firmware functionally compatible"; shared drivers) | | Keyboard wire: 4800 8N1, RS-423 | **confirmed** | | Keyboard self-test command `FD` → `01 00 00 00` (+ `3D`/`3E` errors) | **confirmed** | | Mouse wire: 4800 8O1, RS-232, 7-pin DIN | **confirmed** | | Mouse self-test command `T` → 4-byte report (`3D`/`3E`, ≥0x20 fatal) | **confirmed** | | Separate ports (DUART2-A kbd, DUART2-B mouse); no daisy chain | **confirmed** | | Exact LK401 firmware protocol (LK400 Firmware Requirements) | **unknown** — document not public | | `0xE1`+10-byte level-0 anomaly | **unknown** — not explained by any source here | | Inhibit-transmission opcode (`89` vs `B9`) | **unknown** — sources disagree | | 4P4C keyboard pin order | **probable** — verify at cable |