harness: VSXXX-AB tablet on DUART2, driven by the absolute host pointer

The terminal tells the two pointing devices apart from the 'T' self-test
device code (0010 mouse / 0100 tablet) and switches report framing on it:
3-byte relative for the mouse, 5-byte absolute (header 110) for the tablet.
Verified against the running OS: with the tablet it reads the self-test as
A0 04 00 00 and then reads RHRB in groups of 5 (groups of 3 for the mouse).

Refactor the VSXXX model into a shared base + mouse/tablet subclasses and
select the device on DUART2-B with VT1200_TABLET=1 (default: the mouse, so
the mouse stays and is selectable, not replaced).  The tablet reports its
documented 0..2199 active area (VCB02 D.1) and reads the host pointer
through IPT_LIGHTGUN_X/Y, MAME's absolute control, so the emulated cursor
follows the host pointer positionally: corner (0,0) -> (194,38), centre
(1100,1100) -> (704,467), corner (2199,2199) -> (1214,898), reproducible
pixel-for-pixel on a repeated coordinate.  Driving it from the host mouse
needs '-lightgun -lightgun_device mouse' (documented for run.sh; run.sh is
not edited here).

The mouse and keyboard are unchanged, and all baselines hold: no env
PC=3FDCE400 samples=198 (3 PCs); VT1200_IO_PATH_STUB=1 samples=53 (75 PCs);
ROM md5 unchanged.  Nothing is tuned to satisfy level 0's I/O data path test.
This commit is contained in:
arti committed 2026-10-16 16:07:36 +03:00
1 parent 02acabb7ef
commit 065f946e2f
8 files changed
+638 -105

No files matched your search

