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 , txClock(EmuTime::zero())
64 , clockFreq(clockFreq_)
65 , rxIRQ(motherBoard, name_ + "-rx-IRQ")
66 , txIRQ(motherBoard, name_ + "-tx-IRQ")
67 , outConnector(motherBoard.getPluggingController(), name_ + "-out")
68{
69 reset(EmuTime::zero());
70 setDataFormat();
71}
72
73// (Re-)initialize chip to default values (Tx and Rx disabled)
74void MC6850::reset(EmuTime::param time)
75{
76 syncRecv .removeSyncPoint();
77 syncTrans.removeSyncPoint();
78 txClock.reset(time);
79 txClock.setFreq(clockFreq);
80 rxIRQ.reset();
81 txIRQ.reset();
82 rxReady = false;
83 txShiftRegValid = false;
84 pendingOVRN = false;
85 controlReg = CR_MR;
86 statusReg = 0;
87 rxDataReg = 0;
88 setDataFormat();
89}
90
92{
93 return peekStatusReg();
94}
95
97{
98 byte result = statusReg;
99 if (rxIRQ.getState() || txIRQ.getState()) result |= STAT_IRQ;
100 return result;
101}
102
104{
105 byte result = peekDataReg();
106 statusReg &= byte(~(STAT_RDRF | STAT_OVRN));
107 if (pendingOVRN) {
108 pendingOVRN = false;
109 statusReg |= STAT_OVRN;
110 }
111 rxIRQ.reset();
112 return result;
113}
114
116{
117 return rxDataReg;
118}
119
120void MC6850::writeControlReg(byte value, EmuTime::param time)
121{
122 byte diff = value ^ controlReg;
123 if (diff & CR_CDS) {
124 if ((value & CR_CDS) == CR_MR) {
125 reset(time);
126 } else {
127 // we got out of MR state
128 rxReady = true;
129 statusReg |= STAT_TDRE;
130
131 txClock.reset(time);
132 switch (value & CR_CDS) {
133 case 0: txClock.setFreq(clockFreq, 1); break;
134 case 1: txClock.setFreq(clockFreq, 16); break;
135 case 2: txClock.setFreq(clockFreq, 64); break;
136 }
137 }
138 }
139
140 controlReg = value;
141 if (diff & CR_WS) setDataFormat();
142
143 // update IRQ status
144 rxIRQ.set(( value & CR_RIE) && (statusReg & STAT_RDRF));
145 txIRQ.set(((value & CR_TC) == 0x20) && (statusReg & STAT_TDRE));
146}
147
148// Sync data-format related parameters with the current value of controlReg
149void MC6850::setDataFormat()
150{
151 outConnector.setDataBits(controlReg & CR_WS3 ? DATA_8 : DATA_7);
152
153 static constexpr std::array<StopBits, 8> stopBits = {
156 };
157 outConnector.setStopBits(stopBits[(controlReg & CR_WS) >> 2]);
158
159 outConnector.setParityBit(
160 (controlReg & (CR_WS3 | CR_WS2)) != 0x10, // enable
161 (controlReg & CR_WS1) ? ODD : EVEN);
162
163 // start-bits, data-bits, parity-bits, stop-bits
164 static constexpr std::array<byte, 8> len = {
165 1 + 7 + 1 + 2,
166 1 + 7 + 1 + 2,
167 1 + 7 + 1 + 1,
168 1 + 7 + 1 + 1,
169 1 + 8 + 0 + 2,
170 1 + 8 + 0 + 1,
171 1 + 8 + 1 + 1,
172 1 + 8 + 1 + 1,
173 };
174 charLen = len[(controlReg & CR_WS) >> 2];
175}
176
177void MC6850::writeDataReg(byte value, EmuTime::param time)
178{
179 if ((controlReg & CR_CDS) == CR_MR) return;
180
181 txDataReg = value;
182 statusReg &= byte(~STAT_TDRE);
183 txIRQ.reset();
184
185 if (syncTrans.pendingSyncPoint()) {
186 // We're still sending the previous character, only
187 // buffer this one. Don't accept any further characters
188 } else {
189 // We were not yet sending. Start sending at the next txClock.
190 // Important: till that time TDRE should go low
191 // (MC6850 detection routine in Synthesix depends on this)
192 txClock.advance(time); // clock edge before or at 'time'
193 txClock += 1; // clock edge strictly after 'time'
194 syncTrans.setSyncPoint(txClock.getTime());
195 }
196}
197
198// Triggered between transmitted characters, including before the first and
199// after the last character.
200void MC6850::execTrans(EmuTime::param time)
201{
202 assert(txClock.getTime() == time);
203 assert((controlReg & CR_CDS) != CR_MR);
204
205 if (txShiftRegValid) {
206 txShiftRegValid = false;
207 outConnector.recvByte(txShiftReg, time);
208 }
209
210 if (statusReg & STAT_TDRE) {
211 // No next character to send, we're done.
212 } else {
213 // There already is a next character, start sending that now
214 // and accept a next one.
215 statusReg |= STAT_TDRE;
216 if (((controlReg & CR_TC) == 0x20)) txIRQ.set();
217
218 txShiftReg = txDataReg;
219 txShiftRegValid = true;
220
221 txClock += charLen;
222 syncTrans.setSyncPoint(txClock.getTime());
223 }
224}
225
226// MidiInConnector sends a new character.
227void MC6850::recvByte(byte value, EmuTime::param time)
228{
229 assert(acceptsData() && ready());
230
231 if (statusReg & STAT_RDRF) {
232 // So, there is a byte that has to be read by the MSX still!
233 // This happens when the MSX program doesn't
234 // respond fast enough to an earlier received byte.
235 // The STAT_OVRN flag only becomes active once the prior valid
236 // character has been read from the data register.
237 pendingOVRN = true;
238 } else {
239 rxDataReg = value;
240 statusReg |= STAT_RDRF;
241 }
242 // both for OVRN and RDRF an IRQ is raised
243 if (controlReg & CR_RIE) rxIRQ.set();
244
245 // Not ready now, but we will be in a while
246 rxReady = false;
247
248 // The MC6850 has separate TxCLK and RxCLK inputs, but both share a
249 // common divider. This implementation hard-codes an input frequency
250 // for both. Below we want the receive clock period, but it's OK
251 // to calculate that as 'txClock.getPeriod()'.
252 syncRecv.setSyncPoint(time + txClock.getPeriod() * charLen);
253}
254
255// Triggered when we're ready to receive the next character.
256void MC6850::execRecv(EmuTime::param time)
257{
258 assert(acceptsData());
259 assert(!rxReady);
260 rxReady = true;
261 getPluggedMidiInDev().signal(time); // trigger (possible) send of next char
262}
263
264// MidiInDevice queries whether it can send a new character 'now'.
265bool MC6850::ready()
266{
267 return rxReady;
268}
269
270// MidiInDevice queries whether it can send characters at all.
271bool MC6850::acceptsData()
272{
273 return (controlReg & CR_CDS) != CR_MR;
274}
275
276// MidiInDevice informs us about the format of the data it will send
277// (MIDI is always 1 start-bit, 8 data-bits, 1 stop-bit, no parity-bits).
278void MC6850::setDataBits(DataBits /*bits*/)
279{
280 // ignore
281}
282void MC6850::setStopBits(StopBits /*bits*/)
283{
284 // ignore
285}
286void MC6850::setParityBit(bool /*enable*/, ParityBit /*parity*/)
287{
288 // ignore
289}
290
291// version 1: initial version
292// version 2: added control
293// version 3: actually working MC6850 with many more member variables
294template<typename Archive>
295void MC6850::serialize(Archive& ar, unsigned version)
296{
297 if (ar.versionAtLeast(version, 3)) {
298 ar.template serializeBase<MidiInConnector>(*this);
299 ar.serialize("outConnector", outConnector,
300
301 "syncRecv", syncRecv,
302 "syncTrans", syncTrans,
303
304 "txClock", txClock,
305 "rxIRQ", rxIRQ,
306 "txIRQ", txIRQ,
307
308 "rxReady", rxReady,
309 "txShiftRegValid", txShiftRegValid,
310 "pendingOVRN", pendingOVRN,
311
312 "rxDataReg", rxDataReg,
313 "txDataReg", txDataReg,
314 "txShiftReg", txShiftReg,
315 "controlReg", controlReg,
316 "statusReg", statusReg);
317 } else if (ar.versionAtLeast(version, 2)) {
318 ar.serialize("control", controlReg);
319 } else {
320 controlReg = 3;
321 }
322
323 if constexpr (Archive::IS_LOADER) {
324 setDataFormat();
325 }
326}
328
329} // 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:74
void reset()
Reset the interrupt request on the bus.
Definition IRQHelper.hh:83
bool getState() const
Get the interrupt state.
Definition IRQHelper.hh:103
byte readDataReg()
Definition MC6850.cc:103
byte peekStatusReg() const
Definition MC6850.cc:96
void reset(EmuTime::param time)
Definition MC6850.cc:74
void writeControlReg(byte value, EmuTime::param time)
Definition MC6850.cc:120
byte readStatusReg() const
Definition MC6850.cc:91
void writeDataReg(byte value, EmuTime::param time)
Definition MC6850.cc:177
MC6850(const std::string &name, MSXMotherBoard &motherBoard, unsigned clockFreq)
Definition MC6850.cc:59
byte peekDataReg() const
Definition MC6850.cc:115
void serialize(Archive &ar, unsigned version)
Definition MC6850.cc:295
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:9
uint8_t byte
8 bit unsigned integer
Definition openmsx.hh:26
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)