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