spectrusty_peripherals/serial/
keypad.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8#![allow(clippy::inconsistent_digit_grouping)]
9#![allow(clippy::unusual_byte_groupings)]
10#[cfg(feature = "snapshot")]
11use serde::{Serialize, Deserialize};
12
13use rand::{Rng, SeedableRng};
14use rand::rngs::SmallRng;
15
16use spectrusty_core::clock::{FTs, TimestampOps};
17use super::{SerialPortDevice, DataState, ControlState};
18
19bitflags! {
20    /// Every key's state is encoded as a single bit on this 20-bit flag type.
21    /// * Bit = 1 a key is being pressed.
22    /// * Bit = 0 a key is not being pressed.
23    #[derive(Default)]
24    #[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
25    #[cfg_attr(feature = "snapshot", serde(from = "u32", into = "u32"))]
26    pub struct KeypadKeys: u32 {
27        /// The 1st (top) physical keypad row's mask.
28        const ROW1_MASK = 0b0000_0000_1111_0000_0000;
29        /// The 2nd physical keypad row's mask.
30        const ROW2_MASK = 0b0000_1111_0000_0000_0000;
31        /// The 3rd physical keypad row's mask.
32        const ROW3_MASK = 0b1111_0000_0000_0000_0000;
33        /// The 4th physical keypad row's mask.
34        const ROW4_MASK = 0b0000_0000_0000_1111_0000;
35        /// The 5th (bottom) physical keypad row's mask.
36        const ROW5_MASK = 0b0000_0000_0000_0000_1111;
37        const DOT       = 0b0000_0000_0000_0000_0010;
38        /// An alias of `DOT`.
39        const PERIOD    = Self::DOT.bits();
40        const N0        = 0b0000_0000_0000_0000_1000;
41        /// An alias of `N0`.
42        const SHIFT     = Self::N0.bits();
43        const ENTER     = 0b0000_0000_0000_0001_0000;
44        const N3        = 0b0000_0000_0000_0010_0000;
45        const N2        = 0b0000_0000_0000_0100_0000;
46        const N1        = 0b0000_0000_0000_1000_0000;
47        const RPAREN    = 0b0000_0000_0001_0000_0000;
48        /// An alias of `RPAREN`
49        const TOGGLE    = Self::RPAREN.bits();
50        const LPAREN    = 0b0000_0000_0010_0000_0000;
51        const ASTERISK  = 0b0000_0000_0100_0000_0000;
52        /// An alias of `ASTERISK`
53        const MULTIPLY  = Self::ASTERISK.bits();
54        const SLASH     = 0b0000_0000_1000_0000_0000;
55        /// An alias of `SLASH`
56        const DIVIDE    = Self::SLASH.bits();
57        const MINUS     = 0b0000_0001_0000_0000_0000;
58        /// An alias of `MINUS`
59        const CMND      = Self::MINUS.bits();
60        const N9        = 0b0000_0010_0000_0000_0000;
61        const N8        = 0b0000_0100_0000_0000_0000;
62        const N7        = 0b0000_1000_0000_0000_0000;
63        const PLUS      = 0b0001_0000_0000_0000_0000;
64        const N6        = 0b0010_0000_0000_0000_0000;
65        const N5        = 0b0100_0000_0000_0000_0000;
66        const N4        = 0b1000_0000_0000_0000_0000;
67    }
68}
69
70mod intervals {
71    use super::FTs;
72    pub const PRESENT_MAX_INTERVAL   : FTs = 3593;
73    pub const PRESENT_MIN_INTERVAL   : FTs = PRESENT_MAX_INTERVAL/10;
74    pub const CORRECT_MAX_INTERVAL   : FTs = 3917;
75    pub const CORRECT_MIN_INTERVAL   : FTs = CORRECT_MAX_INTERVAL/10;
76    pub const WAITING_MAX_INTERVAL   : FTs = 4121;
77    pub const WAITING_MIN_INTERVAL   : FTs = WAITING_MAX_INTERVAL/2;
78    pub const BITLOOP_MAX_INTERVAL   : FTs = 570;
79    pub const BITLOOP_MIN_INTERVAL   : FTs = BITLOOP_MAX_INTERVAL/4;
80    pub const READY_MAX_INTERVAL     : FTs = 250;
81    pub const READY_MIN_INTERVAL     : FTs = 50;
82    pub const RESET_MIN_INTERVAL     : FTs = 2_000_000;
83    pub const RESET_MAX_INTERVAL     : FTs = 7_000_000;
84    pub const SET_GO_TIMEOUT         : FTs = 2128; // 0.6 ms
85    pub const READY_START_TIMEOUT    : FTs = 709;  // 0.2 ms
86    pub const STOP_STAND_EASY_TIMEOUT: FTs = 4610; // 1.3 ms
87}
88use intervals::*;
89/// The ZX Spectrum 128 extension keypad.
90///
91/// For the history of this rare and often omitted in emulators device see:
92/// [Spectrum128Keypad.htm](http://www.fruitcake.plus.com/Sinclair/Spectrum128/Keypad/Spectrum128Keypad.htm).
93///
94/// ```text
95/// +-------+  +-------+  +-------+  +-------+
96/// | DEL ← |  | ↑     |  | DEL → |  |TOGGLE |
97/// |   /   |  |   *   |  |   (   |  |   )   |
98/// +-------+  +-------+  +-------+  +-------+
99///
100/// +-------+  +-------+  +-------+  +-------+
101/// | ←     |  | ↓     |  | →     |  | CMND  |
102/// |   7   |  |   8   |  |   9   |  |   -   |
103/// +-------+  +-------+  +-------+  +-------+
104///
105/// +-------+  +-------+  +-------+  +-------+
106/// |<< DEL |  |>> DEL |  |↑↑     |  |↑↑     |
107/// |   4   |  |   5   |  |   6   |  |   +   |
108/// +-------+  +-------+  +-------+  +-------+
109///
110/// +-------+  +-------+  +-------+  +-------+
111/// ||<< DEL|  |>>| DEL|  |⭱⭱     |  |   E   |
112/// |   1   |  |   2   |  |   3   |  |   N   |
113/// +-------+  +-------+  +-------+  |   T   |
114///                                  |   E   |
115/// +------------------+  +-------+  |   R   |
116/// |            SHIFT |  |⭳⭳     |  |       |
117/// |         0        |  |   .   |  |   =   |
118/// +------------------+  +-------+  +-------+
119///```
120///
121/// The extension keypad communicates with Spectrum using a unique protocol over the computer's serial connector.
122///
123/// This type implements [SerialPortDevice] bringing the extension keypad to its virtual existence.
124///
125/// To change the keypad state use methods directly on the implementation of this type.
126#[derive(Clone, Debug)]
127#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
128#[cfg_attr(feature = "snapshot", serde(rename_all = "camelCase"))]
129pub struct SerialKeypad<T> {
130    keys: KeypadKeys,
131    keys_changed: u32,
132    next_row: u8,
133    output_bits: u8,
134    keypad_io: KeypadIoStatus,
135    keypad_event_ts: T,
136    #[cfg_attr(feature = "snapshot", serde(skip, default = "SmallRng::from_entropy"))]
137    rng: SmallRng,
138}
139
140// status = 0 and a stop bit
141const STATUS0_ROW_DATA:     u8 = 0b_____10;
142// status = 1, 4 bit places and a stop bit
143const STATUS1_ROW_TEMPLATE: u8 = 0b10_0001;
144// transmitted after synchronization
145const SERIAL_DATA:          u8 = 0b_1_0100;
146
147#[inline(always)]
148fn row_data_to_output_bits(nibble: u8) -> u8 {
149    STATUS1_ROW_TEMPLATE | ((nibble & 0xF) << 1)
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq)]
153#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
154enum KeypadIoStatus {
155    Reset,
156    SyncPreset,
157    SyncCorrect,
158    Waiting,
159    Ready,
160    Data,
161    Stopped,
162}
163
164impl<T: Default + TimestampOps> Default for SerialKeypad<T> {
165    fn default() -> Self {
166        let keys = KeypadKeys::default();
167        let keys_changed = 0;
168        let next_row = 0;
169        let output_bits = 0;
170        let rng = SmallRng::from_entropy();
171        let keypad_event_ts = T::default() + RESET_MAX_INTERVAL;
172        let keypad_io = KeypadIoStatus::Reset;
173        SerialKeypad {
174            keys, keys_changed, next_row, output_bits,
175            keypad_event_ts, rng, keypad_io,
176        }
177    }
178}
179
180impl<T: TimestampOps> SerialPortDevice for SerialKeypad<T> {
181    type Timestamp = T;
182    #[inline(always)]
183    fn write_data(&mut self, _rxd: DataState, _timestamp: Self::Timestamp) -> ControlState {
184        ControlState::Inactive
185    }
186    #[inline(always)]
187    fn poll_ready(&mut self, _timestamp: Self::Timestamp) -> ControlState {
188        ControlState::Inactive
189    }
190    #[inline]
191    fn update_cts(&mut self, cts: ControlState, timestamp: Self::Timestamp) {
192        self.update_state(cts, timestamp)
193    }
194    #[inline]
195    fn read_data(&mut self, timestamp: Self::Timestamp) -> DataState {
196        self.read_state(timestamp)   
197    }
198    #[inline]
199    fn next_frame(&mut self, eof_timestamp: Self::Timestamp) {
200        if self.keypad_io != KeypadIoStatus::Reset {
201            // timeout everything other than reset state
202            if eof_timestamp.diff_from(self.keypad_event_ts) > RESET_MIN_INTERVAL as FTs {
203                self.reset_status(eof_timestamp);
204            }
205        }
206        self.keypad_event_ts = self.keypad_event_ts.saturating_sub(eof_timestamp);
207    }
208}
209
210impl<T> SerialKeypad<T> {
211    /// Reads the current state of the keypad.
212    #[inline]
213    pub fn get_key_state(&self) -> KeypadKeys {
214        self.keys
215    }
216    /// Sets the state of the keypad.
217    pub fn set_key_state(&mut self, keys: KeypadKeys) {
218        let keys_changed = (self.keys ^ keys).bits();
219        self.keys_changed |= keys_changed;
220        self.keys = keys;
221    }
222}
223
224impl<T: TimestampOps> SerialKeypad<T> {
225    #[inline]
226    fn gen_range_ts(&mut self, ts: T, lo: FTs, hi: FTs) -> T {
227        let delta = self.rng.gen_range(lo..hi);
228        ts + delta
229    }
230
231    #[inline]
232    fn initiate_output_data(&mut self) {
233        self.next_row = 0;
234        // we''ll transmit a full status of each row on the next scan
235        self.keys_changed = KeypadKeys::all().bits();
236        self.output_bits = SERIAL_DATA;
237    }
238
239    #[inline]
240    fn next_output_bit(&mut self) {
241        let bits = self.output_bits >> 1;
242        if (bits & !1) == 0 {
243            self.next_output_data();
244        }
245        else {
246            self.output_bits = bits
247        }
248    }
249
250    #[inline]
251    fn next_output_data(&mut self) {
252        let row = self.next_row % 5;
253        self.next_row = row + 1;
254        let row_bit_shift = 4 * row;
255        let row_mask = 0xF << row_bit_shift;
256        self.output_bits = if self.keys_changed & row_mask == 0 {
257            STATUS0_ROW_DATA
258        }
259        else {
260            let nibble = (self.keys.bits() >> row_bit_shift) as u8;
261            self.keys_changed &= !row_mask;
262            row_data_to_output_bits(nibble)
263        }
264    }
265
266    fn reset_status(&mut self, timestamp: T) {
267        self.keypad_io = KeypadIoStatus::Reset;
268        self.keypad_event_ts = self.gen_range_ts(timestamp, RESET_MIN_INTERVAL, RESET_MAX_INTERVAL);
269        // println!("reset status {}", V::vts_to_tstates(self.keypad_event_ts) - V::vts_to_tstates(timestamp));
270    }
271
272    fn read_state(&mut self, timestamp: T) -> DataState {
273        match self.keypad_io {
274            KeypadIoStatus::Reset|
275            KeypadIoStatus::Waiting => {
276                DataState::Mark
277            }
278            KeypadIoStatus::SyncPreset|
279            KeypadIoStatus::Ready => {
280                if timestamp >= self.keypad_event_ts { // PRESENT|READY
281                    DataState::Space
282                }
283                else {
284                    DataState::Mark
285                }
286            }
287            KeypadIoStatus::SyncCorrect => {
288                if timestamp >= self.keypad_event_ts { // CORRECT
289                    DataState::Mark
290                }
291                else {
292                    DataState::Space
293                }
294            }
295            KeypadIoStatus::Data => {
296                (self.output_bits & 1 == 1).into()
297            }
298            KeypadIoStatus::Stopped => DataState::Space
299        }
300    }
301
302    fn update_state(&mut self, cts: ControlState, timestamp: T) {
303        match self.keypad_io {
304            KeypadIoStatus::Reset => {
305                if timestamp >= self.keypad_event_ts {
306                    if cts.is_active() { // spectrum polls low (MARKS), respond with PRESENT after delay
307                        self.keypad_event_ts = self.gen_range_ts(timestamp, PRESENT_MIN_INTERVAL, PRESENT_MAX_INTERVAL);
308                        // println!("idle -> marks {}", V::vts_to_tstates(self.keypad_event_ts) - V::vts_to_tstates(timestamp));
309                        self.keypad_io = KeypadIoStatus::SyncPreset;
310                    }
311                    else {
312                        // println!("idle -> reset {}", V::vts_to_tstates(self.keypad_event_ts) - V::vts_to_tstates(timestamp));
313                        self.reset_status(timestamp);
314                    }
315                }
316            }
317            KeypadIoStatus::SyncPreset => {
318                assert!(cts.is_inactive(), "CTS must have been active before");
319                if timestamp >= self.keypad_event_ts { // SET
320                    // println!("prsent -> set {}", V::vts_to_tstates(timestamp) - V::vts_to_tstates(self.keypad_event_ts));
321                    self.keypad_event_ts = self.gen_range_ts(timestamp, CORRECT_MIN_INTERVAL, CORRECT_MAX_INTERVAL);
322                    self.keypad_io = KeypadIoStatus::SyncCorrect;
323                }
324                else {
325                    // println!("prsent -> reset {}", V::vts_to_tstates(timestamp) - V::vts_to_tstates(self.keypad_event_ts));
326                    self.reset_status(timestamp);
327                }
328            }
329            KeypadIoStatus::SyncCorrect => {
330                assert!(cts.is_active(), "CTS must have been inactive before");
331                if timestamp >= self.keypad_event_ts && // GO
332                   timestamp < self.keypad_event_ts + SET_GO_TIMEOUT { // timeout 0.6 ms
333                    // println!("correct -> waiting {}", V::vts_to_tstates(timestamp) - V::vts_to_tstates(self.keypad_event_ts));
334                    self.keypad_event_ts = self.gen_range_ts(timestamp, WAITING_MIN_INTERVAL, WAITING_MAX_INTERVAL);
335                    self.keypad_io = KeypadIoStatus::Waiting;
336                    self.initiate_output_data();
337                }
338                else {
339                    // println!("correct -> reset {}", V::vts_to_tstates(timestamp) - V::vts_to_tstates(self.keypad_event_ts));
340                    self.reset_status(timestamp);
341                }
342            }
343            KeypadIoStatus::Waiting => { // synchronized, we are high, cts is low
344                assert!(cts.is_inactive(), "CTS must have been active before");
345                // cts went high (ATTENTION), respond with READY after delay
346                // println!("waiting -> ready {}", V::vts_to_tstates(timestamp) - V::vts_to_tstates(self.keypad_event_ts));
347                self.keypad_event_ts = self.gen_range_ts(self.keypad_event_ts.max(timestamp),
348                                                        READY_MIN_INTERVAL, READY_MAX_INTERVAL); // timeout is 17643
349                self.keypad_io = KeypadIoStatus::Ready;
350            }
351            KeypadIoStatus::Ready => {
352                assert!(cts.is_active(), "CTS must have been inactive before");
353                if timestamp >= self.keypad_event_ts && // START
354                   timestamp < self.keypad_event_ts + READY_START_TIMEOUT { // timeout 0.2 ms
355                    // println!("ready -> data {}", V::vts_to_tstates(timestamp) - V::vts_to_tstates(self.keypad_event_ts));
356                    self.keypad_event_ts = timestamp; // for debug only
357                    self.keypad_io = KeypadIoStatus::Data;
358                    // println!("data: [{}] {:05b}", self.next_row, self.output_bits);
359                    // ? no timeout nor min limit?
360                }
361                else {
362                    // println!("ready -> reset {}", V::vts_to_tstates(timestamp) - V::vts_to_tstates(self.keypad_event_ts));
363                    self.reset_status(timestamp);
364                }
365            }
366            KeypadIoStatus::Data => { // transmitting data
367                assert!(cts.is_inactive(), "CTS must have been active before");
368                // cts went high (STOP), stop transmitting STOPPED (immediate)
369                // println!("data -> stop {}", V::vts_to_tstates(timestamp) - V::vts_to_tstates(self.keypad_event_ts));
370                self.keypad_event_ts = timestamp + STOP_STAND_EASY_TIMEOUT; // timeout 1.3 ms
371                self.keypad_io = KeypadIoStatus::Stopped;
372                // println!("{:06b}", self.output_bits);
373                self.next_output_bit();
374            }
375            KeypadIoStatus::Stopped => { // transmissison over
376                assert!(cts.is_active(), "CTS must have been inactive before");
377                if timestamp < self.keypad_event_ts { // STAND EASY
378                    // println!("stopped -> stand easy {}", V::vts_to_tstates(timestamp) - V::vts_to_tstates(self.keypad_event_ts) + STOP_STAND_EASY_TIMEOUT as i32);
379                    self.keypad_event_ts = self.gen_range_ts(timestamp, BITLOOP_MIN_INTERVAL, BITLOOP_MAX_INTERVAL);
380                    self.keypad_io = KeypadIoStatus::Waiting;
381                }
382                else { // timeout
383                    // println!("stopped -> reset {}", V::vts_to_tstates(timestamp) - V::vts_to_tstates(self.keypad_event_ts));
384                    self.reset_status(timestamp);
385                }
386            }
387        }
388    }
389}
390
391impl From<u32> for KeypadKeys {
392    fn from(keys: u32) -> Self {
393        KeypadKeys::from_bits_truncate(keys)
394    }
395}
396
397impl From<KeypadKeys> for u32 {
398    fn from(keys: KeypadKeys) -> Self {
399        keys.bits()
400    }
401}