1 //============================================================================
2 //
3 // SSSS tt lll lll
4 // SS SS tt ll ll
5 // SS tttttt eeee ll ll aaaa
6 // SSSS tt ee ee ll ll aa
7 // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8 // SS SS tt ee ll ll aa aa
9 // SSSS ttt eeeee llll llll aaaaa
10 //
11 // Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
12 // and the Stella Team
13 //
14 // See the file "License.txt" for information on usage and redistribution of
15 // this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 //============================================================================
17
18 #include "MediaFactory.hxx"
19 #include "System.hxx"
20 #include "OSystem.hxx"
21 #include "AtariVox.hxx"
22
23 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AtariVox(Jack jack,const Event & event,const System & system,const string & portname,const FilesystemNode & eepromfile,const onMessageCallback & callback)24 AtariVox::AtariVox(Jack jack, const Event& event, const System& system,
25 const string& portname, const FilesystemNode& eepromfile,
26 const onMessageCallback& callback)
27 : SaveKey(jack, event, system, eepromfile, callback, Controller::Type::AtariVox),
28 mySerialPort{MediaFactory::createSerialPort()}
29 {
30 if(mySerialPort->openPort(portname))
31 {
32 myCTSFlip = !mySerialPort->isCTS();
33 if(myCTSFlip)
34 myAboutString = " (serial port \'" + portname + "\', inverted CTS)";
35 else
36 myAboutString = " (serial port \'" + portname + "\')";
37 }
38 else
39 myAboutString = " (invalid serial port \'" + portname + "\')";
40
41 setPin(DigitalPin::Three, true);
42 setPin(DigitalPin::Four, true);
43 }
44
45 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
~AtariVox()46 AtariVox::~AtariVox()
47 {
48 }
49
50 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
read(DigitalPin pin)51 bool AtariVox::read(DigitalPin pin)
52 {
53 // We need to override the Controller::read() method, since the timing
54 // of the actual read is important for the EEPROM (we can't just read
55 // 60 times per second in the ::update() method)
56 switch(pin)
57 {
58 // Pin 2: SpeakJet READY
59 // READY signal is sent directly to pin 2
60 case DigitalPin::Two:
61 {
62 // Some USB-serial adaptors support only CTS, others support only
63 // software flow control
64 // So we check the state of both then AND the results, on the
65 // assumption that if a mode isn't supported, then it reads as TRUE
66 // and doesn't change the boolean result
67 // Thus the logic is:
68 // READY_SIGNAL = READY_STATE_CTS && READY_STATE_FLOW
69 // Note that we also have to take inverted CTS into account
70
71 // When using software flow control, only update on a state change
72 uInt8 flowCtrl = 0;
73 if(mySerialPort->readByte(flowCtrl))
74 myReadyStateSoftFlow = flowCtrl == 0x11; // XON
75
76 // Now combine the results of CTS and'ed with flow control
77 return setPin(pin,
78 (mySerialPort->isCTS() ^ myCTSFlip) && myReadyStateSoftFlow);
79 }
80
81 default:
82 return SaveKey::read(pin);
83 }
84 }
85
86 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
write(DigitalPin pin,bool value)87 void AtariVox::write(DigitalPin pin, bool value)
88 {
89 // Change the pin state based on value
90 switch(pin)
91 {
92 // Pin 1: SpeakJet DATA
93 // output serial data to the speakjet
94 case DigitalPin::One:
95 setPin(pin, value);
96 clockDataIn(value);
97 break;
98
99 default:
100 SaveKey::write(pin, value);
101 }
102 }
103
104 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
clockDataIn(bool value)105 void AtariVox::clockDataIn(bool value)
106 {
107 if(value && (myShiftCount == 0))
108 return;
109
110 // If this is the first write this frame, or if it's been a long time
111 // since the last write, start a new data byte.
112 uInt64 cycle = mySystem.cycles();
113 if((cycle < myLastDataWriteCycle) || (cycle > myLastDataWriteCycle + 1000))
114 {
115 myShiftRegister = 0;
116 myShiftCount = 0;
117 }
118
119 // If this is the first write this frame, or if it's been 62 cycles
120 // since the last write, shift this bit into the current byte.
121 if((cycle < myLastDataWriteCycle) || (cycle >= myLastDataWriteCycle + 62))
122 {
123 myShiftRegister >>= 1;
124 myShiftRegister |= (value << 15);
125 if(++myShiftCount == 10)
126 {
127 myShiftCount = 0;
128 myShiftRegister >>= 6;
129 if(!(myShiftRegister & (1<<9)))
130 cerr << "AtariVox: bad start bit" << endl;
131 else if((myShiftRegister & 1))
132 cerr << "AtariVox: bad stop bit" << endl;
133 else
134 {
135 uInt8 data = ((myShiftRegister >> 1) & 0xff);
136 mySerialPort->writeByte(data);
137 }
138 myShiftRegister = 0;
139 }
140 }
141
142 myLastDataWriteCycle = cycle;
143 }
144
145 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
reset()146 void AtariVox::reset()
147 {
148 myLastDataWriteCycle = 0;
149 SaveKey::reset();
150 }
151