Jump to content

Open Club  ·  87 members

AtariVox
IGNORED

Flow control in Stella?


SpiceWare

Recommended Posts

This past week I rediscovered my AtariVox USB Interface:

 

IMG_1101.thumb.jpg.05a6398506ca9557542706ab11e84f4a.jpg

 

 

So I decided to figure out how to get my AtariVox to work with Stella. With @Andrew Davie's post I was able to get it working.

 

While running the vox_test program (from atarivox-programmers-guide.zip in this post) I triggered a bunch of phrases and encountered garbled speech.  Tried it on my 2600 and was not able to recreate it.

 

So I took a look at the code and saw it does a full-buffer check to decide if it can send the next byte of data to the AtariVox or not.  I modified vox_test to turn the screen red when this occurs, then copy/pasted the speech data so it would say a phrase multiple times in order to force a full buffer.  You can see that in action in this video, the delay gets triggered and the speech is just fine:

 

IMG_1100.m4v.zip

 

However, when run in Stella the screen never turns red and the speech gets garbled.

 

I took a look at Stella's source and located the Mac's serial port routines. After reading it I didn't see where Stella would pass back the flow control to the program, so suspect Stella does not emulate the AtariVox's flow control but rather let's the OS deal with buffering the data - which is fine, but is not occurring.   One thing looked odd though:

bool SerialPortMACOS::openPort(const string& device)
{
  myHandle = open(device.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
  if(myHandle <= 0)
    return false;

  struct termios termios;
  tcgetattr(myHandle, &termios);
  memset(&termios, 0, sizeof(struct termios));
  cfmakeraw(&termios);
  cfsetspeed(&termios, 19200);       // change to 19200 baud
  termios.c_cflag = CREAD | CLOCAL;  // turn on READ and ignore modem control lines
  termios.c_cflag |= CS8;            // 8 bit
  termios.c_cflag |= CDTR_IFLOW;     // inbound DTR
  tcsetattr(myHandle, TCSANOW, &termios);

  return true;
}

 

CDTR_IFLOW is for inbound flow control, I think it should be doing outbound flow control.  I took a look at termios.h and found these defines:

 

#if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
#define CCTS_OFLOW	0x00010000	/* CTS flow control of output */
#define CRTSCTS		(CCTS_OFLOW | CRTS_IFLOW)
#define CRTS_IFLOW	0x00020000	/* RTS flow control of input */
#define	CDTR_IFLOW	0x00040000	/* DTR flow control of input */
#define CDSR_OFLOW	0x00080000	/* DSR flow control of output */
#define	CCAR_OFLOW	0x00100000	/* DCD flow control of output */

 

and tried changing CDTR_IFLOW to each of CCTS_OFLOW, CDSR_OFLOW, and CCAR_OFLOW, but still encountered garbled speech.

 

So at this point I'm wondering if the AtariVox USB Interface doesn't handle flow control, or if the problem is something else. Here's my modified version of vox_test - can y'all test it on your computers and let me know:

 

  • computer OS
  • what adapter was used to connect the AtariVox to your computer
  • if speech becomes garbled
  • if screen turns red

 

full_buffer_test.zip

 

Thanks!

Link to comment
Share on other sites

There's a potentially related issue in https://github.com/stella-emu/stella/issues/675.  I've noticed as well that in "Man Goes Down", the text is sometimes cut off.

 

The AtariVox emulation hasn't been well tested, TBH, since not that many people seemed to be using it.  As well, the serial port code is something I grabbed from somewhere online years ago, and I guess it isn't complete.  I would appreciate more testing from anyone that wants to give it a shot, particularly if you're also able to fix it :)

Link to comment
Share on other sites

This issue from Github I mentioned above is now fixed; it was simply user error in not specifying to use the AVox for the right controller.

 

However, as mentioned in that issue, there are magic numbers in the timing code in Stella for this, and I wonder if that's interfering with the red screen.  Now that we see a definitive difference between emulation and real hardware, it should make figuring it out much easier.  I will update when I create a new issue specific to this.

Link to comment
Share on other sites

Looking at SerialPortMACOS I only saw 2 public functions:

  • openPort()
  • writeByte()

I would expect there to be another, along the lines of dtrStatus(), that would be used to pass the AtariVox's buffer-full flag back to the 6507 code.

 

Per the code in speakjet.inc the DTR status should be sent via bit 1 of SWCHA:

SERIAL_RDYMASK  equ     $02

        mac     SPKOUT

        ; check buffer-full status
        lda     SWCHA
        and     #SERIAL_RDYMASK       
        beq     .speech_done

.ok_to_send      
        ; get next speech byte
        ldy     #$00
        lda     (speech_addr),y

...

 

 

Link to comment
Share on other sites

I'm not sure if this is related (and I thought I posted about this somewhere already, but I can't remember now), but in Stella (and Speakalator) the longer phrases I developed for Gorf Arcade get cut off during playback, but on real hardware, they play just fine.

 

