openMSX
MC6850.cc
Go to the documentation of this file.
1#include "MC6850.hh"
2#include "MidiInDevice.hh"
3#include "MSXMotherBoard.hh"
4#include "EmuTime.hh"
5#include "serialize.hh"
6#include <array>
7
8namespace openmsx {
9
10// control register bits
11static constexpr unsigned CR_CDS1 = 0x01; // Counter Divide Select 1
12static constexpr unsigned CR_CDS2 = 0x02; // Counter Divide Select 2
13static constexpr unsigned CR_CDS = CR_CDS1 | CR_CDS2;
14static constexpr unsigned CR_MR = CR_CDS1 | CR_CDS2; // Master Reset
15// CDS2 CDS1
16// 0 0 divide by 1
17// 0 1 divide by 16
18// 1 0 divide by 64
19// 1 1 master reset (!)
20
21static constexpr unsigned CR_WS1 = 0x04; // Word Select 1 (mostly parity)
22static constexpr unsigned CR_WS2 = 0x08; // Word Select 2 (mostly nof stop bits)
23static constexpr unsigned CR_WS3 = 0x10; // Word Select 3: 7/8 bits
24static constexpr unsigned CR_WS = CR_WS1 | CR_WS2 | CR_WS3; // Word Select
25// WS3 WS2 WS1
26// 0 0 0 7 bits - 2 stop bits - Even parity
27// 0 0 1 7 bits - 2 stop bits - Odd parity
28// 0 1 0 7 bits - 1 stop bit - Even parity
29// 0 1 1 7 bits - 1 stop bit - Odd Parity
30// 1 0 0 8 bits - 2 stop bits - No Parity
31// 1 0 1 8 bits - 1 stop bit - No Parity
32// 1 1 0 8 bits - 1 stop bit - Even parity
33// 1 1 1 8 bits - 1 stop bit - Odd parity
34
35static constexpr unsigned CR_TC1 = 0x20; // Transmit Control 1
36static constexpr unsigned CR_TC2 = 0x40; // Transmit Control 2
37static constexpr unsigned CR_TC = CR_TC1 | CR_TC2; // Transmit Control
38// TC2 TC1
39// 0 0 /RTS low, Transmitting Interrupt disabled
40// 0 1 /RTS low, Transmitting Interrupt enabled
41// 1 0 /RTS high, Transmitting Interrupt disabled
42// 1 1 /RTS low, Transmits a Break level on the Transmit Data Output.
43// Interrupt disabled
44
45static constexpr unsigned CR_RIE = 0x80; // Receive Interrupt Enable: interrupt
46// at Receive Data Register Full, Overrun, low-to-high transition on the Data
47// Carrier Detect (/DCD) signal line
48
49// status register bits
50static constexpr unsigned STAT_RDRF = 0x01; // Receive Data Register Full
51static constexpr unsigned STAT_TDRE = 0x02; // Transmit Data Register Empty
52static constexpr unsigned STAT_DCD = 0x04; // Data Carrier Detect (/DCD)
53static constexpr unsigned STAT_CTS = 0x08; // Clear-to-Send (/CTS)
54static constexpr unsigned STAT_FE = 0x10; // Framing Error
55static constexpr unsigned STAT_OVRN = 0x20; // Receiver Overrun
56static constexpr unsigned STAT_PE = 0x40; // Parity Error
57static constexpr unsigned STAT_IRQ = 0x80; // Interrupt Request (/IRQ)
58
59MC6850::MC6850(const std::string& name_, MSXMotherBoard& motherBoard, unsigned clockFreq_)
60 : MidiInConnector(motherBoard.getPluggingController(), name_ + "-in")
61 , syncRecv (motherBoard.getScheduler())
62 , syncTrans(motherBoard.getScheduler())
63 , clockFreq(clockFreq_)
64 , rxIRQ(motherBoard, name_ + "-rx-IRQ")
65 , txIRQ(motherBoard, name_ + "-tx-IRQ")
66 , outConnector(motherBoard.getPluggingController(), name_ + "-out")
67{
68 reset(EmuTime::zero());
69 setDataFormat();
70}
71
72// (Re-)initialize chip to default values (Tx and Rx disabled)
73void MC6850::reset(EmuTime::param time)
74{
75 syncRecv .removeSyncPoint();
76 syncTrans.removeSyncPoint();
77 txClock.reset(time);
78 txClock.setFreq(clockFreq);
79 rxIRQ.reset();
80 txIRQ.reset();
81 rxReady = false;
82 txShiftRegValid = false;
83 pendingOVRN = false;
84 controlReg = CR_MR;
85 statusReg = 0;
86 rxDataReg = 0;
87 setDataFormat();
88}
89
91{
92 return peekStatusReg();
93}
94
96{
97 byte result = statusReg;
98 if (rxIRQ.getState() || txIRQ.getState()) result |= STAT_IRQ;
99 return result;
100}
101
103{
104 byte result = peekDataReg();
105 statusReg &= byte(~(STAT_RDRF | STAT_OVRN));
106 if (pendingOVRN) {
107 pendingOVRN = false;
108 statusReg |= STAT_OVRN;
109 }
110 rxIRQ.reset();
111 return result;
112}
113
115{
116 return rxDataReg;
117}
118
119void MC6850::writeControlReg(byte value, EmuTime::param time)
120{
121 byte diff = value ^ controlReg;
122 if (diff & CR_CDS) {
123 if ((value & CR_CDS) == CR_MR) {
124 reset(time);
125 } else {
126 // we got out of MR state
127 rxReady = true;
128 statusReg |= STAT_TDRE;
129
130 txClock.reset(time);
131 switch (value & CR_CDS) {
132 case 0: txClock.setFreq(clockFreq, 1); break;
133 case 1: txClock.setFreq(clockFreq, 16); break;
134 case 2: txClock.setFreq(clockFreq, 64); break;
135 }
136 }
137 }
138
139 controlReg = value;
140 if (diff & CR_WS) setDataFormat();
141
142 // update IRQ status
143 rxIRQ.set(( value & CR_RIE) && (statusReg & STAT_RDRF));
144 txIRQ.set(((value & CR_TC) == 0x20) && (statusReg & STAT_TDRE));
145}
146
147// Sync data-format related parameters with the current value of controlReg
148void MC6850::setDataFormat()
149{
150 outConnector.setDataBits(controlReg & CR_WS3 ? DATA_8 : DATA_7);
151
152 static constexpr std::array<StopBits, 8> stopBits = {
155 };
156 outConnector.setStopBits(stopBits[(controlReg & CR_WS) >> 2]);
157
158 outConnector.setParityBit(
159 (controlReg & (CR_WS3 | CR_WS2)) != 0x10, // enable
160 (controlReg & CR_WS1) ? ODD : EVEN);
161
162 // start-bits, data-bits, parity-bits, stop-bits
163 static constexpr std::array<byte, 8> len = {
164 1 + 7 + 1 + 2,
165 1 + 7 + 1 + 2,
166 1 + 7 + 1 + 1,
167 1 + 7 + 1 + 1,
168 1 + 8 + 0 + 2,
169 1 + 8 + 0 + 1,
170 1 + 8 + 1 + 1,
171 1 + 8 + 1 + 1,
172 };
173 charLen = len[(controlReg & CR_WS) >> 2];
174}
175
176void MC6850::writeDataReg(byte value, EmuTime::param time)
177{
178 if ((controlReg & CR_CDS) == CR_MR) return;
179
180 txDataReg = value;
181 statusReg &= byte(~STAT_TDRE);
182 txIRQ.reset();
183
184 if (syncTrans.pendingSyncPoint()) {
185 // We're still sending the previous character, only
186 // buffer this one. Don't accept any further characters
187 } else {
188 // We were not yet sending. Start sending at the next txClock.
189 // Important: till that time TDRE should go low
190 // (MC6850 detection routine in Synthesix depends on this)
191 txClock.advance(time); // clock edge before or at 'time'
192 txClock += 1; // clock edge strictly after 'time'
193 syncTrans.setSyncPoint(txClock.getTime());
194 }
195}
196
197// Triggered between transmitted characters, including before the first and
198// after the last character.
199void MC6850::execTrans(EmuTime::param time)
200{
201 assert(txClock.getTime() == time);
202 assert((controlReg & CR_CDS) != CR_MR);
203
204 if (txShiftRegValid) {
205 txShiftRegValid = false;
206 outConnector.recvByte(txShiftReg, time);
207 }
208
209 if (statusReg & STAT_TDRE) {
210 // No next character to send, we're done.
211 } else {
212 // There already is a next character, start sending that now
213 // and accept a next one.
214 statusReg |= STAT_TDRE;
215 if ((controlReg & CR_TC) == 0x20) txIRQ.set();
216
217 txShiftReg = txDataReg;
218 txShiftRegValid = true;
219
220 txClock += charLen;
221 syncTrans.setSyncPoint(txClock.getTime());
222 }
223}
224
225// MidiInConnector sends a new character.
226void MC6850::recvByte(byte value, EmuTime::param time)
227{
228 assert(acceptsData() && ready());
229
230 if (statusReg & STAT_RDRF) {
231 // So, there is a byte that has to be read by the MSX still!
232 // This happens when the MSX program doesn't
233 // respond fast enough to an earlier received byte.
234 // The STAT_OVRN flag only becomes active once the prior valid
235 // character has been read from the data register.
236 pendingOVRN = true;
237 } else {
238 rxDataReg = value;
239 statusReg |= STAT_RDRF;
240 }
241 // both for OVRN and RDRF an IRQ is raised
242 if (controlReg & CR_RIE) rxIRQ.set();
243
244 // Not ready now, but we will be in a while
245 rxReady = false;
246
247 // The MC6850 has separate TxCLK and RxCLK inputs, but both share a
248 // common divider. This implementation hard-codes an input frequency
249 // for both. Below we want the receive clock period, but it's OK
250 // to calculate that as 'txClock.getPeriod()'.
251 syncRecv.setSyncPoint(time + txClock.getPeriod() * charLen);
252}
253
254// Triggered when we're ready to receive the next character.
255void MC6850::execRecv(EmuTime::param time)
256{
257 assert(acceptsData());
258 assert(!rxReady);
259 rxReady = true;
260 getPluggedMidiInDev().signal(time); // trigger (possible) send of next char
261}
262
263// MidiInDevice queries whether it can send a new character 'now'.
264bool MC6850::ready()
265{
266 return rxReady;
267}
268
269// MidiInDevice queries whether it can send characters at all.
270bool MC6850::acceptsData()
271{
272 return (controlReg & CR_CDS) != CR_MR;
273}
274
275// MidiInDevice informs us about the format of the data it will send
276// (MIDI is always 1 start-bit, 8 data-bits, 1 stop-bit, no parity-bits).
277void MC6850::setDataBits(DataBits /*bits*/)
278{
279 // ignore
280}
281void MC6850::setStopBits(StopBits /*bits*/)
282{
283 // ignore
284}
285void MC6850::setParityBit(bool /*enable*/, ParityBit /*parity*/)
286{
287 // ignore
288}
289
290// version 1: initial version
291// version 2: added control
292// version 3: actually working MC6850 with many more member variables
293template<typename Archive>
294void MC6850::serialize(Archive& ar, unsigned version)
295{
296 if (ar.versionAtLeast(version, 3)) {
297 ar.template serializeBase<MidiInConnector>(*this);
298 ar.serialize("outConnector", outConnector,
299
300 "syncRecv", syncRecv,
301 "syncTrans", syncTrans,
302
303 "txClock", txClock,
304 "rxIRQ", rxIRQ,
305 "txIRQ", txIRQ,
306
307 "rxReady", rxReady,
308 "txShiftRegValid", txShiftRegValid,
309 "pendingOVRN", pendingOVRN,
310
311 "rxDataReg", rxDataReg,
312 "txDataReg", txDataReg,
313 "txShiftReg", txShiftReg,
314 "controlReg", controlReg,
315 "statusReg", statusReg);
316 } else if (ar.versionAtLeast(version, 2)) {
317 ar.serialize("control", controlReg);
318 } else {
319 controlReg = 3;
320 }
321
322 if constexpr (Archive::IS_LOADER) {
323 setDataFormat();
324 }
325}
327
328} // namespace openmsx
EmuDuration getPeriod() const
Returns the length of one clock-cycle.
void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
void advance(EmuTime::param e)
Advance this clock in time until the last tick which is not past the given time.
void setFreq(unsigned freq)
Change the frequency at which this clock ticks.
EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
void set()
Set the interrupt request on the bus.
Definition IRQHelper.hh:76
void reset()
Reset the interrupt request on the bus.
Definition IRQHelper.hh:85
bool getState() const
Get the interrupt state.
Definition IRQHelper.hh:105
byte readDataReg()
Definition MC6850.cc:102
byte peekStatusReg() const
Definition MC6850.cc:95
void reset(EmuTime::param time)
Definition MC6850.cc:73
void writeControlReg(byte value, EmuTime::param time)
Definition MC6850.cc:119
byte readStatusReg() const
Definition MC6850.cc:90
void writeDataReg(byte value, EmuTime::param time)
Definition MC6850.cc:176
MC6850(const std::string &name, MSXMotherBoard &motherBoard, unsigned clockFreq)
Definition MC6850.cc:59
byte peekDataReg() const
Definition MC6850.cc:114
void serialize(Archive &ar, unsigned version)
Definition MC6850.cc:294
MidiInDevice & getPluggedMidiInDev() const
virtual void signal(EmuTime::param time)=0
void setStopBits(StopBits bits) override
void setDataBits(DataBits bits) override
void recvByte(byte value, EmuTime::param time) override
void setParityBit(bool enable, ParityBit parity) override
This file implemented 3 utility functions:
Definition Autofire.cc:11
uint8_t byte
8 bit unsigned integer
Definition openmsx.hh:26
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)