+22 -9
View File
@@ -22,6 +22,7 @@ Reports in this directory:
| file | what it records |
|---|---|
| `GATE_CLEANUP_REPORT.md` | promoting the working config to the default; retiring the settled env gates |
| `TABLET_REPORT.md` | the VSXXX-AB tablet: the self-test device-code ID, the 5-byte absolute report, and absolute host-pointer input |
| `RENDER_REPORT.md` | the active-pixel scanline renderer, and the geometry it derived |
| `DUART_REPORT.md` | the DUARTs: wiring, the ISR-driven transmit, the datasheet-verified model edits |
| `BRINGUP_LOG.md` | the whole bring-up narrative, **including withdrawn claims** — read it sceptically |
@@ -68,11 +69,13 @@ to be a six-variable incantation; it is now the default, and a bare
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`.
**DEC LK401 keyboard** (channel A, 4800 8N1) and a pointing device on
channel B (4800 8O1) — either a **DEC VSXXX-AA mouse** (the default, relative)
or, with `VT1200_TABLET=1`, a **DEC VSXXX-AB tablet** (absolute). All are
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` and `TABLET_REPORT.md`.
* A mono palette (pens 0/1 = black/white) and an active-pixel scanline renderer
that reads VRAM. See `RENDER_REPORT.md`.
@@ -152,6 +155,13 @@ those commands, the OS reads `01 00 00 00` / `A0 02 00 00`, switches the mouse t
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).
With **`VT1200_TABLET=1`** the channel-B device is the **VSXXX-AB tablet**
instead: it identifies as device code `0100`, so the OS switches to 5-byte
absolute reports, and the emulated cursor follows the **host pointer's absolute
position** (driven through `IPT_LIGHTGUN_X/Y`, i.e. MAME needs
`-lightgun -lightgun_device mouse`). See `TABLET_REPORT.md` (evidence:
`tablet_cursor_*.png`). The mouse stays the default.
Not modelled: **LANCE Ethernet**. It does not block the X11 bring-up.
### Getting past level 1
@@ -165,7 +175,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 (18; down from 33)
## Environment knobs (19; 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.
@@ -174,9 +184,12 @@ 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_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).
`VT1200_INPUT_LOG` (per-byte keyboard/mouse/tablet TX/RX trace, with emulated
time). Behaviour switches: `VT1200_SKIP_LEVEL1`, `VT1200_TABLET` (put the
VSXXX-AB tablet on DUART2-B instead of the VSXXX-AA mouse — the tablet needs
MAME's `-lightgun -lightgun_device mouse` to follow the host pointer),
`VT1200_IO_PATH_STUB` (the level-0 test-fixture stand-in described above — not a
keyboard model, off by default).
## Regression baseline
+219
View File
@@ -0,0 +1,219 @@
# VSXXX-AB tablet on DUART2 — report
Adds the VT1200's **DEC VSXXX-AB digitizing tablet** as a third real device
model, selects it in place of the **VSXXX-AA mouse** on DUART2 channel B, and
drives it from the **host pointer's absolute position**. The emulated X11 cursor
now follows the host pointer positionally: moving the host pointer to a corner
puts the cursor in that corner.
Status: **works**. The OS accepts the tablet (it frames 5-byte absolute reports
instead of 3-byte relative ones), the cursor tracks the host pointer across the
whole screen, the mouse is still the default and still works, and every existing
regression baseline is unchanged.
---
## 1. The selection mechanism — how the terminal tells a tablet from a mouse
**It is an ID query, and the answer is one nibble of the self-test report.**
There is no separate "are you a tablet?" command and no setup/NVR record.
The host sends `T` (0x54, "self-test & identify"). The device returns the same
**4-byte self-test report** either way; byte 2's low nibble is the **device
code**, `0010` for the mouse and `0100` for the tablet (VCB02 §C.6.5 figure C-5,
§C.7.4). Only *then* does the report format differ:
```
100 - 3-byte relative position report (mouse)
101 - 4-byte self-test report (mouse or tablet)
110 - 5-byte absolute position report (tablet)
```
(VCB02 §C.6.6; Appendix D.3 / figure D-3 for the 5-byte layout.)
### Evidence that the terminal uses it
Captured from the running OS (`VT1200_SKIP_LEVEL1=1`, `VT1200_DUARTLOG`), which
is the terminal's own driver, not a test:
| step | mouse (default) | tablet (`VT1200_TABLET=1`) |
|---|---|---|
| host writes `T` to THRB (`reg B`) | `54` | `54` |
| host reads the 4-byte self-test | `A0 02 00 00` | `A0 04 00 00` |
| host writes `R` (stream mode) | `52` | `52` |
| **host reads RHRB per report** | **3 bytes** | **5 bytes** |
| example report | `98 20 00` (header `100`) | `C1 0C 11 0B 11` (header `110`) |
The reads are grouped by the waiting time between them (≈14 323 DUART cycles
inside a report, ≈75 519 between reports for the mouse): the OS reads exactly
3 bytes for a `100` header and exactly 5 for a `110` header. So the device code
from `T` is what switches the host's report framing — get it wrong and every
report is mis-framed, exactly as the brief warned.
**`PROTOCOL.md` is correct about all of this** — §4.5 states the tablet
self-test "differs only in device code (`0100`) and a 5-byte absolute-position
report (header `110`/`0xC0`)". No correction is needed. One fact worth adding
(sourced, not conflicting): the tablet's **coordinate range is `0..2199`** —
the 11-inch active area at 200 counts/inch (VCB02 Appendix D.1) — which is what
the terminal's pointer scaling expects (see §3).
---
## 2. What was implemented
`harness/mame/vt1200.cpp` only.
* The VSXXX device was refactored into a shared base
(`vt1200_vsxxx_base`: 4800 8**O**1 framing, the printable-ASCII command set
`R`/`D`/`P`/`T`/`S`, prompt power-up, idle-high TxD) with two subclasses:
* `vt1200_vsxxxaa_device` — the mouse, unchanged in behaviour (relative
3-byte reports, device code `0010`).
* `vt1200_vsxxxab_device` — the tablet, absolute (5-byte reports, device code
`0100`).
* Channel B carries **one** of them, chosen by **`VT1200_TABLET=1`** at machine
configuration time (default: the mouse). Only the selected device is
instantiated, so the connector carries one real part; the mouse is selectable,
not replaced.
* The tablet's `send_self_test()` reports device code `0100`
(`A0 04 00 00`); its `send_abs()` emits the Appendix-D figure D-3 format:
```
[0] 1 1 0 B4 B3 B2 B1 Pr
[1] 0 0 X5..X0 [2] 0 0 X11..X6
[3] 0 0 Y5..Y0 [4] 0 0 Y11..Y6
```
Y is reported DEC-style (up); the host pointer's Y is screen-style (down), so
it is inverted. `P` (request position) returns the current absolute report;
in stream mode a report is sent when the position or a button changes, and
one is forced on entering stream mode so the host gets an initial position.
* Host input for the tablet is **`IPT_LIGHTGUN_X` / `IPT_LIGHTGUN_Y`** with
`PORT_MINMAX(0, 2199)` — MAME's **absolute** pointing controls, so the value is
the host pointer's window position scaled into `0..2199`, not an accumulated
delta. Buttons B1/B2/B3 map to mouse left/middle/right; `Pr` (proximity) is
asserted, since the emulated pointer is always over the tablet.
* Both devices still hold TxD at mark when idle (the DUART receiver-sync trap
from `INPUT_REPORT.md`), inherited unchanged from the base `scan()`.
Nothing is tuned to satisfy level 0's I/O data-path test: the tablet's `T`
reply is 4 bytes, and the test is untouched (still fails honestly without
`VT1200_IO_PATH_STUB`).
### Why `IPT_LIGHTGUN_X/Y` and not something else
MAME's only *absolute* pointing control a host mouse can drive is
`IPT_LIGHTGUN_X/Y` (the SDL lightgun provider reads the mouse's window position
and maps it to the absolute axis: `input_sdl.cpp`, `sdl_lightgun_device`).
`IPT_MOUSE_X/Y` are relative (they would reproduce exactly the problem the owner
is trying to get away from). `IPT_POSITIONAL` is absolute too, but the SDL
mouse/lightgun providers do **not** supply it — only joystick axes do — so it
would need a joystick, not the host pointer. Lightgun it is, and because it is
absolute the pointer is **not captured** (no relative-mode grab), which is the
owner's stated motivation.
---
## 3. Does the cursor follow the host pointer? — YES, absolutely
Run: `VT1200_TABLET=1 VT1200_SKIP_LEVEL1=1`, tablet driven by
`harness/mame/tab_inject.lua`, frames dumped at each settled position.
| tablet coordinate (host) | device report | cursor on screen |
|---|---|---|
| `(0, 0)` top-left corner | `C1 00 00 17 22` | **(194, 38)** |
| `(2199, 2199)` bottom-right | `C1 17 22 00 00` | **(1214, 898)** |
| `(1100, 1100)` centre | `C1 0C 11 0B 11` | **(704, 467)** |
Each row is a different host position producing a different, correct cursor
position; the cursor moves across the whole screen (top-left corner, centre,
bottom-right corner). Evidence: `tablet_cursor_tl_f4600.png`,
`tablet_cursor_centre_f5100.png`, `tablet_cursor_br_f4850.png` (full frames,
downscaled) and `tablet_cursor_positions.png` (3× zoom at the cursor).
The mapping is **linear and absolute**, verified by repeating a coordinate in
one run: on an x-sweep with `y` fixed, `x=1024` produced a pixel-identical
cursor both times it was visited (`x1024a == x1024b`), so the cursor is a
function of the current absolute coordinate, not of the path taken.
The measured map is `screen_x ≈ 0.464·x` and `screen_y ≈ 898 − 0.392·(2199−y)`
in MAME-frame pixels. The apparent offsets are **not** terminal quirks: this
driver's renderer draws framebuffer column 0 at the device's horizontal-blanking
start (`heblnk = 192`) and framebuffer row 0 at `veblnk = 36`
(`render_active_line()`), and the tablet's `0..2199` maps onto the framebuffer's
`0..1024` / `0..864` visible rows. So the tablet's active area lands on the
terminal's *whole* screen — that is why the corners reach `(194,38)` and
`(1214,898)` rather than the frame's `(0,0)`/`(1279,900)`.
---
## 4. What it needs on the command line
To drive `IPT_LIGHTGUN_X/Y` from the host mouse, MAME needs
```
-lightgun -lightgun_device mouse
```
(`lightgun_device` defaults to `keyboard`; either option alone is enough to
auto-enable the host mouse for the lightgun control — MAME logs
*"Autoenabling mouse due to presence of a lightgun"* — but both is the
documented, explicit form). **`run.sh` is the owner's to edit**; the tablet mode
is therefore
```
VT1200_TABLET=1 VT1200_SKIP_LEVEL1=1 <mame> vt1200 ... \
-lightgun -lightgun_device mouse ... -autoboot_script harness/mame/tab_inject.lua
```
There is **no** extra option needed for the mouse (the default) and none to
*select* the tablet beyond the `VT1200_TABLET=1` environment knob, which is
consistent with the other `VT1200_*` behaviour switches. Note `-video none`
(headless) has no window, so the host-mouse path is only meaningful in a real
window; the headless acceptance run drives the same ports from Lua.
---
## 5. What still works
### The mouse is the default and still works
No `VT1200_TABLET` ⇒ `:mouse` is the VSXXX-AA:
* self-test `A0 02 00 00` (device code `0010`), then `R`, then 3-byte relative
reports `98 20 00`.
* the X11 cursor still moves: it was at `(768,468)` in frame 4400 and had left
that location by frame 4800 (same result as `INPUT_REPORT.md`).
### The keyboard still works
`input_inject.lua` presses `A`; the OS reads keycode `0xC2` from DUART2 channel A
at `t=62.2856 s` (`2 R reg=3 val=00C2 pc=3FB42710`), as before.
### Regression baselines — unchanged
| 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, "All Diagnostics Passed" | no |
ROM `md5 b46a3e442ccb86134d2356bd70d22fd2` — unchanged, and the driver's
`ROM_LOAD` CRC/SHA1 are untouched. Nothing was tuned to satisfy level 0's I/O
data-path test; that puzzle (`reference/level0-10byte-puzzle.md`) stands.
---
## 6. Caveats
* **HLE, not firmware.** Like the keyboard and mouse, this models what the
VSXXX-AB *does on the wire*, from `PROTOCOL.md` and VCB02 Appendix D; it does
not execute tablet firmware.
* **Tablet extras not modelled.** VCB02 Appendix D.4 adds report-rate commands
`K`/`L`/`M` (55/72/120 reports/s) and a `B` baud-switch (9600). They are
accepted and ignored (as the mouse ignores unknown commands); the VT1200 OS
uses none of them. Only 4800 baud is modelled, which is the default and what
the OS selects.
* **Coordinate range.** The model reports in the documented `0..2199` active
area. The 12-bit report field could carry more, but the part does not report
outside its active area, and the terminal's scaling expects this range.
* The exact pointer placement is the terminal's own (`heblnk`/`veblnk` frame
offset aside); the emulator only delivers an honest absolute coordinate.
+38
View File
@@ -0,0 +1,38 @@
-- Acceptance test: drive the modelled VSXXX-AB tablet with the host absolute
-- pointer and show the emulated cursor following it -- a corner, the opposite
-- corner, and the centre.
--
-- VT1200_TABLET=1 VT1200_SKIP_LEVEL1=1 \
-- VT1200_DUMP_FRAMES=4600,4850,5100 VT1200_DUMP_DIR=<dir> \
-- <mame> vt1200 ... -autoboot_script harness/mame/tab_inject.lua
--
-- Coordinates are the tablet's own: 0..2199 (11 in at 200 counts/in, VCB02 D.1),
-- Y screen-style downward (the device inverts it for the DEC report). A report
-- is emitted whenever the position changes, so the cursor jumps between points
-- and holds.
local machine = manager.machine
local ports = machine.ioport.ports
local tx = ports[":tablet:TABLET_X"].fields["Tablet X"]
local ty = ports[":tablet:TABLET_Y"].fields["Tablet Y"]
local PTS = {
{ 4400, 0, 0, "corner top-left" },
{ 4650, 2199, 2199, "corner bot-right" },
{ 4900, 1100, 1100, "centre" },
}
local frames = 0
local next = 1
emu.register_frame_done(function()
frames = frames + 1
if next <= #PTS and frames == PTS[next][1] then
tx:set_value(PTS[next][2])
ty:set_value(PTS[next][3])
print(string.format("tab: frame %d %-18s x=%d y=%d", frames, PTS[next][4], PTS[next][2], PTS[next][3]))
next = next + 1
end
if frames >= 5150 then
machine:exit()
end
end, "vt1200-tab-inject")
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+359 -96
View File
@@ -402,30 +402,67 @@ INPUT_PORTS_START(vt1200_vsxxxaa)
PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_BUTTON3) PORT_CODE(MOUSECODE_BUTTON3) PORT_PLAYER(1) // right
INPUT_PORTS_END
// --- VSXXX-AA mouse ---------------------------------------------------------
// Tablet host inputs. The VSXXX-AB is an ABSOLUTE pointing device, so its X/Y
// come from MAME's absolute pointing controls, IPT_LIGHTGUN_X / IPT_LIGHTGUN_Y:
// their value is the host pointer's position in the MAME window scaled into
// MIN..MAX (0..4095, the tablet's 12-bit coordinate space), NOT an accumulated
// delta. That is exactly the property the mouse cannot offer. Drive them from
// the host mouse with `-lightgun -lightgun_device mouse` (lightgun_device
// defaults to keyboard); see TABLET_REPORT.md and run.sh.
INPUT_PORTS_START(vt1200_vsxxxab)
PORT_START("TABLET_X")
PORT_BIT(0x0fff, 0x0000, IPT_LIGHTGUN_X) PORT_MINMAX(0x0000, 2199) PORT_SENSITIVITY(100) PORT_KEYDELTA(0) PORT_PLAYER(1) PORT_NAME("Tablet X")
PORT_START("TABLET_Y")
PORT_BIT(0x0fff, 0x0000, IPT_LIGHTGUN_Y) PORT_MINMAX(0x0000, 2199) PORT_SENSITIVITY(100) PORT_KEYDELTA(0) PORT_PLAYER(1) PORT_NAME("Tablet Y")
// VSXXX-AB ABS report byte 0: 110 B4 B3 B2 B1 Pr. By convention puck
// B1->mouse left, B2->middle, B3->right; B4 is the puck's fourth button.
// Pr ("in proximity") is held asserted: the emulated pointer is always
// present over the tablet, so the host always has a valid position.
PORT_START("TABLET_BTN")
PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_CODE(MOUSECODE_BUTTON1) PORT_PLAYER(1) // B1 left
PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_BUTTON2) PORT_CODE(MOUSECODE_BUTTON2) PORT_PLAYER(1) // B2 middle
PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_BUTTON3) PORT_CODE(MOUSECODE_BUTTON3) PORT_PLAYER(1) // B3 right
PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_BUTTON4) PORT_CODE(MOUSECODE_BUTTON4) PORT_PLAYER(1) // B4
INPUT_PORTS_END
// --- VSXXX family: VSXXX-AA/-GA mouse + VSXXX-AB tablet ---------------------
// 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`).
//
// The mouse and the tablet share that whole serial interface and command set
// (VCB02 C.7.4: "the tablet uses the same connector and serial interface
// format as the mouse, and recognizes the same commands"). Exactly two things
// differ, and they are the subclass hooks below:
//
// * the self-test ID: byte 2's device code is 0010 for the mouse and 0100
// for the tablet (VCB02 C.6.5 figure C-5 + C.7.4). This is how the host
// tells the two apart -- there is no separate "is it a tablet?" command.
// * the position report: 3-byte relative (header 100) for the mouse vs
// 5-byte absolute (header 110) for the tablet (VCB02 C.6.6 + D.3).
enum : uint8_t
{
MS_CMD_STREAM = 'R',
MS_CMD_PROMPT = 'D',
MS_CMD_POSITION = 'P',
MS_CMD_SELFTEST = 'T',
MS_CMD_FORMAT = 'S'
VS_CMD_STREAM = 'R',
VS_CMD_PROMPT = 'D',
VS_CMD_POSITION = 'P',
VS_CMD_SELFTEST = 'T',
VS_CMD_FORMAT = 'S'
};
class vt1200_vsxxxaa_device : public device_t, public device_buffered_serial_interface<16U>
class vt1200_vsxxx_base : 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);
vt1200_vsxxx_base(const machine_config &mconfig, device_type type, const char *tag,
device_t *owner, uint32_t clock, const char *x_tag, const char *y_tag,
const char *btn_tag);
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;
@@ -433,9 +470,14 @@ protected:
TIMER_CALLBACK_MEMBER(scan);
TIMER_CALLBACK_MEMBER(power_up_selftest);
private:
void send_self_test();
void send_report(int dx, int dy, int buttons);
// What the two parts do differently.
virtual void send_self_test() = 0; // device-code byte differs
virtual void sample_host() = 0; // read the host input for this tick
virtual bool stream_report_due() = 0; // a report is waiting to be sent
virtual void emit_stream_report() = 0; // ...send it (REL vs ABS)
virtual void emit_position_report() = 0; // answer `P`
virtual void on_stream_mode() { } // entered stream mode (`R`)
void queue(u8 byte);
devcb_write_line m_tx_handler;
@@ -446,18 +488,67 @@ private:
emu_timer *m_power_up_timer = nullptr;
bool m_stream = false; // false = prompt mode (power-up default)
FILE *m_log = nullptr;
};
// VSXXX-AA/-GA mouse: relative.
class vt1200_vsxxxaa_device : public vt1200_vsxxx_base
{
public:
vt1200_vsxxxaa_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
protected:
virtual void device_reset() override ATTR_COLD;
virtual ioport_constructor device_input_ports() const override ATTR_COLD;
virtual void send_self_test() override;
virtual void sample_host() override;
virtual bool stream_report_due() override;
virtual void emit_stream_report() override;
virtual void emit_position_report() override;
private:
void send_report(int dx, int dy, int buttons);
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;
int m_buttons = 0;
};
FILE *m_log = nullptr;
// VSXXX-AB tablet: absolute.
class vt1200_vsxxxab_device : public vt1200_vsxxx_base
{
public:
vt1200_vsxxxab_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
protected:
virtual void device_reset() override ATTR_COLD;
virtual ioport_constructor device_input_ports() const override ATTR_COLD;
virtual void send_self_test() override;
virtual void sample_host() override;
virtual bool stream_report_due() override;
virtual void emit_stream_report() override;
virtual void emit_position_report() override;
virtual void on_stream_mode() override;
private:
void send_abs(int x, int y, int buttons);
int m_x = 0; // 12-bit absolute tablet coordinates
int m_y = 0; // (host pointer scaled into 0..0xfff)
int m_buttons = 0;
int m_last_x = -1; // -1 forces a first report on entering stream
int m_last_y = -1;
int m_last_buttons = 0;
};
DECLARE_DEVICE_TYPE(VT1200_LK401, vt1200_lk401_device)
DECLARE_DEVICE_TYPE(VT1200_VSXXXAA, vt1200_vsxxxaa_device)
DECLARE_DEVICE_TYPE(VT1200_VSXXXAB, vt1200_vsxxxab_device)
namespace {
@@ -496,6 +587,7 @@ public:
, m_duart2(*this, "duart2")
, m_lk401(*this, "lk401")
, m_mouse(*this, "mouse")
, m_tablet(*this, "tablet")
, m_vram(*this, "vram")
{
}
@@ -560,10 +652,16 @@ 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.
// The real devices on DUART2. Keyboard on channel A, pointing device on
// channel B, as the board has them. Wired bit-by-bit to the SCN2681's
// serial lines. Channel B carries EITHER the VSXXX-AA mouse (the default)
// OR the VSXXX-AB tablet, selected at machine-configuration time by
// VT1200_TABLET=1; exactly one is instantiated, so the connector carries
// one real part.
required_device<vt1200_lk401_device> m_lk401;
required_device<vt1200_vsxxxaa_device> m_mouse;
optional_device<vt1200_vsxxxaa_device> m_mouse;
optional_device<vt1200_vsxxxab_device> m_tablet;
bool m_tablet_selected = false;
void duart2_a_tx_w(int state);
void duart2_b_tx_w(int state);
@@ -1393,17 +1491,29 @@ void vt1200_state::vt1200(machine_config &config)
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.
// the pointing device (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_TABLET=1 puts the VSXXX-AB tablet on channel B instead of the
// VSXXX-AA mouse. The two are the same connector; the terminal tells them
// apart from the self-test ID, so only one is instantiated.
m_tablet_selected = (getenv("VT1200_TABLET") != nullptr);
VT1200_LK401(config, m_lk401, 0);
VT1200_VSXXXAA(config, m_mouse, 0);
if (m_tablet_selected)
{
VT1200_VSXXXAB(config, m_tablet, 0);
m_tablet->tx_handler().set(m_duart2, FUNC(scn2681_device::rx_b_w));
}
else
{
VT1200_VSXXXAA(config, m_mouse, 0);
m_mouse->tx_handler().set(m_duart2, FUNC(scn2681_device::rx_b_w));
}
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));
}
@@ -1412,7 +1522,13 @@ void vt1200_state::vt1200(machine_config &config)
// 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); }
void vt1200_state::duart2_b_tx_w(int state)
{
if (m_tablet_selected)
m_tablet->rx_w(state);
else
m_mouse->rx_w(state);
}
INPUT_PORTS_START(vt1200)
INPUT_PORTS_END
@@ -1706,38 +1822,32 @@ TIMER_CALLBACK_MEMBER(vt1200_lk401_device::scan)
//**************************************************************************
// vt1200_vsxxxaa_device -- DEC VSXXX-AA mouse
// vt1200_vsxxx_base -- shared DEC VSXXX serial pointer interface
//**************************************************************************
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)
vt1200_vsxxx_base::vt1200_vsxxx_base(const machine_config &mconfig, device_type type, const char *tag,
device_t *owner, uint32_t clock, const char *x_tag, const char *y_tag, const char *btn_tag)
: device_t(mconfig, type, 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")
, m_host_x(*this, x_tag)
, m_host_y(*this, y_tag)
, m_host_buttons(*this, btn_tag)
{
}
ioport_constructor vt1200_vsxxxaa_device::device_input_ports() const
void vt1200_vsxxx_base::device_start()
{
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);
m_scan_timer = timer_alloc(FUNC(vt1200_vsxxx_base::scan), this);
m_power_up_timer = timer_alloc(FUNC(vt1200_vsxxx_base::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");
fprintf(m_log, "# %s device started\n", shortname());
}
void vt1200_vsxxxaa_device::device_reset()
void vt1200_vsxxx_base::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);
@@ -1746,12 +1856,6 @@ void vt1200_vsxxxaa_device::device_reset()
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));
@@ -1759,39 +1863,39 @@ void vt1200_vsxxxaa_device::device_reset()
m_scan_timer->adjust(attotime::from_hz(120), 0, attotime::from_hz(120));
}
void vt1200_vsxxxaa_device::queue(u8 byte)
void vt1200_vsxxx_base::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()
void vt1200_vsxxx_base::tra_callback()
{
m_tx_handler(transmit_register_get_data_bit());
}
void vt1200_vsxxxaa_device::received_byte(u8 byte)
void vt1200_vsxxx_base::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:
case VS_CMD_SELFTEST:
send_self_test();
break;
case MS_CMD_STREAM:
case VS_CMD_STREAM:
m_stream = true;
on_stream_mode();
break;
case MS_CMD_PROMPT:
case VS_CMD_PROMPT:
m_stream = false;
break;
case MS_CMD_POSITION:
case VS_CMD_POSITION:
m_stream = false;
send_report(m_acc_dx, m_acc_dy, 0);
m_acc_dx = m_acc_dy = 0;
emit_position_report();
break;
case MS_CMD_FORMAT:
case VS_CMD_FORMAT:
// Standard format: already the only one modelled.
break;
default:
@@ -1799,11 +1903,97 @@ void vt1200_vsxxxaa_device::received_byte(u8 byte)
}
}
TIMER_CALLBACK_MEMBER(vt1200_vsxxx_base::power_up_selftest)
{
send_self_test();
}
TIMER_CALLBACK_MEMBER(vt1200_vsxxx_base::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);
sample_host();
if (m_stream && stream_report_due())
emit_stream_report();
}
//**************************************************************************
// vt1200_vsxxxaa_device -- DEC VSXXX-AA mouse (relative)
//**************************************************************************
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)
: vt1200_vsxxx_base(mconfig, VT1200_VSXXXAA, tag, owner, clock, "MOUSE_X", "MOUSE_Y", "MOUSE_BTN")
{
}
ioport_constructor vt1200_vsxxxaa_device::device_input_ports() const
{
return INPUT_PORTS_NAME(vt1200_vsxxxaa);
}
void vt1200_vsxxxaa_device::device_reset()
{
vt1200_vsxxx_base::device_reset();
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 = 0;
}
void vt1200_vsxxxaa_device::sample_host()
{
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;
if (dx || dy)
{
m_acc_dx += dx;
m_acc_dy -= dy; // DEC Y is inverted relative to screen Y
}
m_buttons = m_host_buttons->read() & 0x07;
}
bool vt1200_vsxxxaa_device::stream_report_due()
{
return m_acc_dx || m_acc_dy || m_buttons != m_last_buttons;
}
void vt1200_vsxxxaa_device::emit_stream_report()
{
send_report(m_acc_dx, m_acc_dy, m_buttons);
m_acc_dx = m_acc_dy = 0;
m_last_buttons = m_buttons;
}
void vt1200_vsxxxaa_device::emit_position_report()
{
send_report(m_acc_dx, m_acc_dy, m_buttons);
m_acc_dx = m_acc_dy = 0;
m_last_buttons = m_buttons;
}
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.
// Four-byte power-on/self-test report (PROTOCOL.md section 4.4, VCB02 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
@@ -1825,47 +2015,120 @@ void vt1200_vsxxxaa_device::send_report(int dx, int dy, int buttons)
queue(u8(ay));
}
TIMER_CALLBACK_MEMBER(vt1200_vsxxxaa_device::power_up_selftest)
//**************************************************************************
// vt1200_vsxxxab_device -- DEC VSXXX-AB tablet (absolute)
//**************************************************************************
//
// Same connector, same 4800 8O1 serial format and the same command set as the
// mouse (VCB02 C.7.4). The two differences are the self-test device code
// (0100) and the 5-byte absolute position report (header 110). Position comes
// from the host pointer via IPT_LIGHTGUN_X/Y, so the emulated cursor is
// positionally mapped rather than integrated from deltas.
DEFINE_DEVICE_TYPE(VT1200_VSXXXAB, vt1200_vsxxxab_device, "vt1200vsxxxab", "DEC VSXXX-AB Tablet")
// The tablet's coordinate space is its active area: 11 in square at 200
// counts/in = 2200 counts, so 0..2199 (VCB02 Appendix D.1). The 12-bit report
// field (figure D-3) can carry a larger value, but the part only reports inside
// its active area. The host absolute pointer is scaled into this range by the
// ioport layer; see INPUT_PORTS_NAME(vt1200_vsxxxab).
static const int VSXXX_ABS_MAX = 2199;
vt1200_vsxxxab_device::vt1200_vsxxxab_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: vt1200_vsxxx_base(mconfig, VT1200_VSXXXAB, tag, owner, clock, "TABLET_X", "TABLET_Y", "TABLET_BTN")
{
send_self_test();
}
TIMER_CALLBACK_MEMBER(vt1200_vsxxxaa_device::scan)
ioport_constructor vt1200_vsxxxab_device::device_input_ports() const
{
// Hold TxD at idle-high when not transmitting (see the LK401 scan note).
if (fifo_empty() && is_transmit_register_empty())
m_tx_handler(1);
return INPUT_PORTS_NAME(vt1200_vsxxxab);
}
const u8 now_x = u8(m_host_x->read());
const u8 now_y = u8(m_host_y->read());
void vt1200_vsxxxab_device::device_reset()
{
vt1200_vsxxx_base::device_reset();
m_x = 0;
m_y = 0;
m_buttons = 0;
m_last_x = -1; // force a first report when stream mode starts
m_last_y = -1;
m_last_buttons = 0;
}
// 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;
void vt1200_vsxxxab_device::on_stream_mode()
{
m_last_x = -1; // send the current absolute position at once
m_last_y = -1;
}
const int buttons = m_host_buttons->read() & 0x07;
void vt1200_vsxxxab_device::sample_host()
{
// The port already scales the host pointer into 0..VSXXX_ABS_MAX; clamp
// defensively (do NOT mask -- VSXXX_ABS_MAX is a value, not a bit mask).
m_x = int(m_host_x->read());
m_y = int(m_host_y->read());
if (m_x < 0) m_x = 0;
if (m_x > VSXXX_ABS_MAX) m_x = VSXXX_ABS_MAX;
if (m_y < 0) m_y = 0;
if (m_y > VSXXX_ABS_MAX) m_y = VSXXX_ABS_MAX;
m_buttons = m_host_buttons->read() & 0x1e; // B4..B1 (Pr is added on send)
}
if (dx || dy)
{
m_acc_dx += dx;
m_acc_dy -= dy; // DEC Y is inverted relative to screen Y
}
bool vt1200_vsxxxab_device::stream_report_due()
{
return m_x != m_last_x || m_y != m_last_y || m_buttons != m_last_buttons;
}
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;
void vt1200_vsxxxab_device::emit_stream_report()
{
send_abs(m_x, m_y, m_buttons);
m_last_x = m_x;
m_last_y = m_y;
m_last_buttons = m_buttons;
}
void vt1200_vsxxxab_device::emit_position_report()
{
sample_host();
send_abs(m_x, m_y, m_buttons);
}
void vt1200_vsxxxab_device::send_self_test()
{
// Same 4-byte self-test format as the mouse, but device code 0100 = tablet
// (VCB02 C.6.5 figure C-5 and C.7.4). This is what a host reads to learn
// that the pointing device is a tablet rather than a mouse.
queue(0xa0); // 1010 0000: header 101, revision 0
queue(0x04); // manufacturer 0, device code 0100 = tablet
queue(0x00); // error code 0 = OK
queue(0x00); // no buttons
}
void vt1200_vsxxxab_device::send_abs(int x, int y, int buttons)
{
// VSXXX-AB absolute position report, 5 bytes, header 110 (VCB02 D.3 /
// figure D-3):
// [0] 1 1 0 B4 B3 B2 B1 Pr
// [1] 0 0 X5 X4 X3 X2 X1 X0
// [2] 0 0 X11 X10 X9 X8 X7 X6
// [3] 0 0 Y5 Y4 Y3 Y2 Y1 Y0
// [4] 0 0 Y11 Y10 Y9 Y8 Y7 Y6
// Y is reported DEC-style (up); the host pointer's Y is screen-style (down),
// so it is inverted. Pr is asserted: the emulated pointer is always in
// proximity, so the host always has a valid position.
if (x < 0) x = 0;
if (x > VSXXX_ABS_MAX) x = VSXXX_ABS_MAX;
if (y < 0) y = 0;
if (y > VSXXX_ABS_MAX) y = VSXXX_ABS_MAX;
const int dy = VSXXX_ABS_MAX - y;
const u8 b0 = u8(0xc0 | (buttons & 0x1e) | 0x01);
queue(b0);
queue(u8(x & 0x3f));
queue(u8((x >> 6) & 0x3f));
queue(u8(dy & 0x3f));
queue(u8((dy >> 6) & 0x3f));
}