This is a recording off of an AtariVox plugged into a real 2600:

AtariVox-2600-Gorf.wav

 

The same binary, running from Stella (using the same type of USB adapter Darrell pictured above):

 AtariVox-Stella-Gorf.wav

 

It cuts it off about the same place every time, but not always the same way - it garbles the audio differently.

Link to comment
Share on other sites

27 minutes ago, Richard H. said:

This is the schematic for the interface. AVox buffer full is connected to serial Clear To Send (CTS) pin.

 

Thanks!

 

I tried setting CCTS_OFLOW but still encounter the garbled output.  Maybe the OS doesn't handle flow control like I thought, and it's up to the program to monitor CTS.

 

I found this cross-platform RS232 library, shows checking CTS is done like this:

 

bool serialib::isCTS()
{
#if defined (_WIN32) || defined(_WIN64)
    DWORD modemStat;
    GetCommModemStatus(hSerial, &modemStat);
    return modemStat & MS_CTS_ON;
#endif
#ifdef __linux__
    int status=0;
    //Get the current status of the CTS bit
    ioctl(fd, TIOCMGET, &status);
    return status & TIOCM_CTS;
#endif
}

 

I think Mac uses the same routine as Linux. As a quick test I modified writeByte() to show the status:

bool SerialPortMACOS::writeByte(uInt8 data)
{
  int status = 0;
  ioctl(myHandle, TIOCMGET, &status);
  
  cout << "TIOCM_CTS: " << (status & TIOCM_CTS) << "\n";
  
  if(myHandle)
  {
//    cerr << "SerialPortMACOS::writeByte " << int(data) << endl;
    return write(myHandle, &data, 1) == 1;
  }
  return false;
}

 

I then ran the buffer full test - console output was all 0, so something else must be needed:

TIOCM_CTS: 0
TIOCM_CTS: 0

...

TIOCM_CTS: 0
TIOCM_CTS: 0

 

Link to comment
Share on other sites

What a difference having an actual circuit diagram makes :)

 

In Stella, the AtariVox class inherits from the SaveKey class.  So it delegates SaveKey-related stuff to the parent class.  Here's the relevant code.

 

