docs: keyboard/mouse protocol spec — LK401 (LK201 protocol) + VSXXX-AA mouse, sources, MAME lk201 device

Distils public docs into reference/keyboard-mouse/PROTOCOL.md for the level-1
Keyboard (27) / Mouse (26) device tests:
- keyboard: 4800 8N1 RS-423, self-test via FD -> 01 00 00 00 (errors 3D/3E)
- mouse: 4800 8O1 RS-232, self-test via 'T' -> 4-byte report (errors 3D/3E, >=0x20 fatal)
- separate ports: DUART2 A=keyboard, B=mouse (NOT daisy-chained)
MAME already has an LK201 device (src/mame/dec/lk201.cpp/.h) but no DEC mouse.
This commit is contained in:
SDD committed 2026-10-16 11:38:52 +03:00
1 parent 23e508f075
commit 3fd1e13b2e
129 files changed
+401708

No files matched your search

@@ -0,0 +1,152 @@
// elkay stage 1: LK401 serial sniffer and command console.
//
// Every byte from the keyboard is printed to the USB serial console, and
// commands typed on the console are sent to the keyboard. Used to confirm the
// wiring and record the keycode table (docs/keymap.md) before the HID stage.
//
// Console input, one line at a time:
// hex bytes e.g. "13 84" (Lock LED on), sent as typed
// init the initialisation sequence from docs/protocol.md
// reset power-up reset; the keyboard repeats its self-test report
// id request the keyboard ID
// bell sound the bell
// click / noclick
// help
#include <Arduino.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "lk.h"
static const uint32_t kConsoleBaud = 115200;
static const uint8_t kActivityLed = LED_BUILTIN;
static const unsigned long kActivityMs = 30;
static char line[64];
static uint8_t lineLen;
static unsigned long activityUntil;
static void printHex(uint8_t b) {
if (b < 0x10) Serial.print('0');
Serial.print(b, HEX);
}
static void printStamp() {
Serial.print(millis());
Serial.print('\t');
}
static void send(uint8_t b) {
Serial1.write(b);
printStamp();
Serial.print(F("-> "));
printHex(b);
Serial.println();
}
static void sendInit() {
send(lk::kRequestId);
send(lk::kSetDefaults);
send(lk::kEnableLk401);
for (uint8_t d = 1; d <= lk::kDivisions; d++) {
send(lk::setMode(d, lk::kModeUpDown));
}
send(lk::kDisableKeyclick);
send(lk::kDisableCtrlClick);
send(lk::kLedOff);
send(lk::param(lk::kLedAll));
}
static void printHelp() {
Serial.println(F("elkay console: hex bytes | init | reset | id | bell | "
"click | noclick | help"));
}
// Sends each hex byte in the line; returns false if any token is not hex.
static bool sendHex(char *s) {
uint8_t bytes[sizeof(line) / 2];
uint8_t n = 0;
for (char *tok = strtok(s, " ,"); tok; tok = strtok(nullptr, " ,")) {
char *end;
unsigned long v = strtoul(tok, &end, 16);
if (*end != '\0' || v > 0xFF || n == sizeof(bytes)) return false;
bytes[n++] = v;
}
for (uint8_t i = 0; i < n; i++) send(bytes[i]);
return true;
}
static void runCommand(char *cmd) {
if (!strcmp(cmd, "")) return;
if (!strcmp(cmd, "help")) {
printHelp();
} else if (!strcmp(cmd, "init")) {
sendInit();
} else if (!strcmp(cmd, "reset")) {
send(lk::kPowerUpReset);
} else if (!strcmp(cmd, "id")) {
send(lk::kRequestId);
} else if (!strcmp(cmd, "bell")) {
send(lk::kSoundBell);
} else if (!strcmp(cmd, "click")) {
send(lk::kEnableKeyclick);
send(lk::param(2));
} else if (!strcmp(cmd, "noclick")) {
send(lk::kDisableKeyclick);
} else if (!sendHex(cmd)) {
Serial.print(F("? "));
Serial.println(cmd);
}
}
static void readConsole() {
while (Serial.available()) {
char c = Serial.read();
if (c == '\r' || c == '\n') {
line[lineLen] = '\0';
lineLen = 0;
runCommand(line);
} else if (lineLen < sizeof(line) - 1) {
line[lineLen++] = tolower(c);
}
}
}
static void readKeyboard() {
while (Serial1.available()) {
uint8_t b = Serial1.read();
digitalWrite(kActivityLed, HIGH);
activityUntil = millis() + kActivityMs;
printStamp();
Serial.print(F("<- "));
printHex(b);
const char *name = lk::responseName(b);
if (name) {
Serial.print(' ');
Serial.print(name);
}
Serial.println();
}
}
void setup() {
pinMode(kActivityLed, OUTPUT);
Serial.begin(kConsoleBaud);
Serial1.begin(lk::kBaud);
// Hold RX at idle when nothing drives it, so an unconnected input does not
// read noise as bytes. The UART still owns the pin; this only sets the pull-up.
pinMode(0, INPUT_PULLUP);
printHelp();
}
void loop() {
readKeyboard();
readConsole();
if (activityUntil && (long)(millis() - activityUntil) >= 0) {
digitalWrite(kActivityLed, LOW);
activityUntil = 0;
}
}