diff --git a/harness/mame/README.md b/harness/mame/README.md index bfd72dd..477f114 100644 --- a/harness/mame/README.md +++ b/harness/mame/README.md @@ -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 diff --git a/harness/mame/TABLET_REPORT.md b/harness/mame/TABLET_REPORT.md new file mode 100644 index 0000000..fd5c2ff --- /dev/null +++ b/harness/mame/TABLET_REPORT.md @@ -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 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. diff --git a/harness/mame/tab_inject.lua b/harness/mame/tab_inject.lua new file mode 100644 index 0000000..7e7068c --- /dev/null +++ b/harness/mame/tab_inject.lua @@ -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= \ +-- 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") diff --git a/harness/mame/tablet_cursor_br_f4850.png b/harness/mame/tablet_cursor_br_f4850.png new file mode 100644 index 0000000..67ef40e Binary files /dev/null and b/harness/mame/tablet_cursor_br_f4850.png differ diff --git a/harness/mame/tablet_cursor_centre_f5100.png b/harness/mame/tablet_cursor_centre_f5100.png new file mode 100644 index 0000000..c755648 Binary files /dev/null and b/harness/mame/tablet_cursor_centre_f5100.png differ diff --git a/harness/mame/tablet_cursor_positions.png b/harness/mame/tablet_cursor_positions.png new file mode 100644 index 0000000..7874565 Binary files /dev/null and b/harness/mame/tablet_cursor_positions.png differ diff --git a/harness/mame/tablet_cursor_tl_f4600.png b/harness/mame/tablet_cursor_tl_f4600.png new file mode 100644 index 0000000..9dcfdbe Binary files /dev/null and b/harness/mame/tablet_cursor_tl_f4600.png differ diff --git a/harness/mame/vt1200.cpp b/harness/mame/vt1200.cpp index d1d6e10..37af0b0 100644 --- a/harness/mame/vt1200.cpp +++ b/harness/mame/vt1200.cpp @@ -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 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 m_lk401; - required_device m_mouse; + optional_device m_mouse; + optional_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)); }