summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/rs232/rs232_sync_io.cpp
blob: 8403c41e2cfdbb694d7e6308a2a352bfdcbbabe4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// license:BSD-3-Clause
// copyright-holders: F. Ulivi
/*********************************************************************

    rs232_sync_io.cpp

    Synchronous I/O on RS232 port

    This device provides a bitbanger-based interface to a synchronous
    RS232 port.
    It has the following functions:
    - Provides Tx and/or Rx clock to RS232 port; clocks can be
      individually turned off when not needed (e.g. Tx clock can
      disabled when receiving in half-duplex mode)
    - In the tx direction, it gathers bits from TxD,
      packs them into bytes LSB to MSB, then sends them out to
      bitbanger
    - In the rx direction, it extracts bytes from bitbanger
      and serializes them to RxD, LSB to MSB.
    - Supports both full-duplex and half-duplex modes
    - Generates CTS signal

    There's no kind of synchronization to any higher-level framing:
    bits are just clocked in/out of the port, packed/unpacked into
    bytes and exchanged with bitbanger.
    There's also no automatic stuffing of idle line characters, this
    function is to be implemented externally if needed.

*********************************************************************/

#include "emu.h"
#include "rs232_sync_io.h"

// Debugging

#include "logmacro.h"
//#undef VERBOSE
//#define VERBOSE LOG_GENERAL

// Bit manipulation
namespace {
	template<typename T> constexpr T BIT_MASK(unsigned n)
	{
		return (T)1U << n;
	}

	template<typename T> void BIT_CLR(T& w , unsigned n)
	{
		w &= ~BIT_MASK<T>(n);
	}

	template<typename T> void BIT_SET(T& w , unsigned n)
	{
		w |= BIT_MASK<T>(n);
	}
}

rs232_sync_io_device::rs232_sync_io_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig , RS232_SYNC_IO , tag , owner , clock)
	, device_rs232_port_interface(mconfig , *this)
	, m_stream(*this , "stream")
	, m_rs232_baud(*this , "RS232_BAUD")
	, m_rts_duplex(*this , "rtsduplex")
	, m_txc_setting(*this , "txcontrol")
	, m_rxc_setting(*this , "rxcontrol")
{
}

rs232_sync_io_device:: ~rs232_sync_io_device()
{
}

WRITE_LINE_MEMBER(rs232_sync_io_device::input_txd)
{
	m_txd = state;
	LOG("TxD %d %u\n" , state , m_tx_counter);
}

WRITE_LINE_MEMBER(rs232_sync_io_device::input_rts)
{
	m_rts = state;
}

static INPUT_PORTS_START(rs232_sync_io)
	PORT_RS232_BAUD("RS232_BAUD" , RS232_BAUD_2400 , "Baud rate" , rs232_sync_io_device , update_serial)

	PORT_START("rtsduplex")
	PORT_CONFNAME(1 , 1 , "RTS controls half-duplex")
	PORT_CONFSETTING(0 , DEF_STR(Off))
	PORT_CONFSETTING(1 , DEF_STR(On))

	PORT_START("txcontrol")
	PORT_CONFNAME(1 , 1 , "TxC generated")
	PORT_CONFSETTING(0 , "Always")
	PORT_CONFSETTING(1 , "When Tx is active")

	PORT_START("rxcontrol")
	PORT_CONFNAME(3 , 1 , "RxC generated")
	PORT_CONFSETTING(0 , "Always")
	PORT_CONFSETTING(1 , "When Rx is active")
	PORT_CONFSETTING(2 , "When a byte is available")
INPUT_PORTS_END

ioport_constructor rs232_sync_io_device::device_input_ports() const
{
	return INPUT_PORTS_NAME(rs232_sync_io);
}

void rs232_sync_io_device::device_add_mconfig(machine_config &config)
{
	BITBANGER(config , m_stream , 0);
}

void rs232_sync_io_device::device_start()
{
	m_clk_timer = timer_alloc(FUNC(rs232_sync_io_device::clock_tick), this);
}

void rs232_sync_io_device::device_reset()
{
	output_dcd(0);
	output_dsr(0);
	output_rxd(1);
	output_cts(1);

	update_serial(0);

	m_clk = false;
	m_rx_byte = 0xff;
	m_rx_counter = 0;
	m_tx_counter = 0;
	m_tx_enabled = false;
	m_rx_enabled = false;

	output_txc(1);
	output_rxc(1);
}

TIMER_CALLBACK_MEMBER(rs232_sync_io_device::clock_tick)
{
	m_clk = !m_clk;
	if (m_clk) {
		output_txc(1);
		output_rxc(1);
		if (m_tx_enabled) {
			// Rising edge: capture TxD
			m_tx_byte = (m_tx_byte >> 1) & 0x7f;
			if (m_txd) {
				BIT_SET(m_tx_byte , 7);
			}
			if (++m_tx_counter >= 8) {
				LOG("Tx %02x @%s\n" , m_tx_byte , machine().time().to_string());
				m_tx_counter = 0;
				m_stream->output(m_tx_byte);
				m_tx_enabled = false;
			}
		}
	} else {
		if (!m_tx_enabled) {
			m_tx_enabled = !m_rts || !m_rts_duplex->read();
			output_cts(!m_tx_enabled);
		}
		if (m_tx_enabled || m_txc_setting->read() == 0) {
			output_txc(0);
		}
		if (!m_rx_enabled) {
			m_rx_enabled = m_rts || !m_rts_duplex->read();
			if (m_rx_enabled) {
				if (m_stream->input(&m_rx_byte , 1) == 0) {
					m_rx_byte = ~0;
					if (m_rxc_setting->read() == 2) {
						m_rx_enabled = false;
					}
				} else {
					LOG("Rx %02x @%s\n" , m_rx_byte , machine().time().to_string());
				}
			}
		}
		if (m_rx_enabled || m_rxc_setting->read() == 0) {
			output_rxc(0);
		}
		if (m_rx_enabled) {
			// Falling edge: update RxD
			output_rxd(BIT(m_rx_byte , 0));
			m_rx_byte >>= 1;
			if (++m_rx_counter >= 8) {
				m_rx_counter = 0;
				m_rx_enabled = false;
			}
		}
	}
}

WRITE_LINE_MEMBER(rs232_sync_io_device::update_serial)
{
	auto baud_rate = convert_baud(m_rs232_baud->read());
	auto period = attotime::from_hz(baud_rate * 2);
	m_clk_timer->adjust(period , 0 , period);
}

DEFINE_DEVICE_TYPE(RS232_SYNC_IO, rs232_sync_io_device, "rs232_sync_io", "RS232 Synchronous I/O")