SaveKey, clearly showing that pins 3 and 4 are being used (just as in the circuit):

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool SaveKey::read(DigitalPin pin)
{
  // We need to override the Controller::read() method, since the timing
  // of the actual read is important for the EEPROM (we can't just read
  // 60 times per second in the ::update() method)
  switch(pin)
  {
    // Pin 3: EEPROM SDA
    //        input data from the 24LC256 EEPROM using the I2C protocol
    case DigitalPin::Three:
      return setPin(pin, myEEPROM->readSDA());

    default:
      return Controller::read(pin);
  }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SaveKey::write(DigitalPin pin, bool value)
{
  // Change the pin state based on value
  switch(pin)
  {
    // Pin 3: EEPROM SDA
    //        output data to the 24LC256 EEPROM using the I2C protocol
    case DigitalPin::Three:
      setPin(pin, value);
      myEEPROM->writeSDA(value);
      break;

    // Pin 4: EEPROM SCL
    //        output clock data to the 24LC256 EEPROM using the I2C protocol
    case DigitalPin::Four:
      setPin(pin, value);
      myEEPROM->writeSCL(value);
      break;

    default:
      break;
  }
}

Now AtariVox:

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool AtariVox::read(DigitalPin pin)
{
  // We need to override the Controller::read() method, since the timing
  // of the actual read is important for the EEPROM (we can't just read
  // 60 times per second in the ::update() method)
  switch(pin)
  {
    // Pin 2: SpeakJet READY
    case DigitalPin::Two:
      // For now, we just assume the device is always ready
      return setPin(pin, true);

    default:
      return SaveKey::read(pin);
  }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AtariVox::write(DigitalPin pin, bool value)
{
  // Change the pin state based on value
  switch(pin)
  {
    // Pin 1: SpeakJet DATA
    //        output serial data to the speakjet
    case DigitalPin::One:
      setPin(pin, value);
      clockDataIn(value);
      break;

    default:
      SaveKey::write(pin, value);
  }
}

Pin 1 is clearly connected as in the diagram, and is sending data.  Pin 2 is the CTS signal, but is always sending true, meaning the device is always ready.  Obviously this needs to be changed to indicate the actual READY state.

Link to comment
Share on other sites

I set the FTDI_RL CTS to inverted (firmware is flashable through the USB).

 

Also, CBUS0 and CBUS1 (connected to the AVox's EEPROM) are set to I/O mode. I was hoping someone would one day code an EEPROM util.

Edited by Richard H.
Link to comment
Share on other sites

I remember the USB interface failing on really long phrases in Phrasealator. But a conventional serial port interface (powered by an external PSU) worked fine.

 

AAMOI I've always found USB-serial interfaces not to be as reliable as actual serial ports.

Link to comment
Share on other sites

I've had some success with the code that @SpiceWare posted above.  Note that it doesn't go in writeByte(), but in a new method which I named isCTS().  So I now have the ROM above working in Linux!  It correctly shows red at overflow of buffer, and also no longer cuts out the speech.

 

So now I need to test on Windows and Mac.  The Windows code probably works fine, but I still need to search for the equivalent Mac code.

  • Like 1
Link to comment
Share on other sites

On 7/27/2020 at 3:42 PM, Nathan Strum said:

I'm not sure if this is related (and I thought I posted about this somewhere already, but I can't remember now), but in Stella (and Speakalator) the longer phrases I developed for Gorf Arcade get cut off during playback, but on real hardware, they play just fine.

@Nathan Strum, can you point me to that thread, or provide the test ROMs here?  I want to test whether the new code fixes the issues for you.

Link to comment
Share on other sites

38 minutes ago, stephena said:

@Nathan Strum, can you point me to that thread, or provide the test ROMs here?  I want to test whether the new code fixes the issues for you.

I'll ask John if it's okay to post the ROM. I still can't remember where I posted about it. Could've just been a PM.

Link to comment
Share on other sites

57 minutes ago, stephena said:

I've had some success with the code that @SpiceWare posted above.  Note that it doesn't go in writeByte(), but in a new method which I named isCTS().  So I now have the ROM above working in Linux!  It correctly shows red at overflow of buffer, and also no longer cuts out the speech.

 

Awesome!  I only put it in writeByte() as a quick test.

Link to comment
Share on other sites

22 minutes ago, stephena said:

It doesn't even have to be an entire ROM.  Just one that illustrates the issue.  Although that's probably more work than just sending the original ROM.  You/he can also do it by PM if you prefer.

PM sent. It'd probably be okay to post the ROM - it's just the voices. But I didn't want to do so without permission, since at some point we want to do a reveal of the game, and nobody's heard them yet (well, almost nobody). :) 

Link to comment
Share on other sites

OK, some problems.  I have two AtariVox's and 2 USB adaptors.  It seems the USB adaptors are working differently.  One is active high when the buffer is full, and one is active low ?  I did all my testing on Linux initially with the device I now assuming is working incorrectly.  This is consistent across Linux, macOS and Windows.  I've just committed code that assumes the following.  Please have a look at it, to see if I'm wrong.  Because at this point I'm not sure if my code is wrong and adaptor A is working, or my code is right and adaptor B is working ?

// In SerialPort class:
	/**
      Test for 'Clear To Send' enabled.  By default, assume it's always
      OK to send more data.

      @return  True if CTS signal enabled, else false
    */
    virtual bool isCTS() { return true; }

// In AtariVox class:
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool AtariVox::read(DigitalPin pin)
{
  // We need to override the Controller::read() method, since the timing
  // of the actual read is important for the EEPROM (we can't just read
  // 60 times per second in the ::update() method)
  switch(pin)
  {
    // Pin 2: SpeakJet READY
    //        CTS (Clear To Send) is connected inverted
    //        So CTS = 0 means the buffer is full, which pulls pin 2 high
    case DigitalPin::Two:
    {
      return setPin(pin, !mySerialPort->isCTS());
    }

    default:
      return SaveKey::read(pin);
  }
}

In particular, is this comment valid??

    // Pin 2: SpeakJet READY
    //        CTS (Clear To Send) is connected inverted
    //        So CTS = 0 means the buffer is full, which pulls pin 2 high

The currently committed code assumes this is how it works.  And that code works with one of the USB adaptors.  If I remove the invert in the above, then the other USB adaptor starts working ☺️  Now, the problem is, which version of the adaptors do users have?  I can't tell which is which, but one was bought from the initial batch BITD, and the other is from a more recent run, from a different source.  Oh what a mess, since we can't tell which the user will have.  I'm about to take both apart, and see if there's any visible difference.  To me, one obviously has the CTS line inverted and the other doesn't.  And according to Richard H, the former is the correct one.

Link to comment
Share on other sites

2 minutes ago, Thomas Jentzsch said:

Couldn't we autodetect which is correct?

First we need to definitely determine if my code is correct.  Then I can use it as a baseline for determining the correct adaptor.  Then we can try to figure out a way to detect it in software.

Link to comment
Share on other sites

No luck for me.  As written I get garbled speech.  If I change this:

 

bool AtariVox::read(DigitalPin pin)
{
  // We need to override the Controller::read() method, since the timing
  // of the actual read is important for the EEPROM (we can't just read
  // 60 times per second in the ::update() method)
  switch(pin)
  {
    // Pin 2: SpeakJet READY
    //        CTS (Clear To Send) is connected inverted
    //        So CTS = 0 means the buffer is full, which pulls pin 2 high
    case DigitalPin::Two:
    {
//      return setPin(pin, !mySerialPort->isCTS());
      return setPin(pin, mySerialPort->isCTS());
    }

    default:
      return SaveKey::read(pin);
  }
}

 

Then I get the red screen right away, and no speech.

 

1500897932_ScreenShot2020-07-30at3_43_00PM.thumb.png.a9a632ed7694a9f751871b3d2d42b5d1.png

 

I have another AtariVox somewhere that I'd picked up for my Vectrex, will see if I can't track that down and see if the results are different.

 

I also found the other AtariVox interface that plugs in a serial port and needs power.  Will need to find a USB to serial adapter for it though as I no longer have a computer with a traditional serial port.

Link to comment
Share on other sites

Yes, but what I mean is that if CTS is on, then pin 1 of SWCHA is low (ie, can still receive data).  And when CTS is off, then pin 1 of SWCHA is high (ie, buffer is full).  That's my understanding based on the circuit and the 'invert' as explained by Richard above.  And it's how the current code works.  If so, then we know for sure which one of the adaptors is faulty.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...