spectrusty_peripherals/
serial.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//! Serial port related API and emulation of ZX Spectrum's peripheral devices using serial communication.
9//!
10//! The terminology regarding serial port pins/lines being used in ZX Spectrum's technical documentation:
11//!
12//! * `RxD` (Receive Data) Transmitted data from Spectrum to the remote device.
13//! * `CTS` (Clear to Send) Tells remote station that Spectrum wishes to send data.
14//! * `TxD` (Transmit Data) Received data from the remote device.
15//! * `DTR` (Data Terminal Ready) Tells Spectrum that the remote station wishes to send data.
16#[cfg(feature = "snapshot")]
17use serde::{Serialize, Deserialize};
18
19mod keypad;
20mod rs232;
21
22pub use rs232::*;
23pub use keypad::*;
24
25/// A type representing a state on one of the `DATA` lines: `RxD` or `TxD`.
26///
27/// `Space` represents a logical 0 (positive voltage), while `Mark` represents a logical 1 (negative voltage).
28#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
29#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
30#[repr(u8)]
31pub enum DataState {
32    Space = 0,
33    Mark = 1
34}
35
36/// A type representing a state on one of the `CONTROL` lines: `CTS` or `DTR`.
37///
38/// `Active` represents a logical 0 (positive voltage), while `Inactive` represents a logical 1 (negative voltage).
39#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
40#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
41#[repr(u8)]
42pub enum ControlState {
43    Active = 0,
44    Inactive = 1
45}
46
47/// An interface for emulating communication between a ZX Spectrum's hardware port and remote devices.
48///
49/// Emulators of peripheral devices should implement this trait.
50///
51/// Methods of this trait are being called by bus devices implementing serial port communication.
52pub trait SerialPortDevice {
53    type Timestamp: Sized;
54    /// A device receives a current `RxD` state from Spectrum and should output a `DTR` line state.
55    ///
56    /// This method is being called when a `CPU` writes (OUT) to a port and only when the `CTS` line isn't 
57    /// changed by the write.
58    fn write_data(&mut self, rxd: DataState, timestamp: Self::Timestamp) -> ControlState;
59    /// This method is being called once every frame, near the end of it, and should return a `DTR` line state.
60    fn poll_ready(&mut self, timestamp: Self::Timestamp) -> ControlState;
61    /// Receives an updated `CTS` line state.
62    ///
63    /// This method is being called when a `CPU` writes (OUT) to a port and only when the value of `CTS` changes.
64    fn update_cts(&mut self, cts: ControlState, timestamp: Self::Timestamp);
65    /// Should return a `TxD` line state.
66    ///
67    /// This method is being called when a `CPU` reads (IN) from a port.
68    fn read_data(&mut self, timestamp: Self::Timestamp) -> DataState;
69    /// Called when the current frame ends to allow emulators to wrap stored timestamps.
70    fn next_frame(&mut self, eof_timestamp: Self::Timestamp);
71}
72
73impl DataState {
74    #[inline]
75    pub fn is_space(self) -> bool {
76        self == DataState::Space
77    }
78    #[inline]
79    pub fn is_mark(self) -> bool {
80        self == DataState::Mark
81    }
82}
83
84impl ControlState {
85    #[inline]
86    pub fn is_active(self) -> bool {
87        self == ControlState::Active
88    }
89    #[inline]
90    pub fn is_inactive(self) -> bool {
91        self == ControlState::Inactive
92    }
93}
94
95impl From<DataState> for bool {
96    #[inline]
97    fn from(ds: DataState) -> bool {
98        match ds {
99            DataState::Space => false,
100            DataState::Mark => true,
101        }
102    }
103}
104
105impl From<DataState> for u8 {
106    #[inline]
107    fn from(ds: DataState) -> u8 {
108        ds as u8
109    }
110}
111
112impl From<bool> for DataState {
113    #[inline]
114    fn from(flag: bool) -> DataState {
115        if flag {
116            DataState::Mark
117        }
118        else {
119            DataState::Space
120        }
121    }
122}
123
124impl From<ControlState> for bool {
125    #[inline]
126    fn from(cs: ControlState) -> bool {
127        match cs {
128            ControlState::Active => false,
129            ControlState::Inactive => true,
130        }
131    }
132}
133
134impl From<bool> for ControlState {
135    #[inline]
136    fn from(flag: bool) -> ControlState {
137        if flag {
138            ControlState::Inactive
139        }
140        else {
141            ControlState::Active
142        }
143    }
144}
145
146/// A serial port device that does nothing and provides a constant [ControlState::Inactive] signal
147/// on the `DTR` line and a [DataState::Mark] signal on the `TxD` line.
148#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
149#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
150pub struct NullSerialPort<T>(core::marker::PhantomData<T>);
151
152impl<T> SerialPortDevice for NullSerialPort<T> {
153    type Timestamp = T;
154
155    #[inline(always)]
156    fn write_data(&mut self, _rxd: DataState, _timestamp: Self::Timestamp) -> ControlState {
157        ControlState::Inactive
158    }
159    #[inline(always)]
160    fn poll_ready(&mut self, _timestamp: Self::Timestamp) -> ControlState {
161        ControlState::Inactive
162    }
163    #[inline(always)]
164    fn update_cts(&mut self, _cts: ControlState, _timestamp: Self::Timestamp) {}
165    #[inline(always)]
166    fn read_data(&mut self, _timestamp: Self::Timestamp) -> DataState {
167        DataState::Mark
168    }
169    #[inline(always)]
170    fn next_frame(&mut self, _eof_timestamp: Self::Timestamp) {}
171}