mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
harness: real LK401 keyboard + VSXXX-AA mouse devices on DUART2
Add the terminal's actual input parts to the harness as device models on the already-modelled SCN2681 at DUART2: a DEC LK401 keyboard (channel A, 4800 8N1) and a DEC VSXXX-AA mouse (channel B, 4800 8O1), both driven by MAME's host input and wired bit-by-bit to the DUART (rx_a_w/rx_b_w and a_tx_cb/b_tx_cb). Route: the docs, not MAME's lk201. MAME's lk201 device executes the 68HC05 firmware 23-001s9-00.bin, which is not in our ROM set (the downloaded zip only carries the 8051 23-004M2-00.bin, MAME's unused second region), so route 1 is unusable without sourcing that ROM. The models follow PROTOCOL.md: the keyboard answers FD with the 4-byte self-test and reports documented keycodes honouring the division modes; the mouse answers T with its 4-byte self-test and streams 3-byte incremental reports. One physical-layer detail was required: the SCN2681 receiver mis-samples the first byte after a receiver reset unless the line is explicitly idle-high before the start bit. Both models now hold TxD at mark when idle (which is faithful anyway) -- isolated evidence: bit-banging 0x55 into rx_a_w gives RHRA=D5 with no idle-high, RHRA=55 with four idle-high bits. Verified (VT1200_SKIP_LEVEL1=1, input_inject.lua): the OS reads the keyboard keycode 0xC2 (A) and ~900 bytes of VSXXX reports (dx=+16/+32); after the self-test it switches the mouse to stream mode; the X11 cursor arrow is visible at frame 4400 and has moved by frame 4600. Nothing was tuned for the level-0 I/O data-path test: with no env the boot still fails it honestly and the regression baseline is unchanged (PC=3FDCE400 samples=198, 3 PCs). VT1200_IO_PATH_STUB=1 (53/75) and the skip run (Terminal Manager at frame 3000) are also unchanged. ROM md5 unchanged. See harness/mame/INPUT_REPORT.md.
This commit is contained in:
1 parent
7e65692b54
commit
02acabb7ef
9 files changed
+1079
-6
No files matched your search
@@ -0,0 +1,195 @@
|
||||
# Keyboard & mouse as real devices on DUART2 — report
|
||||
|
||||
Adds the VT1200's actual input parts to the harness as **device models** on the
|
||||
already-modelled SCN2681 at DUART2: a **DEC LK401 keyboard** on channel A and a
|
||||
**DEC VSXXX-AA mouse** on channel B. Both are driven by MAME's host input and
|
||||
talk to the DUART through its genuine bit-level serial lines.
|
||||
|
||||
Status: **works**. The X11 cursor moves with the mouse and keystrokes reach the
|
||||
OS. Existing configurations are unchanged.
|
||||
|
||||
---
|
||||
|
||||
## 1. Route chosen: LK401 from the docs (route 2)
|
||||
|
||||
The brief offered two routes. **Route 1 (MAME's own `lk201` device + firmware
|
||||
ROM) is not usable**, and the ROM check is decisive:
|
||||
|
||||
* MAME's `lk201` device (`src/mame/dec/lk201.cpp`) runs an **M68HC05EG** and
|
||||
loads `23-001s9-00.bin` (8 KiB, CRC `be293c51`) from its `lk201_cpu` region.
|
||||
* The firmware zip downloaded during the protocol research
|
||||
(`reference/keyboard-mouse/bitsavers-23-004M2-00_LK201AA_Firmware.zip`)
|
||||
contains only `23-004M2-00.BIN` (4096 bytes, SHA1 `9744c561…`,
|
||||
CRC `35dd04c6`). That is exactly MAME's **second** ROM — the **8051**
|
||||
`23-004m2.8051h.e1` — which the `lk201` device does **not execute** (it is the
|
||||
red-LED LK201 variant; the device models the 6805 green-LED one).
|
||||
* So MAME's `lk201` would need a ROM we do not have. Running it is impossible
|
||||
without sourcing `23-001s9-00.bin`.
|
||||
|
||||
**Route 2 it is**: an HLE model of the documented LK401/LK201 host protocol from
|
||||
`reference/keyboard-mouse/PROTOCOL.md`, driving the real DUART wires. This also
|
||||
matches the **actual fitted part** (LK401) rather than an LK201, and needs no
|
||||
external ROM. Had route 1 been available it would have had the honest drawback
|
||||
of speaking LK201 keycodes — for this OS, which reads the keyboard over the
|
||||
protocol and does not care which family the keycodes came from, that would
|
||||
probably not have mattered in practice; but it is moot because the ROM is
|
||||
missing.
|
||||
|
||||
The mouse has no MAME device, so the VSXXX-AA is modelled either way (it is
|
||||
small).
|
||||
|
||||
## 2. What was implemented
|
||||
|
||||
Both devices live in `harness/mame/vt1200.cpp` (inside the owned driver, so the
|
||||
MAME tree gains **no** files), derive from
|
||||
`device_buffered_serial_interface<>`, and are wired bit-by-bit:
|
||||
|
||||
```
|
||||
keyboard tx_handler -> duart2.rx_a_w (device TX -> DUART RX)
|
||||
mouse tx_handler -> duart2.rx_b_w
|
||||
duart2.a_tx_cb / b_tx_cb -> device rx_w (DUART TX -> device RX)
|
||||
```
|
||||
|
||||
So the framing (keyboard 4800 8N1, mouse 4800 8**O**1) and the baud timing are
|
||||
the DUART's own; there is no byte-level shortcut.
|
||||
|
||||
**LK401 keyboard** (`vt1200_lk401_device`), protocol from `PROTOCOL.md`:
|
||||
|
||||
* Answers `FD` with the documented 4-byte self-test `01 00 00 00` (and runs it
|
||||
70 ms after power-up, as the real part does). `AB` returns the same ID bytes.
|
||||
* Parses the command set: `D3`, `E9` (LK401 mode), `13 p`/`11 p` (LEDs),
|
||||
keyclick/bell/auto-repeat commands, `CB`, `8B`, and the division mode-set
|
||||
commands (acknowledged with `BA`).
|
||||
* Reports key transitions as single keycodes (> 0x40) from the documented
|
||||
table (`netbsd-lk201-keycodes.html` + `elkay/docs/keymap.md`), honouring each
|
||||
division's programmed mode (down-only / auto-repeat-down / down-up) and
|
||||
sending `B3` (all-ups) when the last down-up key is released. `B4` metronome
|
||||
ticks implement auto-repeat. LK401-only keys (RShift/LAlt/RAlt/RCompose) are
|
||||
gated on the `E9` command.
|
||||
* Host input: one MAME keyboard port per key (`:lk401:K0…K107`) with `PORT_CODE`
|
||||
and `PORT_CHAR`, polled at 120 Hz.
|
||||
|
||||
**VSXXX-AA mouse** (`vt1200_vsxxxaa_device`), protocol from `PROTOCOL.md` §4:
|
||||
|
||||
* Answers `T` (and power-up) with the 4-byte self-test report `A0 02 00 00`
|
||||
(header 101 + revision 0; manufacturer 0; device code 0b0010 = mouse; error
|
||||
0; no buttons).
|
||||
* Implements stream mode (`R`), prompt mode (`D`/`P`), and the `S` format
|
||||
command. Reports `80 | SX SY L M R`, `|dx|`, `|dy|`.
|
||||
* Host input: relative `IPT_MOUSE_X` / `IPT_MOUSE_Y` plus three buttons;
|
||||
deltas are turned into incremental reports (Y inverted for DEC coordinates).
|
||||
|
||||
### The one non-obvious fix: idle-high TxD
|
||||
|
||||
The SCN2681's `device_serial_interface` receiver has a phase dependency: if the
|
||||
**first** edge it sees after a receiver reset is the start bit, with no explicit
|
||||
idle-high beforehand, it synchronises late and mis-samples the byte. Isolated
|
||||
evidence — bit-banging `0x55` (8N1, 4800) straight into `duart2.rx_a_w` with the
|
||||
CPU halted:
|
||||
|
||||
| preceding bits | SRA | RHRA |
|
||||
|---|---|---|
|
||||
| none | `0D` | `D5` |
|
||||
| 4 idle-high | `0D` | `55` |
|
||||
|
||||
The fix is faithful anyway: a UART's TxD **idles at mark (high)**. Both device
|
||||
models now hold the line high whenever they are not mid-byte, which is what
|
||||
makes the link work. This is the only place a physical-layer detail had to be
|
||||
made explicit.
|
||||
|
||||
## 3. Verification
|
||||
|
||||
### 3.1 Does the cursor move / do keystrokes register? — YES
|
||||
|
||||
Run: `VT1200_SKIP_LEVEL1=1` (the only configuration that reaches the OS),
|
||||
headless, with `harness/mame/input_inject.lua` injecting input.
|
||||
|
||||
* **Keystroke registers.** The script presses and releases `A`
|
||||
(`:lk401:K26`) at frame 4450. The OS's DUART2 poller reads the keycode
|
||||
`0xC2` (the documented LK401 code for `A`) from channel A at emulated
|
||||
`t=62.2856 s` (`2 R reg=3 val=00C2 pc=3FB42710`). The protocol delivers
|
||||
keystrokes to the OS.
|
||||
* **Mouse reports reach the OS.** The script walks the mouse 16–32 units/frame.
|
||||
The OS reads ~900 bytes of well-formed VSXXX reports from channel B; the raw
|
||||
stream is `98 10 00` / `98 20 00` = header `100`, SX=1, SY=1, no buttons,
|
||||
dx=+16/+32, dy=0.
|
||||
* **The cursor moves.** The X server's X11 pointer arrow is visible at
|
||||
`(768,472)` at frame 4400 and has left that location by frame 4600 (frames
|
||||
4700 and 5000 are byte-identical, i.e. it settled). Evidence images:
|
||||
`input_cursor_before_f4400.png`, `input_cursor_after_f4600.png` (full frames,
|
||||
downscaled) and `input_cursor_band_before_f4400.png` /
|
||||
`input_cursor_band_after_f4600.png` (3× zoom of the memory-bar band the
|
||||
pointer sat on).
|
||||
* The mouse self-test now decodes cleanly and the OS **switches the mouse to
|
||||
incremental stream mode** (`R`, THRB) — before the idle-high fix the
|
||||
self-test was garbled (`0x27…`) and the OS never sent `R`.
|
||||
* After `FD`, the OS reads the keyboard self-test as `01 00 00 00` and the
|
||||
mouse self-test as `A0 02 00 00`.
|
||||
|
||||
The pointer's travel under continuous injection is small in absolute terms
|
||||
because it ends up clamped at the Terminal Manager window border; the point is
|
||||
that it is the X server's own cursor and it tracks the device.
|
||||
|
||||
### 3.2 Level-0 test outcome — unchanged (report only)
|
||||
|
||||
**The level-0 I/O data-path outcome did not change.** With no environment the
|
||||
boot still fails level 0's count-only 10-byte test and parks in the diagnostics
|
||||
menu; the regression baseline is byte-for-byte the same:
|
||||
|
||||
```
|
||||
# no env, progress.lua at 200 frames
|
||||
PC=3FDCE400 samples=198 (3 distinct PCs)
|
||||
```
|
||||
|
||||
This is the expected, honest result and **does not contradict
|
||||
`reference/level0-10byte-puzzle.md`**: the keyboard's power-up self-test is
|
||||
4 bytes (and is sent before the DUART channel is configured, so it is dropped),
|
||||
`0x11 0x8C` and `0xE1` provoke no reply, and the test flushes the receiver — so
|
||||
the real device contributes **0** bytes to the count, never 10. Nothing was
|
||||
tuned to make that test pass.
|
||||
|
||||
### 3.3 Existing configurations still hold
|
||||
|
||||
| configuration | expected | observed | changed? |
|
||||
|---|---|---|---|
|
||||
| no env | level 0 fails honestly, menu | `PC=3FDCE400 samples=198 (3 PCs)` | no |
|
||||
| `VT1200_IO_PATH_STUB=1` | reaches level 1 | `PC=3FDCE400 samples=53 (75 PCs)` | no |
|
||||
| `VT1200_SKIP_LEVEL1=1` | X11 weave + Terminal Manager | frame 3000 = Terminal Manager | no |
|
||||
|
||||
ROM `md5 b46a3e442ccb86134d2356bd70d22fd2` — unchanged.
|
||||
|
||||
No baseline moved, so no baseline change needs recording. In particular the
|
||||
keyboard's power-up transmission does **not** perturb the faithful boot: it
|
||||
happens at 70 ms, before the OS programs DUART2, and the level-0 test's flush
|
||||
would consume it anyway.
|
||||
|
||||
The stub run's level-1 outcome is also unchanged (still the DUART1
|
||||
counter-ready interrupt storm), so the new DUART2 devices do not alter the
|
||||
level-1 path.
|
||||
|
||||
## 4. Caveats / what is not done yet
|
||||
|
||||
* **HLE, not firmware.** The keyboard models what the LK401 firmware *does*; it
|
||||
does not execute it. If `23-001s9-00.bin` (or an LK401 ROM) is ever sourced,
|
||||
route 1 could replace this.
|
||||
* **Keycode source.** Keycodes/divisions come from the NetBSD LK201 table and
|
||||
elkay's LK401 map; the LK401 firmware's exact ID bytes are unknown, so the
|
||||
self-test uses the documented healthy `01 00 00 00`.
|
||||
* **Auto-repeat** is a simple 500 ms / 20 Hz model — enough to be correct in
|
||||
shape, not measured against hardware.
|
||||
* **`0xB9` ambiguity.** Per `PROTOCOL.md` it is "disable Ctrl-key click"
|
||||
(no reply); NetBSD also lists it as "inhibit transmission" (reply `B7`). The
|
||||
no-reply reading was chosen because the VT1200 OS never sends it.
|
||||
* The mouse reports are read by the OS's poller; the exact pointer
|
||||
acceleration/clamping is the terminal software's, not ours.
|
||||
|
||||
## 5. Files
|
||||
|
||||
* `harness/mame/vt1200.cpp` — the two device models, input ports, and DUART2
|
||||
wiring (the only source file changed).
|
||||
* `harness/mame/input_probe.lua` — lists the device ports/fields (the tags Lua
|
||||
needs).
|
||||
* `harness/mame/input_inject.lua` — drives the mouse and one key from Lua for
|
||||
the acceptance test.
|
||||
* `harness/mame/input_cursor_*.png` — the frame evidence above.
|
||||
* New opt-in knob `VT1200_INPUT_LOG=<path>` — per-byte device TX/RX trace.
|
||||
+30
-4
@@ -67,6 +67,12 @@ to be a six-variable incantation; it is now the default, and a bare
|
||||
*faithfulness* fix, not a convenience — the real board has one. Without it the
|
||||
board write-back latch answers a read of DUART2's **write-only** IMR with its
|
||||
own written value, so the OS services a phantom interrupt that can never clear.
|
||||
* The **real input devices** on DUART2, driven by MAME's host input: a
|
||||
**DEC LK401 keyboard** (channel A, 4800 8N1) and a **DEC VSXXX-AA mouse**
|
||||
(channel B, 4800 8O1), both modelled from
|
||||
`reference/keyboard-mouse/PROTOCOL.md` and wired to the SCN2681 at the
|
||||
bit level (`rx_a_w`/`rx_b_w` and `a_tx_cb`/`b_tx_cb`). They answer `FD`/`T`
|
||||
because that is the protocol, not to satisfy a test. See `INPUT_REPORT.md`.
|
||||
* A mono palette (pens 0/1 = black/white) and an active-pixel scanline renderer
|
||||
that reads VRAM. See `RENDER_REPORT.md`.
|
||||
|
||||
@@ -141,8 +147,10 @@ the same sequence — see `reference/vt1200-boot-video/`.
|
||||
The OS's keyboard and mouse drivers **initialise and speak the real protocol**: it
|
||||
programs DUART2 (`MR1A=0x13`, `MR2A=0x07`, `CSRA/CSRB=0x99`, `ACR=0x60`,
|
||||
`IMR=0x33`) and sends `FD` plus LED/click/bell/auto-repeat to the keyboard and `'T'`
|
||||
to the mouse. **Input does not work** — nothing is attached — and nothing is faked
|
||||
to pretend otherwise.
|
||||
to the mouse. **Input now works**: the LK401 and VSXXX-AA device models answer
|
||||
those commands, the OS reads `01 00 00 00` / `A0 02 00 00`, switches the mouse to
|
||||
stream mode, and the X11 cursor moves with the mouse while keycodes reach the OS.
|
||||
See `INPUT_REPORT.md` (the cursor evidence is the `input_cursor_*.png` frames).
|
||||
|
||||
Not modelled: **LANCE Ethernet**. It does not block the X11 bring-up.
|
||||
|
||||
@@ -157,7 +165,7 @@ behaviour**, and is **not** evidence about the real terminal's POST.
|
||||
The skip is deliberately **not** the default: the real machine runs level 1, so
|
||||
the faithful configuration stays the default and the switch is how we diverge.
|
||||
|
||||
## Environment knobs (17; down from 33)
|
||||
## Environment knobs (18; down from 33)
|
||||
|
||||
All opt-in, and all off by default — there is no longer a "working incantation" to
|
||||
remember, because everything the firmware needs to run is now the default.
|
||||
@@ -165,7 +173,8 @@ remember, because everything the firmware needs to run is now the default.
|
||||
Instrumentation: `VT1200_DUARTLOG`, `VT1200_BOARD_LOG`, `VT1200_BOARD_MAP`,
|
||||
`VT1200_IRQLOG`, `VT1200_LOG_UNMAPPED`, `VT1200_DPYLOG` (+`_FRAMES`),
|
||||
`VT1200_DUMP_FRAME` (+`_FRAMES`), `VT1200_DUMP_RAM` (+`_FRAMES`),
|
||||
`VT1200_DUMP_VRAM` (+`_FRAMES`), `VT1200_DUMP_DIR`, `VT1200_NO_RENDER`.
|
||||
`VT1200_DUMP_VRAM` (+`_FRAMES`), `VT1200_DUMP_DIR`, `VT1200_NO_RENDER`,
|
||||
`VT1200_INPUT_LOG` (per-byte keyboard/mouse TX/RX trace, with emulated time).
|
||||
Behaviour switches: `VT1200_SKIP_LEVEL1`, `VT1200_IO_PATH_STUB` (the level-0
|
||||
test-fixture stand-in described above — not a keyboard model, off by default).
|
||||
|
||||
@@ -255,6 +264,23 @@ The Lua API exposes `manager.machine.debugger`, `device.debug` (step, go,
|
||||
bpset, wpset), CPU registers (`cpu.state['A0'].value`), memory
|
||||
(`cpu.spaces['program']:read_u32/write_u32`), and frame hooks.
|
||||
|
||||
### Drive the keyboard and mouse (`input_inject.lua`)
|
||||
|
||||
The LK401/VSXXX-AA models read MAME's host input, so they can be driven from
|
||||
Lua for a headless acceptance run:
|
||||
|
||||
```sh
|
||||
M=/home/arti/code/k-space/mame0289/vt1200
|
||||
VT1200_SKIP_LEVEL1=1 \
|
||||
VT1200_DUMP_FRAMES=4400,4600 VT1200_DUMP_DIR=/tmp \
|
||||
$M vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \
|
||||
-debug -debugger none \
|
||||
-autoboot_script harness/mame/input_inject.lua -autoboot_delay 0
|
||||
```
|
||||
|
||||
`input_inject.lua` walks the mouse from frame 4400 and taps `A` at frame 4450.
|
||||
`input_probe.lua` prints every device port/field tag. See `INPUT_REPORT.md`.
|
||||
|
||||
### Trace from the reset vector (`trace.lua`)
|
||||
|
||||
Prints the reset PC (expect `3F200040`), disassembles the power-on code to
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 544 B |
Binary file not shown.
|
After Width: | Height: | Size: 635 B |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,46 @@
|
||||
-- Drive the modelled LK401 keyboard and VSXXX-AA mouse from MAME's host input
|
||||
-- ports, so the acceptance test "does the cursor move / do keystrokes register"
|
||||
-- can be observed deterministically.
|
||||
--
|
||||
-- VT1200_SKIP_LEVEL1=1 \
|
||||
-- VT1200_DUMP_FRAMES=4400,4700,5000 VT1200_DUMP_DIR=/tmp/opencode \
|
||||
-- <mame> vt1200 ... -autoboot_script harness/mame/input_inject.lua
|
||||
--
|
||||
-- Between INJ_MOUSE_FROM and INJ_MOUSE_TO the mouse is walked 16 units/frame;
|
||||
-- the keyboard presses and releases 'A' at INJ_KEY_FRAME.
|
||||
|
||||
local machine = manager.machine
|
||||
local ports = machine.ioport.ports
|
||||
|
||||
local mouse_x = ports[":mouse:MOUSE_X"].fields["Mouse X"]
|
||||
local key_a = ports[":lk401:K26"].fields["A"]
|
||||
|
||||
local INJ_MOUSE_FROM = 4400
|
||||
local INJ_MOUSE_TO = 4700
|
||||
local INJ_KEY_FRAME = 4450
|
||||
local INJ_KEY_FRAMES = 20
|
||||
|
||||
local frames = 0
|
||||
local mx = 0
|
||||
local key_down = false
|
||||
|
||||
emu.register_frame_done(function()
|
||||
frames = frames + 1
|
||||
if frames >= INJ_MOUSE_FROM and frames <= INJ_MOUSE_TO then
|
||||
mx = (mx + 32) % 256
|
||||
mouse_x:set_value(mx)
|
||||
end
|
||||
if frames == INJ_KEY_FRAME then
|
||||
key_a:set_value(1)
|
||||
key_down = true
|
||||
print(string.format("inject: frame %d press A", frames))
|
||||
end
|
||||
if key_down and frames == INJ_KEY_FRAME + INJ_KEY_FRAMES then
|
||||
key_a:set_value(0)
|
||||
key_down = false
|
||||
print(string.format("inject: frame %d release A", frames))
|
||||
end
|
||||
if frames >= INJ_MOUSE_TO + 500 then
|
||||
machine:exit()
|
||||
end
|
||||
end, "vt1200-input-inject")
|
||||
@@ -0,0 +1,20 @@
|
||||
-- List every I/O port and field the driver registers, with the tag needed to
|
||||
-- address it from Lua. Used to drive the LK401/VSXXX-AA device models.
|
||||
--
|
||||
-- <mame> vt1200 ... -autoboot_script harness/mame/input_probe.lua
|
||||
|
||||
local machine = manager.machine
|
||||
local frames = 0
|
||||
emu.register_frame_done(function()
|
||||
frames = frames + 1
|
||||
if frames < 3 then return end
|
||||
print("=== ioport ports ===")
|
||||
for tag, port in pairs(machine.ioport.ports) do
|
||||
print(string.format("port %s", tag))
|
||||
for fname, field in pairs(port.fields) do
|
||||
local ok, ftype = pcall(function() return field.type end)
|
||||
print(string.format(" field %-28s type=%s name=%s", tostring(fname), tostring(ftype), tostring(field.name)))
|
||||
end
|
||||
end
|
||||
machine:exit()
|
||||
end, "vt1200-input-probe")
|
||||
+788
-2
@@ -134,10 +134,12 @@
|
||||
|
||||
#include "emu.h"
|
||||
#include "cpu/tms34010/tms34010.h"
|
||||
#include "diserial.h"
|
||||
#include "emupal.h"
|
||||
#include "machine/mc68681.h"
|
||||
#include "screen.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -149,6 +151,315 @@
|
||||
#include "vt1200_board.h"
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
// DEC LK401 keyboard + VSXXX-AA mouse -- real device models on DUART2
|
||||
//**************************************************************************
|
||||
//
|
||||
// The VT1200 keyboard is an LK401 speaking the LK201 host protocol (4800 8N1;
|
||||
// see reference/keyboard-mouse/PROTOCOL.md). MAME's own lk201 device executes
|
||||
// the real 68HC05 keyboard firmware, but that ROM (`23-001s9-00.bin`) is not in
|
||||
// our set: the downloaded zip only carries the *8051* image (`23-004M2-00.bin`,
|
||||
// SHA1 9744c561... = MAME's unused `23-004m2.8051h.e1`). So the keyboard is an
|
||||
// HLE model of the documented protocol, not a firmware emulation.
|
||||
//
|
||||
// The mouse has no MAME device at all; the VSXXX-AA is modelled here too
|
||||
// (4800 8O1). Both connect to the real SCN2681 at DUART2 through the genuine
|
||||
// bit-level serial interfaces (`rx_a_w`/`rx_b_w` and `a_tx_cb`/`b_tx_cb`), so
|
||||
// the framing/timing is the DUART's, not a shortcut.
|
||||
//
|
||||
// These are device models: they answer `FD`/`T` because that is the protocol,
|
||||
// not because a test wants an answer. They are NOT tuned to satisfy the
|
||||
// level-0 I/O data path test (which wants 10 bytes; the keyboard's own power-up
|
||||
// and FD reply is 4). See reference/level0-10byte-puzzle.md.
|
||||
|
||||
// --- LK401 keycode / division table -----------------------------------------
|
||||
// Every LK401 key has one keycode (netbsd-lk201-keycodes.html and elkay's
|
||||
// keymap.md). Each key also belongs to one of 14 divisions, which the terminal
|
||||
// programs to a mode (down / auto-repeat-down / down-up).
|
||||
#define VT1200_LK401_KEY_LIST(X) \
|
||||
X( 0, 0xbf, 1, PORT_NAME("` ~") PORT_CODE(KEYCODE_TILDE) PORT_CHAR('`') PORT_CHAR('~') ) \
|
||||
X( 1, 0xc0, 1, PORT_NAME("1 !") PORT_CODE(KEYCODE_1) PORT_CHAR('1') PORT_CHAR('!') ) \
|
||||
X( 2, 0xc5, 1, PORT_NAME("2 @") PORT_CODE(KEYCODE_2) PORT_CHAR('2') PORT_CHAR('@') ) \
|
||||
X( 3, 0xcb, 1, PORT_NAME("3 #") PORT_CODE(KEYCODE_3) PORT_CHAR('3') PORT_CHAR('#') ) \
|
||||
X( 4, 0xd0, 1, PORT_NAME("4 $") PORT_CODE(KEYCODE_4) PORT_CHAR('4') PORT_CHAR('$') ) \
|
||||
X( 5, 0xd6, 1, PORT_NAME("5 %") PORT_CODE(KEYCODE_5) PORT_CHAR('5') PORT_CHAR('%') ) \
|
||||
X( 6, 0xdb, 1, PORT_NAME("6 ^") PORT_CODE(KEYCODE_6) PORT_CHAR('6') PORT_CHAR('^') ) \
|
||||
X( 7, 0xe0, 1, PORT_NAME("7 &") PORT_CODE(KEYCODE_7) PORT_CHAR('7') PORT_CHAR('&') ) \
|
||||
X( 8, 0xe5, 1, PORT_NAME("8 *") PORT_CODE(KEYCODE_8) PORT_CHAR('8') PORT_CHAR('*') ) \
|
||||
X( 9, 0xea, 1, PORT_NAME("9 (") PORT_CODE(KEYCODE_9) PORT_CHAR('9') PORT_CHAR('(') ) \
|
||||
X(10, 0xef, 1, PORT_NAME("0 )") PORT_CODE(KEYCODE_0) PORT_CHAR('0') PORT_CHAR(')') ) \
|
||||
X(11, 0xf9, 1, PORT_NAME("- _") PORT_CODE(KEYCODE_MINUS) PORT_CHAR('-') PORT_CHAR('_') ) \
|
||||
X(12, 0xf5, 1, PORT_NAME("= +") PORT_CODE(KEYCODE_EQUALS) PORT_CHAR('=') PORT_CHAR('+') ) \
|
||||
X(13, 0xc1, 1, PORT_NAME("Q") PORT_CODE(KEYCODE_Q) PORT_CHAR('q') PORT_CHAR('Q') ) \
|
||||
X(14, 0xc6, 1, PORT_NAME("W") PORT_CODE(KEYCODE_W) PORT_CHAR('w') PORT_CHAR('W') ) \
|
||||
X(15, 0xcc, 1, PORT_NAME("E") PORT_CODE(KEYCODE_E) PORT_CHAR('e') PORT_CHAR('E') ) \
|
||||
X(16, 0xd1, 1, PORT_NAME("R") PORT_CODE(KEYCODE_R) PORT_CHAR('r') PORT_CHAR('R') ) \
|
||||
X(17, 0xd7, 1, PORT_NAME("T") PORT_CODE(KEYCODE_T) PORT_CHAR('t') PORT_CHAR('T') ) \
|
||||
X(18, 0xdc, 1, PORT_NAME("Y") PORT_CODE(KEYCODE_Y) PORT_CHAR('y') PORT_CHAR('Y') ) \
|
||||
X(19, 0xe1, 1, PORT_NAME("U") PORT_CODE(KEYCODE_U) PORT_CHAR('u') PORT_CHAR('U') ) \
|
||||
X(20, 0xe6, 1, PORT_NAME("I") PORT_CODE(KEYCODE_I) PORT_CHAR('i') PORT_CHAR('I') ) \
|
||||
X(21, 0xeb, 1, PORT_NAME("O") PORT_CODE(KEYCODE_O) PORT_CHAR('o') PORT_CHAR('O') ) \
|
||||
X(22, 0xf0, 1, PORT_NAME("P") PORT_CODE(KEYCODE_P) PORT_CHAR('p') PORT_CHAR('P') ) \
|
||||
X(23, 0xfa, 1, PORT_NAME("[ {") PORT_CODE(KEYCODE_OPENBRACE) PORT_CHAR('[') PORT_CHAR('{') ) \
|
||||
X(24, 0xf6, 1, PORT_NAME("] }") PORT_CODE(KEYCODE_CLOSEBRACE)PORT_CHAR(']') PORT_CHAR('}') ) \
|
||||
X(25, 0xf7, 1, PORT_NAME("\\ |") PORT_CODE(KEYCODE_BACKSLASH) PORT_CHAR('\\') PORT_CHAR('|') ) \
|
||||
X(26, 0xc2, 1, PORT_NAME("A") PORT_CODE(KEYCODE_A) PORT_CHAR('a') PORT_CHAR('A') ) \
|
||||
X(27, 0xc7, 1, PORT_NAME("S") PORT_CODE(KEYCODE_S) PORT_CHAR('s') PORT_CHAR('S') ) \
|
||||
X(28, 0xcd, 1, PORT_NAME("D") PORT_CODE(KEYCODE_D) PORT_CHAR('d') PORT_CHAR('D') ) \
|
||||
X(29, 0xd2, 1, PORT_NAME("F") PORT_CODE(KEYCODE_F) PORT_CHAR('f') PORT_CHAR('F') ) \
|
||||
X(30, 0xd8, 1, PORT_NAME("G") PORT_CODE(KEYCODE_G) PORT_CHAR('g') PORT_CHAR('G') ) \
|
||||
X(31, 0xdd, 1, PORT_NAME("H") PORT_CODE(KEYCODE_H) PORT_CHAR('h') PORT_CHAR('H') ) \
|
||||
X(32, 0xe2, 1, PORT_NAME("J") PORT_CODE(KEYCODE_J) PORT_CHAR('j') PORT_CHAR('J') ) \
|
||||
X(33, 0xe7, 1, PORT_NAME("K") PORT_CODE(KEYCODE_K) PORT_CHAR('k') PORT_CHAR('K') ) \
|
||||
X(34, 0xec, 1, PORT_NAME("L") PORT_CODE(KEYCODE_L) PORT_CHAR('l') PORT_CHAR('L') ) \
|
||||
X(35, 0xf2, 1, PORT_NAME("; :") PORT_CODE(KEYCODE_COLON) PORT_CHAR(';') PORT_CHAR(':') ) \
|
||||
X(36, 0xfb, 1, PORT_NAME("' \"") PORT_CODE(KEYCODE_QUOTE) PORT_CHAR('\'') PORT_CHAR('"') ) \
|
||||
X(37, 0xc9, 1, PORT_NAME("< >") PORT_CODE(KEYCODE_BACKSLASH2)PORT_CHAR('<') PORT_CHAR('>') ) \
|
||||
X(38, 0xc3, 1, PORT_NAME("Z") PORT_CODE(KEYCODE_Z) PORT_CHAR('z') PORT_CHAR('Z') ) \
|
||||
X(39, 0xc8, 1, PORT_NAME("X") PORT_CODE(KEYCODE_X) PORT_CHAR('x') PORT_CHAR('X') ) \
|
||||
X(40, 0xce, 1, PORT_NAME("C") PORT_CODE(KEYCODE_C) PORT_CHAR('c') PORT_CHAR('C') ) \
|
||||
X(41, 0xd3, 1, PORT_NAME("V") PORT_CODE(KEYCODE_V) PORT_CHAR('v') PORT_CHAR('V') ) \
|
||||
X(42, 0xd9, 1, PORT_NAME("B") PORT_CODE(KEYCODE_B) PORT_CHAR('b') PORT_CHAR('B') ) \
|
||||
X(43, 0xde, 1, PORT_NAME("N") PORT_CODE(KEYCODE_N) PORT_CHAR('n') PORT_CHAR('N') ) \
|
||||
X(44, 0xe3, 1, PORT_NAME("M") PORT_CODE(KEYCODE_M) PORT_CHAR('m') PORT_CHAR('M') ) \
|
||||
X(45, 0xe8, 1, PORT_NAME(", <") PORT_CODE(KEYCODE_COMMA) PORT_CHAR(',') PORT_CHAR('<') ) \
|
||||
X(46, 0xed, 1, PORT_NAME(". >") PORT_CODE(KEYCODE_STOP) PORT_CHAR('.') PORT_CHAR('>') ) \
|
||||
X(47, 0xf3, 1, PORT_NAME("/ ?") PORT_CODE(KEYCODE_SLASH) PORT_CHAR('/') PORT_CHAR('?') ) \
|
||||
X(48, 0xd4, 1, PORT_NAME("Space") PORT_CODE(KEYCODE_SPACE) PORT_CHAR(' ') ) \
|
||||
X(49, 0x92, 2, PORT_NAME("KP 0") PORT_CODE(KEYCODE_0_PAD) ) \
|
||||
X(50, 0x96, 2, PORT_NAME("KP 1") PORT_CODE(KEYCODE_1_PAD) ) \
|
||||
X(51, 0x97, 2, PORT_NAME("KP 2") PORT_CODE(KEYCODE_2_PAD) ) \
|
||||
X(52, 0x98, 2, PORT_NAME("KP 3") PORT_CODE(KEYCODE_3_PAD) ) \
|
||||
X(53, 0x99, 2, PORT_NAME("KP 4") PORT_CODE(KEYCODE_4_PAD) ) \
|
||||
X(54, 0x9a, 2, PORT_NAME("KP 5") PORT_CODE(KEYCODE_5_PAD) ) \
|
||||
X(55, 0x9b, 2, PORT_NAME("KP 6") PORT_CODE(KEYCODE_6_PAD) ) \
|
||||
X(56, 0x9d, 2, PORT_NAME("KP 7") PORT_CODE(KEYCODE_7_PAD) ) \
|
||||
X(57, 0x9e, 2, PORT_NAME("KP 8") PORT_CODE(KEYCODE_8_PAD) ) \
|
||||
X(58, 0x9f, 2, PORT_NAME("KP 9") PORT_CODE(KEYCODE_9_PAD) ) \
|
||||
X(59, 0x94, 2, PORT_NAME("KP .") PORT_CODE(KEYCODE_DEL_PAD) ) \
|
||||
X(60, 0x9c, 2, PORT_NAME("KP ,") PORT_CODE(KEYCODE_COMMA_PAD) ) \
|
||||
X(61, 0xa0, 2, PORT_NAME("KP -") PORT_CODE(KEYCODE_MINUS_PAD) ) \
|
||||
X(62, 0x95, 2, PORT_NAME("KP Enter") PORT_CODE(KEYCODE_ENTER_PAD) ) \
|
||||
X(63, 0xa1, 2, PORT_NAME("PF1") PORT_CODE(KEYCODE_NUMLOCK) ) \
|
||||
X(64, 0xa2, 2, PORT_NAME("PF2") PORT_CODE(KEYCODE_SLASH_PAD) ) \
|
||||
X(65, 0xa3, 2, PORT_NAME("PF3") PORT_CODE(KEYCODE_ASTERISK) ) \
|
||||
X(66, 0xa4, 2, PORT_NAME("PF4") PORT_CODE(KEYCODE_EQUALS_PAD) ) \
|
||||
X(67, 0xbc, 3, PORT_NAME("Delete") PORT_CODE(KEYCODE_BACKSPACE) PORT_CHAR(8) ) \
|
||||
X(68, 0xbd, 4, PORT_NAME("Return") PORT_CODE(KEYCODE_ENTER) PORT_CHAR(13) ) \
|
||||
X(69, 0xbe, 4, PORT_NAME("Tab") PORT_CODE(KEYCODE_TAB) PORT_CHAR(9) ) \
|
||||
X(70, 0xb0, 5, PORT_NAME("Lock") PORT_CODE(KEYCODE_CAPSLOCK) ) \
|
||||
X(71, 0xb1, 5, PORT_NAME("Compose") PORT_CODE(KEYCODE_MENU) ) \
|
||||
X(72, 0xae, 6, PORT_NAME("Shift") PORT_CODE(KEYCODE_LSHIFT) ) \
|
||||
X(73, 0xaf, 6, PORT_NAME("Ctrl") PORT_CODE(KEYCODE_LCONTROL) ) \
|
||||
X(74, 0xa7, 7, PORT_NAME("Left") PORT_CODE(KEYCODE_LEFT) PORT_CHAR(UCHAR_MAMEKEY(LEFT)) ) \
|
||||
X(75, 0xa8, 7, PORT_NAME("Right") PORT_CODE(KEYCODE_RIGHT) PORT_CHAR(UCHAR_MAMEKEY(RIGHT)) ) \
|
||||
X(76, 0xa9, 8, PORT_NAME("Down") PORT_CODE(KEYCODE_DOWN) PORT_CHAR(UCHAR_MAMEKEY(DOWN)) ) \
|
||||
X(77, 0xaa, 8, PORT_NAME("Up") PORT_CODE(KEYCODE_UP) PORT_CHAR(UCHAR_MAMEKEY(UP)) ) \
|
||||
X(78, 0x8a, 9, PORT_NAME("Find") PORT_CODE(KEYCODE_INSERT) PORT_CHAR(UCHAR_MAMEKEY(INSERT)) ) \
|
||||
X(79, 0x8b, 9, PORT_NAME("Insert Here") PORT_CODE(KEYCODE_HOME) PORT_CHAR(UCHAR_MAMEKEY(HOME)) ) \
|
||||
X(80, 0x8c, 9, PORT_NAME("Remove") PORT_CODE(KEYCODE_PGUP) PORT_CHAR(UCHAR_MAMEKEY(PGUP)) ) \
|
||||
X(81, 0x8d, 9, PORT_NAME("Select") PORT_CODE(KEYCODE_DEL) PORT_CHAR(UCHAR_MAMEKEY(DEL)) ) \
|
||||
X(82, 0x8e, 9, PORT_NAME("Prev Screen") PORT_CODE(KEYCODE_END) PORT_CHAR(UCHAR_MAMEKEY(END)) ) \
|
||||
X(83, 0x8f, 9, PORT_NAME("Next Screen") PORT_CODE(KEYCODE_PGDN) PORT_CHAR(UCHAR_MAMEKEY(PGDN)) ) \
|
||||
X(84, 0x56, 10, PORT_NAME("F1 Hold") PORT_CODE(KEYCODE_F1) PORT_CHAR(UCHAR_MAMEKEY(F1)) ) \
|
||||
X(85, 0x57, 10, PORT_NAME("F2 Print") PORT_CODE(KEYCODE_F2) PORT_CHAR(UCHAR_MAMEKEY(F2)) ) \
|
||||
X(86, 0x58, 10, PORT_NAME("F3 Setup") PORT_CODE(KEYCODE_F3) PORT_CHAR(UCHAR_MAMEKEY(F3)) ) \
|
||||
X(87, 0x59, 10, PORT_NAME("F4 Data") PORT_CODE(KEYCODE_F4) PORT_CHAR(UCHAR_MAMEKEY(F4)) ) \
|
||||
X(88, 0x5a, 10, PORT_NAME("F5 Break") PORT_CODE(KEYCODE_F5) PORT_CHAR(UCHAR_MAMEKEY(F5)) ) \
|
||||
X(89, 0x64, 11, PORT_NAME("F6") PORT_CODE(KEYCODE_F6) PORT_CHAR(UCHAR_MAMEKEY(F6)) ) \
|
||||
X(90, 0x65, 11, PORT_NAME("F7") PORT_CODE(KEYCODE_F7) PORT_CHAR(UCHAR_MAMEKEY(F7)) ) \
|
||||
X(91, 0x66, 11, PORT_NAME("F8") PORT_CODE(KEYCODE_F8) PORT_CHAR(UCHAR_MAMEKEY(F8)) ) \
|
||||
X(92, 0x67, 11, PORT_NAME("F9") PORT_CODE(KEYCODE_F9) PORT_CHAR(UCHAR_MAMEKEY(F9)) ) \
|
||||
X(93, 0x68, 11, PORT_NAME("F10") PORT_CODE(KEYCODE_F10) PORT_CHAR(UCHAR_MAMEKEY(F10)) ) \
|
||||
X(94, 0x71, 12, PORT_NAME("F11 Esc") PORT_CODE(KEYCODE_F11) PORT_CHAR(UCHAR_MAMEKEY(F11)) ) \
|
||||
X(95, 0x72, 12, PORT_NAME("F12 BS") PORT_CODE(KEYCODE_F12) PORT_CHAR(UCHAR_MAMEKEY(F12)) ) \
|
||||
X(96, 0x73, 12, PORT_NAME("F13 LF") PORT_CODE(KEYCODE_F13) PORT_CHAR(UCHAR_MAMEKEY(F13)) ) \
|
||||
X(97, 0x74, 12, PORT_NAME("F14") PORT_CODE(KEYCODE_F14) PORT_CHAR(UCHAR_MAMEKEY(F14)) ) \
|
||||
X(98, 0x7c, 13, PORT_NAME("Help") PORT_CODE(KEYCODE_F15) PORT_CHAR(UCHAR_MAMEKEY(F15)) ) \
|
||||
X(99, 0x7d, 13, PORT_NAME("Do") PORT_CODE(KEYCODE_F16) PORT_CHAR(UCHAR_MAMEKEY(F16)) ) \
|
||||
X(100,0x80, 14, PORT_NAME("F17") PORT_CODE(KEYCODE_F17) PORT_CHAR(UCHAR_MAMEKEY(F17)) ) \
|
||||
X(101,0x81, 14, PORT_NAME("F18") PORT_CODE(KEYCODE_F18) PORT_CHAR(UCHAR_MAMEKEY(F18)) ) \
|
||||
X(102,0x82, 14, PORT_NAME("F19") PORT_CODE(KEYCODE_F19) PORT_CHAR(UCHAR_MAMEKEY(F19)) ) \
|
||||
X(103,0x83, 14, PORT_NAME("F20") PORT_CODE(KEYCODE_F20) PORT_CHAR(UCHAR_MAMEKEY(F20)) ) \
|
||||
X(104,0xab, 8, PORT_NAME("RShift") PORT_CODE(KEYCODE_RSHIFT) ) \
|
||||
X(105,0xac, 5, PORT_NAME("LAlt") PORT_CODE(KEYCODE_LALT) ) \
|
||||
X(106,0xb2, 5, PORT_NAME("RAlt") PORT_CODE(KEYCODE_RALT) ) \
|
||||
X(107,0xad, 5, PORT_NAME("RCompose") PORT_CODE(KEYCODE_RWIN) )
|
||||
|
||||
#define VT1200_LK401_COUNT(n, code, div, ...) + 1
|
||||
#define VT1200_LK401_PORT(n, code, div, ...) \
|
||||
PORT_START("K" #n) PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYBOARD) __VA_ARGS__
|
||||
#define VT1200_LK401_ENTRY(n, code, div, ...) { code, div },
|
||||
|
||||
enum { VT1200_LK401_NKEYS = 0 VT1200_LK401_KEY_LIST(VT1200_LK401_COUNT) };
|
||||
|
||||
// Keyboard command / response opcodes (PROTOCOL.md section 3.3/3.4).
|
||||
enum : uint8_t
|
||||
{
|
||||
LK_CMD_POWER_UP = 0xfd,
|
||||
LK_CMD_REQUEST_ID = 0xab,
|
||||
LK_CMD_SET_DEFAULTS = 0xd3,
|
||||
LK_CMD_ENABLE_LK401 = 0xe9,
|
||||
LK_CMD_LED_ON = 0x13,
|
||||
LK_CMD_LED_OFF = 0x11,
|
||||
LK_CMD_KEYCLICK_ON = 0x1b,
|
||||
LK_CMD_KEYCLICK_OFF = 0x99,
|
||||
LK_CMD_CTRLCLICK_ON = 0xbb,
|
||||
LK_CMD_CTRLCLICK_OFF = 0xb9,
|
||||
LK_CMD_BELL_ON = 0x23,
|
||||
LK_CMD_BELL_OFF = 0xa1,
|
||||
LK_CMD_BELL = 0xa7,
|
||||
LK_CMD_SOUND_CLICK = 0x9f,
|
||||
LK_CMD_MMS_NO_RPT = 0xd1,
|
||||
LK_CMD_ENB_RPT = 0xe3,
|
||||
LK_CMD_DIS_RPT = 0xe1,
|
||||
LK_CMD_RPT_TO_DOWN = 0xd9,
|
||||
LK_CMD_RESUME = 0x8b,
|
||||
LK_CMD_TEST_MODE = 0xcb,
|
||||
|
||||
LK_ALL_KEYS_UP = 0xb3,
|
||||
LK_METRONOME = 0xb4,
|
||||
LK_OUTPUT_ERROR = 0xb5,
|
||||
LK_INPUT_ERROR = 0xb6,
|
||||
LK_KBD_LOCKED = 0xb7,
|
||||
LK_TEST_MODE_ACK = 0xb8,
|
||||
LK_PREFIX_KEY_DOWN = 0xb9,
|
||||
LK_MODE_CHANGE_ACK = 0xba
|
||||
};
|
||||
|
||||
// Power-up / `FD` self-test reply. PROTOCOL.md section 3.5: firmware ID 01,
|
||||
// hardware ID 00, error 00, no stuck key.
|
||||
static const uint8_t LK_SELFTEST[4] = { 0x01, 0x00, 0x00, 0x00 };
|
||||
|
||||
class vt1200_lk401_device : public device_t, public device_buffered_serial_interface<64U>
|
||||
{
|
||||
public:
|
||||
vt1200_lk401_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
|
||||
|
||||
auto tx_handler() { return m_tx_handler.bind(); }
|
||||
|
||||
protected:
|
||||
virtual void device_start() override ATTR_COLD;
|
||||
virtual void device_reset() override ATTR_COLD;
|
||||
virtual ioport_constructor device_input_ports() const override ATTR_COLD;
|
||||
|
||||
virtual void tra_callback() override;
|
||||
virtual void received_byte(u8 byte) override;
|
||||
|
||||
TIMER_CALLBACK_MEMBER(scan);
|
||||
TIMER_CALLBACK_MEMBER(power_up_selftest);
|
||||
|
||||
private:
|
||||
void process_command();
|
||||
void run_self_test();
|
||||
void queue(u8 byte);
|
||||
void emit_key(unsigned index, bool pressed);
|
||||
static bool lk401_only(uint8_t code);
|
||||
|
||||
devcb_write_line m_tx_handler;
|
||||
required_ioport_array<VT1200_LK401_NKEYS> m_kbd;
|
||||
emu_timer *m_scan_timer = nullptr;
|
||||
emu_timer *m_power_up_timer = nullptr;
|
||||
|
||||
std::vector<u8> m_cmd; // command bytes accumulated so far
|
||||
u8 m_div_mode[15] = { 0 }; // [1..14]: 0=down 1=autodown 3=updown
|
||||
bool m_autorepeat = true;
|
||||
bool m_lk401_mode = false;
|
||||
bool m_inhibit = false;
|
||||
u8 m_led_mask = 0;
|
||||
bool m_lost_keys = false;
|
||||
|
||||
std::array<bool, VT1200_LK401_NKEYS> m_down = { };
|
||||
std::array<attotime, VT1200_LK401_NKEYS> m_repeat = { };
|
||||
int m_updown_down = 0;
|
||||
|
||||
FILE *m_log = nullptr; // VT1200_INPUT_LOG=<path>
|
||||
};
|
||||
|
||||
static const struct { uint8_t code; uint8_t div; } VT1200_LK401_TABLE[] =
|
||||
{
|
||||
VT1200_LK401_KEY_LIST(VT1200_LK401_ENTRY)
|
||||
};
|
||||
|
||||
INPUT_PORTS_START(vt1200_lk401)
|
||||
VT1200_LK401_KEY_LIST(VT1200_LK401_PORT)
|
||||
INPUT_PORTS_END
|
||||
|
||||
// Mouse host inputs. X/Y are relative (MAME accumulates movement into the port
|
||||
// value, which the device reads as a signed 8-bit delta). The three button
|
||||
// bits are laid out in the VSXXX report order: bit 2 = left, bit 1 = middle,
|
||||
// bit 0 = right.
|
||||
INPUT_PORTS_START(vt1200_vsxxxaa)
|
||||
PORT_START("MOUSE_X")
|
||||
PORT_BIT(0xff, 0x00, IPT_MOUSE_X) PORT_SENSITIVITY(50) PORT_KEYDELTA(0) PORT_PLAYER(1)
|
||||
|
||||
PORT_START("MOUSE_Y")
|
||||
PORT_BIT(0xff, 0x00, IPT_MOUSE_Y) PORT_SENSITIVITY(50) PORT_KEYDELTA(0) PORT_PLAYER(1)
|
||||
|
||||
PORT_START("MOUSE_BTN")
|
||||
PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_CODE(MOUSECODE_BUTTON1) PORT_PLAYER(1) // left
|
||||
PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_BUTTON2) PORT_CODE(MOUSECODE_BUTTON2) PORT_PLAYER(1) // middle
|
||||
PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_BUTTON3) PORT_CODE(MOUSECODE_BUTTON3) PORT_PLAYER(1) // right
|
||||
INPUT_PORTS_END
|
||||
|
||||
// --- VSXXX-AA mouse ---------------------------------------------------------
|
||||
// 4800 8O1 (VCB02 section C.6.1). Commands are printable ASCII; the self-test
|
||||
// (`T`, and at power-up) returns a 4-byte report. Movement is reported as
|
||||
// 3-byte incremental packets in stream mode (`R`), or on request (`P`).
|
||||
enum : uint8_t
|
||||
{
|
||||
MS_CMD_STREAM = 'R',
|
||||
MS_CMD_PROMPT = 'D',
|
||||
MS_CMD_POSITION = 'P',
|
||||
MS_CMD_SELFTEST = 'T',
|
||||
MS_CMD_FORMAT = 'S'
|
||||
};
|
||||
|
||||
class vt1200_vsxxxaa_device : public device_t, public device_buffered_serial_interface<16U>
|
||||
{
|
||||
public:
|
||||
vt1200_vsxxxaa_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
|
||||
|
||||
auto tx_handler() { return m_tx_handler.bind(); }
|
||||
|
||||
protected:
|
||||
virtual void device_start() override ATTR_COLD;
|
||||
virtual void device_reset() override ATTR_COLD;
|
||||
virtual ioport_constructor device_input_ports() const override ATTR_COLD;
|
||||
|
||||
virtual void tra_callback() override;
|
||||
virtual void received_byte(u8 byte) override;
|
||||
|
||||
TIMER_CALLBACK_MEMBER(scan);
|
||||
TIMER_CALLBACK_MEMBER(power_up_selftest);
|
||||
|
||||
private:
|
||||
void send_self_test();
|
||||
void send_report(int dx, int dy, int buttons);
|
||||
void queue(u8 byte);
|
||||
|
||||
devcb_write_line m_tx_handler;
|
||||
required_ioport m_host_x;
|
||||
required_ioport m_host_y;
|
||||
required_ioport m_host_buttons;
|
||||
emu_timer *m_scan_timer = nullptr;
|
||||
emu_timer *m_power_up_timer = nullptr;
|
||||
|
||||
bool m_stream = false; // false = prompt mode (power-up default)
|
||||
u8 m_last_x = 0;
|
||||
u8 m_last_y = 0;
|
||||
int m_acc_dx = 0;
|
||||
int m_acc_dy = 0;
|
||||
int m_last_buttons = 0;
|
||||
bool m_buttons_dirty = false;
|
||||
|
||||
FILE *m_log = nullptr;
|
||||
};
|
||||
|
||||
DECLARE_DEVICE_TYPE(VT1200_LK401, vt1200_lk401_device)
|
||||
DECLARE_DEVICE_TYPE(VT1200_VSXXXAA, vt1200_vsxxxaa_device)
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
// The undecoded windows, in bit-address units. 0x5900000 is paired with
|
||||
@@ -183,6 +494,8 @@ public:
|
||||
, m_mainram(*this, "mainram")
|
||||
, m_duart1(*this, "duart1")
|
||||
, m_duart2(*this, "duart2")
|
||||
, m_lk401(*this, "lk401")
|
||||
, m_mouse(*this, "mouse")
|
||||
, m_vram(*this, "vram")
|
||||
{
|
||||
}
|
||||
@@ -247,6 +560,13 @@ private:
|
||||
required_device<scn2681_device> m_duart2; // 0x0620000: keyboard(A)/mouse(B)
|
||||
bool m_duart1_int2 = false; // set by VT1200_SKIP_LEVEL1
|
||||
|
||||
// The real devices on DUART2. Keyboard on channel A, mouse on channel B,
|
||||
// as the board has them. Wired bit-by-bit to the SCN2681's serial lines.
|
||||
required_device<vt1200_lk401_device> m_lk401;
|
||||
required_device<vt1200_vsxxxaa_device> m_mouse;
|
||||
void duart2_a_tx_w(int state);
|
||||
void duart2_b_tx_w(int state);
|
||||
|
||||
// VT1200_IO_PATH_STUB=1: opt-in stand-in for the unknown test fixture on
|
||||
// the keyboard port, so level-0's count-only I/O data path test can pass.
|
||||
// NOT a keyboard model. See iopath_stub_arm() and the README.
|
||||
@@ -464,6 +784,7 @@ void vt1200_state::machine_start()
|
||||
logerror("vt1200: duart1=on (SCN2681 @0640000 host/printer), duart2=on (SCN2681 @0620000 kbd/mouse), skip_level1=%s\n",
|
||||
getenv("VT1200_SKIP_LEVEL1") ? "on (IP3 high, IRQ->INT2)" : "off (IP3 low, IRQ->INT1)");
|
||||
logerror("vt1200: io_path_stub=%s\n", m_iopath_stub ? "on (DUART2-A reply to 0xE1)" : "off (faithful)");
|
||||
logerror("vt1200: DUART2 devices: LK401 keyboard (ch A) + VSXXX-AA mouse (ch B)\n");
|
||||
}
|
||||
|
||||
void vt1200_state::machine_reset()
|
||||
@@ -788,9 +1109,10 @@ void vt1200_state::log_duart(int win, char kind, u32 reg, u16 value)
|
||||
{
|
||||
if (!m_duartlog)
|
||||
return;
|
||||
fprintf(m_duartlog, "%d %c reg=%X val=%04X pc=%08X cyc=%llu\n",
|
||||
fprintf(m_duartlog, "%d %c reg=%X val=%04X pc=%08X cyc=%llu t=%.6f\n",
|
||||
win, kind, reg, value, (unsigned)m_maincpu->state_int(TMS34010_PC),
|
||||
(unsigned long long)m_maincpu->total_cycles());
|
||||
(unsigned long long)m_maincpu->total_cycles(),
|
||||
machine().time().as_double());
|
||||
fflush(m_duartlog);
|
||||
}
|
||||
|
||||
@@ -1069,8 +1391,29 @@ void vt1200_state::vt1200(machine_config &config)
|
||||
// (OS) from VT1200_SKIP_LEVEL1; DUART2's trampoline is on INT2.
|
||||
m_duart1->irq_cb().set(FUNC(vt1200_state::duart1_irq_w));
|
||||
m_duart2->irq_cb().set(FUNC(vt1200_state::duart2_irq_w));
|
||||
|
||||
// The real devices on DUART2: channel A = keyboard (4800 8N1), channel B =
|
||||
// mouse (4800 8O1), exactly as the board wires them. Both talk to the
|
||||
// SCN2681 through the genuine device_serial_interface bit lines: the
|
||||
// device's transmit output drives the DUART's receiver input
|
||||
// (rx_a_w / rx_b_w), and the DUART's transmitter output
|
||||
// (a_tx_cb / b_tx_cb) drives the device's receiver input (rx_w). No
|
||||
// byte-level shortcut, so the framing and the 4800-baud timing are the
|
||||
// DUART's own.
|
||||
VT1200_LK401(config, m_lk401, 0);
|
||||
VT1200_VSXXXAA(config, m_mouse, 0);
|
||||
m_lk401->tx_handler().set(m_duart2, FUNC(scn2681_device::rx_a_w));
|
||||
m_mouse->tx_handler().set(m_duart2, FUNC(scn2681_device::rx_b_w));
|
||||
m_duart2->a_tx_cb().set(FUNC(vt1200_state::duart2_a_tx_w));
|
||||
m_duart2->b_tx_cb().set(FUNC(vt1200_state::duart2_b_tx_w));
|
||||
}
|
||||
|
||||
// These two just fan the DUART's serial transmitter output into the attached
|
||||
// device's receiver. (A state-level forward keeps the devcb binding simple;
|
||||
// rx_w() is public on device_serial_interface.)
|
||||
void vt1200_state::duart2_a_tx_w(int state) { m_lk401->rx_w(state); }
|
||||
void vt1200_state::duart2_b_tx_w(int state) { m_mouse->rx_w(state); }
|
||||
|
||||
INPUT_PORTS_START(vt1200)
|
||||
INPUT_PORTS_END
|
||||
|
||||
@@ -1083,6 +1426,449 @@ ROM_END
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
// vt1200_lk401_device -- HLE model of the DEC LK401 keyboard
|
||||
//**************************************************************************
|
||||
//
|
||||
// The protocol comes from reference/keyboard-mouse/PROTOCOL.md. This is not
|
||||
// the keyboard's firmware; it is a model of what the firmware does on the wire.
|
||||
// It answers `FD` with the documented 4-byte self-test because that is what the
|
||||
// part does, and it reports key transitions with the documented keycodes.
|
||||
|
||||
DEFINE_DEVICE_TYPE(VT1200_LK401, vt1200_lk401_device, "vt1200lk401", "DEC LK401 Keyboard")
|
||||
|
||||
vt1200_lk401_device::vt1200_lk401_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
|
||||
: device_t(mconfig, VT1200_LK401, tag, owner, clock)
|
||||
, device_buffered_serial_interface(mconfig, *this)
|
||||
, m_tx_handler(*this)
|
||||
, m_kbd(*this, "K%u", 0U)
|
||||
{
|
||||
}
|
||||
|
||||
ioport_constructor vt1200_lk401_device::device_input_ports() const
|
||||
{
|
||||
return INPUT_PORTS_NAME(vt1200_lk401);
|
||||
}
|
||||
|
||||
void vt1200_lk401_device::device_start()
|
||||
{
|
||||
m_scan_timer = timer_alloc(FUNC(vt1200_lk401_device::scan), this);
|
||||
m_power_up_timer = timer_alloc(FUNC(vt1200_lk401_device::power_up_selftest), this);
|
||||
|
||||
if (const char *p = getenv("VT1200_INPUT_LOG"))
|
||||
m_log = fopen(p, "a");
|
||||
if (m_log)
|
||||
fprintf(m_log, "# LK401 device started\n");
|
||||
}
|
||||
|
||||
void vt1200_lk401_device::device_reset()
|
||||
{
|
||||
// 4800 baud, 8 data bits, no parity, 1 stop bit (PROTOCOL.md section 3.1).
|
||||
set_data_frame(1, 8, PARITY_NONE, STOP_BITS_1);
|
||||
set_rate(4800);
|
||||
|
||||
clear_fifo();
|
||||
receive_register_reset();
|
||||
m_cmd.clear();
|
||||
m_down.fill(false);
|
||||
m_repeat.fill(attotime::never);
|
||||
m_updown_down = 0;
|
||||
m_autorepeat = true;
|
||||
m_lk401_mode = false;
|
||||
m_inhibit = false;
|
||||
m_led_mask = 0;
|
||||
m_lost_keys = false;
|
||||
for (int i = 1; i <= 14; i++)
|
||||
m_div_mode[i] = 0; // down-only at power-up
|
||||
|
||||
// The real keyboard runs its self-test and transmits the 4-byte result a
|
||||
// few tens of ms after power-up, before the host has configured the port.
|
||||
// Queued faithfully; if the DUART channel is not yet clocked the bytes are
|
||||
// discarded, which is exactly what happens on real hardware.
|
||||
if (m_power_up_timer)
|
||||
m_power_up_timer->adjust(attotime::from_msec(70));
|
||||
|
||||
// Poll the host keyboard at 120 Hz. Each scan reads the ioport state and
|
||||
// turns key transitions into keycodes.
|
||||
if (m_scan_timer)
|
||||
m_scan_timer->adjust(attotime::from_hz(120), 0, attotime::from_hz(120));
|
||||
}
|
||||
|
||||
bool vt1200_lk401_device::lk401_only(uint8_t code)
|
||||
{
|
||||
// Codes only reported after the `E9` LK401-mode command.
|
||||
return code == 0xab || code == 0xac || code == 0xad || code == 0xb2;
|
||||
}
|
||||
|
||||
void vt1200_lk401_device::queue(u8 byte)
|
||||
{
|
||||
if (m_log)
|
||||
fprintf(m_log, "LK401 tx %02X t=%.6f\n", byte, machine().time().as_double());
|
||||
transmit_byte(byte);
|
||||
}
|
||||
|
||||
void vt1200_lk401_device::tra_callback()
|
||||
{
|
||||
m_tx_handler(transmit_register_get_data_bit());
|
||||
}
|
||||
|
||||
void vt1200_lk401_device::received_byte(u8 byte)
|
||||
{
|
||||
if (m_log)
|
||||
fprintf(m_log, "LK401 rx %02X t=%.6f\n", byte, machine().time().as_double());
|
||||
// A command runs until a byte with bit 7 set arrives (PARAM bit: 1 = no
|
||||
// parameter follows). See PROTOCOL.md section 3.2 and the netbsd LK201
|
||||
// interface description.
|
||||
m_cmd.push_back(byte);
|
||||
if (byte & 0x80)
|
||||
{
|
||||
process_command();
|
||||
m_cmd.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void vt1200_lk401_device::process_command()
|
||||
{
|
||||
if (m_cmd.empty())
|
||||
return;
|
||||
const u8 op = m_cmd[0];
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case LK_CMD_POWER_UP:
|
||||
run_self_test();
|
||||
return;
|
||||
|
||||
case LK_CMD_REQUEST_ID:
|
||||
// Returns the same 2 bytes as the power-up self test (PROTOCOL.md 3.3).
|
||||
queue(LK_SELFTEST[0]);
|
||||
queue(LK_SELFTEST[1]);
|
||||
return;
|
||||
|
||||
case LK_CMD_SET_DEFAULTS:
|
||||
m_autorepeat = true;
|
||||
m_lk401_mode = false;
|
||||
for (int i = 1; i <= 14; i++)
|
||||
m_div_mode[i] = 0;
|
||||
return;
|
||||
|
||||
case LK_CMD_ENABLE_LK401:
|
||||
m_lk401_mode = true;
|
||||
return;
|
||||
|
||||
case LK_CMD_LED_ON:
|
||||
if (m_cmd.size() >= 2)
|
||||
m_led_mask |= (m_cmd[1] & 0x0f);
|
||||
return;
|
||||
|
||||
case LK_CMD_LED_OFF:
|
||||
if (m_cmd.size() >= 2)
|
||||
m_led_mask &= ~(m_cmd[1] & 0x0f);
|
||||
return;
|
||||
|
||||
case LK_CMD_KEYCLICK_ON:
|
||||
case LK_CMD_KEYCLICK_OFF:
|
||||
case LK_CMD_CTRLCLICK_ON:
|
||||
case LK_CMD_CTRLCLICK_OFF:
|
||||
case LK_CMD_BELL_ON:
|
||||
case LK_CMD_BELL_OFF:
|
||||
case LK_CMD_BELL:
|
||||
case LK_CMD_SOUND_CLICK:
|
||||
case LK_CMD_RPT_TO_DOWN:
|
||||
return;
|
||||
|
||||
case LK_CMD_ENB_RPT:
|
||||
m_autorepeat = true;
|
||||
return;
|
||||
|
||||
case LK_CMD_DIS_RPT:
|
||||
m_autorepeat = false;
|
||||
return;
|
||||
|
||||
case LK_CMD_MMS_NO_RPT:
|
||||
// Temporary auto-repeat inhibit for the key currently down.
|
||||
return;
|
||||
|
||||
case LK_CMD_RESUME:
|
||||
m_inhibit = false;
|
||||
if (m_lost_keys)
|
||||
{
|
||||
m_lost_keys = false;
|
||||
queue(LK_OUTPUT_ERROR);
|
||||
}
|
||||
return;
|
||||
|
||||
case LK_CMD_TEST_MODE:
|
||||
queue(LK_TEST_MODE_ACK);
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Mode commands: bit 7 set, bit 0 clear, division in bits 3-6, mode in
|
||||
// bits 1-2. Also reached with bit 7 clear (an autorepeat-register
|
||||
// association byte follows) -- same division/mode fields. The keyboard
|
||||
// acknowledges a successful change with BA.
|
||||
if (!(op & 0x01) && (op & 0x78))
|
||||
{
|
||||
const int div = (op >> 3) & 0x0f;
|
||||
const int mode = (op >> 1) & 0x03;
|
||||
if (div >= 1 && div <= 14)
|
||||
m_div_mode[div] = u8(mode);
|
||||
queue(LK_MODE_CHANGE_ACK);
|
||||
return;
|
||||
}
|
||||
|
||||
queue(LK_INPUT_ERROR);
|
||||
}
|
||||
|
||||
void vt1200_lk401_device::run_self_test()
|
||||
{
|
||||
// `FD` resets the keyboard and re-runs the self-test. Healthy result is
|
||||
// the documented four bytes; a stuck key would change byte 3/4 but we have
|
||||
// no stuck keys here.
|
||||
for (unsigned i = 0; i < sizeof(LK_SELFTEST); i++)
|
||||
queue(LK_SELFTEST[i]);
|
||||
}
|
||||
|
||||
TIMER_CALLBACK_MEMBER(vt1200_lk401_device::power_up_selftest)
|
||||
{
|
||||
run_self_test();
|
||||
}
|
||||
|
||||
void vt1200_lk401_device::emit_key(unsigned index, bool pressed)
|
||||
{
|
||||
const uint8_t code = VT1200_LK401_TABLE[index].code;
|
||||
const uint8_t div = VT1200_LK401_TABLE[index].div;
|
||||
|
||||
if (lk401_only(code) && !m_lk401_mode)
|
||||
return; // LK401-only key, host has not enabled LK401 mode
|
||||
|
||||
if (m_inhibit)
|
||||
{
|
||||
m_lost_keys = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const int mode = m_div_mode[div];
|
||||
|
||||
if (pressed)
|
||||
{
|
||||
queue(code);
|
||||
if (mode == 3)
|
||||
m_updown_down++;
|
||||
else if (mode == 1 && m_autorepeat)
|
||||
m_repeat[index] = machine().time() + attotime::from_msec(500);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mode == 3)
|
||||
{
|
||||
if (m_updown_down > 0)
|
||||
m_updown_down--;
|
||||
if (m_updown_down == 0)
|
||||
queue(LK_ALL_KEYS_UP);
|
||||
else
|
||||
queue(code);
|
||||
}
|
||||
m_repeat[index] = attotime::never;
|
||||
}
|
||||
}
|
||||
|
||||
TIMER_CALLBACK_MEMBER(vt1200_lk401_device::scan)
|
||||
{
|
||||
// A UART's TxD idles at mark (high). Driving the idle level matters for
|
||||
// the SCN2681's receiver: after its receiver is reset it needs to see an
|
||||
// explicit idle-high before the next start bit, or it synchronises late and
|
||||
// mis-samples the byte. Keeping the line high whenever we are not mid-byte
|
||||
// is both faithful and what makes the link work.
|
||||
if (fifo_empty() && is_transmit_register_empty())
|
||||
m_tx_handler(1);
|
||||
|
||||
const attotime now = machine().time();
|
||||
for (unsigned i = 0; i < VT1200_LK401_NKEYS; i++)
|
||||
{
|
||||
const bool down = (m_kbd[i]->read() & 1) != 0;
|
||||
if (down != m_down[i])
|
||||
{
|
||||
m_down[i] = down;
|
||||
emit_key(i, down);
|
||||
}
|
||||
if (down && m_repeat[i] != attotime::never && now >= m_repeat[i] && m_autorepeat && !m_inhibit)
|
||||
{
|
||||
// Auto-repeat tick (B4) for an auto-repeat-down key.
|
||||
queue(LK_METRONOME);
|
||||
m_repeat[i] += attotime::from_hz(20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
// vt1200_vsxxxaa_device -- DEC VSXXX-AA mouse
|
||||
//**************************************************************************
|
||||
|
||||
DEFINE_DEVICE_TYPE(VT1200_VSXXXAA, vt1200_vsxxxaa_device, "vt1200vsxxxaa", "DEC VSXXX-AA Mouse")
|
||||
|
||||
vt1200_vsxxxaa_device::vt1200_vsxxxaa_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
|
||||
: device_t(mconfig, VT1200_VSXXXAA, tag, owner, clock)
|
||||
, device_buffered_serial_interface(mconfig, *this)
|
||||
, m_tx_handler(*this)
|
||||
, m_host_x(*this, "MOUSE_X")
|
||||
, m_host_y(*this, "MOUSE_Y")
|
||||
, m_host_buttons(*this, "MOUSE_BTN")
|
||||
{
|
||||
}
|
||||
|
||||
ioport_constructor vt1200_vsxxxaa_device::device_input_ports() const
|
||||
{
|
||||
return INPUT_PORTS_NAME(vt1200_vsxxxaa);
|
||||
}
|
||||
|
||||
void vt1200_vsxxxaa_device::device_start()
|
||||
{
|
||||
m_scan_timer = timer_alloc(FUNC(vt1200_vsxxxaa_device::scan), this);
|
||||
m_power_up_timer = timer_alloc(FUNC(vt1200_vsxxxaa_device::power_up_selftest), this);
|
||||
|
||||
if (const char *p = getenv("VT1200_INPUT_LOG"))
|
||||
m_log = fopen(p, "a");
|
||||
if (m_log)
|
||||
fprintf(m_log, "# VSXXX-AA device started\n");
|
||||
}
|
||||
|
||||
void vt1200_vsxxxaa_device::device_reset()
|
||||
{
|
||||
// 4800 baud, 8 data bits, ODD parity, 1 stop bit (PROTOCOL.md section 4.1).
|
||||
set_data_frame(1, 8, PARITY_ODD, STOP_BITS_1);
|
||||
set_rate(4800);
|
||||
|
||||
clear_fifo();
|
||||
receive_register_reset();
|
||||
m_stream = false; // prompt mode is the power-up default
|
||||
m_last_x = u8(m_host_x->read());
|
||||
m_last_y = u8(m_host_y->read());
|
||||
m_acc_dx = 0;
|
||||
m_acc_dy = 0;
|
||||
m_last_buttons = 0;
|
||||
m_buttons_dirty = false;
|
||||
|
||||
if (m_power_up_timer)
|
||||
m_power_up_timer->adjust(attotime::from_msec(250));
|
||||
if (m_scan_timer)
|
||||
m_scan_timer->adjust(attotime::from_hz(120), 0, attotime::from_hz(120));
|
||||
}
|
||||
|
||||
void vt1200_vsxxxaa_device::queue(u8 byte)
|
||||
{
|
||||
if (m_log)
|
||||
fprintf(m_log, "VSXXX tx %02X t=%.6f\n", byte, machine().time().as_double());
|
||||
transmit_byte(byte);
|
||||
}
|
||||
|
||||
void vt1200_vsxxxaa_device::tra_callback()
|
||||
{
|
||||
m_tx_handler(transmit_register_get_data_bit());
|
||||
}
|
||||
|
||||
void vt1200_vsxxxaa_device::received_byte(u8 byte)
|
||||
{
|
||||
if (m_log)
|
||||
fprintf(m_log, "VSXXX rx %02X (%c) t=%.6f\n", byte, (byte >= 0x20 && byte < 0x7f) ? byte : '.', machine().time().as_double());
|
||||
switch (byte)
|
||||
{
|
||||
case MS_CMD_SELFTEST:
|
||||
send_self_test();
|
||||
break;
|
||||
case MS_CMD_STREAM:
|
||||
m_stream = true;
|
||||
break;
|
||||
case MS_CMD_PROMPT:
|
||||
m_stream = false;
|
||||
break;
|
||||
case MS_CMD_POSITION:
|
||||
m_stream = false;
|
||||
send_report(m_acc_dx, m_acc_dy, 0);
|
||||
m_acc_dx = m_acc_dy = 0;
|
||||
break;
|
||||
case MS_CMD_FORMAT:
|
||||
// Standard format: already the only one modelled.
|
||||
break;
|
||||
default:
|
||||
break; // invalid commands are ignored
|
||||
}
|
||||
}
|
||||
|
||||
void vt1200_vsxxxaa_device::send_self_test()
|
||||
{
|
||||
// Four-byte power-on/self-test report (PROTOCOL.md section 4.4, figure C-5):
|
||||
// header 101 + revision, manufacturer 0 + device code 0010 (mouse), error
|
||||
// 00, no buttons.
|
||||
queue(0xa0); // 1010 0000: header 101, revision 0
|
||||
queue(0x02); // manufacturer 0, device code 0010 = mouse
|
||||
queue(0x00); // error code 0 = OK
|
||||
queue(0x00); // no buttons
|
||||
}
|
||||
|
||||
void vt1200_vsxxxaa_device::send_report(int dx, int dy, int buttons)
|
||||
{
|
||||
int sx = (dx >= 0) ? 1 : 0;
|
||||
int sy = (dy >= 0) ? 1 : 0;
|
||||
int ax = (dx < 0) ? -dx : dx;
|
||||
int ay = (dy < 0) ? -dy : dy;
|
||||
if (ax > 0x7f) ax = 0x7f;
|
||||
if (ay > 0x7f) ay = 0x7f;
|
||||
|
||||
const u8 b0 = u8(0x80 | (sx << 4) | (sy << 3) | (buttons & 0x07));
|
||||
queue(b0);
|
||||
queue(u8(ax));
|
||||
queue(u8(ay));
|
||||
}
|
||||
|
||||
TIMER_CALLBACK_MEMBER(vt1200_vsxxxaa_device::power_up_selftest)
|
||||
{
|
||||
send_self_test();
|
||||
}
|
||||
|
||||
TIMER_CALLBACK_MEMBER(vt1200_vsxxxaa_device::scan)
|
||||
{
|
||||
// Hold TxD at idle-high when not transmitting (see the LK401 scan note).
|
||||
if (fifo_empty() && is_transmit_register_empty())
|
||||
m_tx_handler(1);
|
||||
|
||||
const u8 now_x = u8(m_host_x->read());
|
||||
const u8 now_y = u8(m_host_y->read());
|
||||
|
||||
// The MAME mouse inputs are relative; MAME accumulates them into the port
|
||||
// value, so a signed 8-bit difference (with wrap) is the movement since the
|
||||
// last scan. Screen Y grows downward; the VSXXX reports DEC Y (up).
|
||||
int dx = int(now_x) - int(m_last_x);
|
||||
int dy = int(now_y) - int(m_last_y);
|
||||
if (dx > 0x80) dx -= 0x100; else if (dx < -0x80) dx += 0x100;
|
||||
if (dy > 0x80) dy -= 0x100; else if (dy < -0x80) dy += 0x100;
|
||||
m_last_x = now_x;
|
||||
m_last_y = now_y;
|
||||
|
||||
const int buttons = m_host_buttons->read() & 0x07;
|
||||
|
||||
if (dx || dy)
|
||||
{
|
||||
m_acc_dx += dx;
|
||||
m_acc_dy -= dy; // DEC Y is inverted relative to screen Y
|
||||
}
|
||||
|
||||
if (m_stream)
|
||||
{
|
||||
if (m_acc_dx || m_acc_dy || buttons != m_last_buttons)
|
||||
{
|
||||
send_report(m_acc_dx, m_acc_dy, buttons);
|
||||
m_acc_dx = m_acc_dy = 0;
|
||||
}
|
||||
}
|
||||
m_last_buttons = buttons;
|
||||
}
|
||||
|
||||
|
||||
// year, name, parent, compat, machine, input, class, init, company, fullname
|
||||
CONS(1990, vt1200, 0, 0, vt1200, vt1200, vt1200_state, empty_init, "DEC",
|
||||
"VT1200 X terminal (v2.2 firmware)", MACHINE_NOT_WORKING | MACHINE_NO_SOUND)
|
||||
Reference in new issue
Block a user