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