David's MOS6581 SID network player (Teensy 3.2)

Status
Not open for further replies.

Dave47

Member
Hi!

Longtime lurker here. I have recently been able to get a MOS6581 SID to run as a network player using ACID64 and a program I wrote in LabView to actually work. It works about 95% at the moment. By that, I can play every SID I've tried. I can even play the heavily digitized ones at pretty much regular speed. The other 5% is in the stuff that feels like a hack in my program. I currently use a PWM channel (FTM0) for the ~1MHz SID clock and the IntervalTimer for the delays between writes. The problem is that these two timers don't really synchronize together with the settings I have used. I can see on my oscilloscope that the CLK walks away from the chip select and I have to reset the FTM0 counter right before I write the data so I can place the ~400ns write pulse around the falling edge of the CLK.

So, my question is... Is there a better way to generate a synchronized interval timer and a clock? The CLK doesn't have to be exactly 1MHz since the actual PAL clock is a little slower and the NTSC clock is a little faster. Here is my program...

Teensy 3.2 @ 96MHz

Code:
#define RESET 17
#define RW 16
#define CLK 4
#define CS1 3
#define NOP __asm__("nop\n\t")

volatile unsigned int nexttime;
volatile unsigned int timetonext;
volatile byte address;
volatile byte data;
volatile byte dataavailable;
volatile byte serdatlow;
volatile byte serdathigh;
volatile byte serdataddress;
volatile byte serdatdata;
volatile byte testdata;
volatile int cycles_per_us;
byte x = 1;

IntervalTimer myTimer;

void setup() {
  Serial.begin(115200);
  pinMode(RESET, OUTPUT);
  digitalWrite(RESET, HIGH);
  pinMode(RW, OUTPUT);
  digitalWrite(RW, HIGH);
  pinMode(CLK, OUTPUT);
  digitalWrite(CLK, LOW);
  pinMode(CS1, OUTPUT);
  digitalWrite(CS1, HIGH);

  pinMode(2, OUTPUT);  //DATA0
  pinMode(14, OUTPUT);  //DATA1
  pinMode(7, OUTPUT);  //DATA2
  pinMode(8, OUTPUT);  //DATA3
  pinMode(6, OUTPUT);  //DATA4
  pinMode(20, OUTPUT);  //DATA5
  pinMode(21, OUTPUT);  //DATA6
  pinMode(5, OUTPUT);  //DATA7

  pinMode(15, OUTPUT);  //ADDRESS0
  pinMode(22, OUTPUT);  //ADDRESS1
  pinMode(23, OUTPUT);  //ADDRESS2
  pinMode(9, OUTPUT);  //ADDRESS3
  pinMode(10, OUTPUT);  //ADDRESS4
 
  analogWriteFrequency(CLK, 1000000);               // set SID clock frequency to 1MHz
  analogWriteResolution(5);                         // PWM resolution
  analogWrite(CLK, 15);                             // PWM dutycycle

  myTimer.begin(DATA, 5);
  myTimer.priority(8);

  cycles_per_us = F_BUS / 1000000;


}

FASTRUN void loop() {                               // get serial data and pass to device
  digitalWriteFast(RESET, LOW);
  delayMicroseconds(20);
  digitalWriteFast(RESET, HIGH);
  while (x == 1) {
    if (Serial.available() >= 4) {
      serdathigh = Serial.read();
      serdatlow = Serial.read();
      timetonext = serdathigh * 256 + serdatlow;
      PIT_LDVAL0 = timetonext * cycles_per_us;     // reset the PIT timer in myTimer to the delay until the next write
      serdataddress = Serial.read();
      serdatdata = Serial.read();
      dataavailable = 1;
      while (dataavailable == 1) {}                // wait for myTimer to fire interrupt
    }
  }
}

FASTRUN void DATA(void)
{
  if (dataavailable >= 1) {
    for (int i = 0; i <= 4; i++) {
      NOP;
    }
    FTM1_CNT = 0;                                 // reset PWM FTM counter so that I can align the data with the falling edge of the CLK
    GPIOC_PDOR = serdataddress;
    digitalWriteFast(RW, LOW);
    digitalWriteFast(CS1, LOW);
    for (int i = 0; i <= 3; i++) {
      NOP;
    }
    GPIOD_PDOR = serdatdata;
    for (int i = 0; i <= 3; i++) {
      NOP;
    }
    dataavailable = 0;
    digitalWriteFast(RW, HIGH);
    digitalWriteFast(CS1, HIGH);
  }
}

Thanks in advance for any advice on how tor make it better.

David
 
Last edited:
Hmm, maybe there's a bug in IntervalTimer or analogWriteFrequency() at play here? Their relative phase will depend on their start times, and the IntervalTimer response can have variable latency leading to jitter, but since they do derive from the same F_BUS clock, both should stay at the same phase relationship.

Which version of Arduino & Teensyduino are you using? Use Help > About (or Arduino > About on mac) to check.
 
Paul,

Thanks for looking at this. I just updated to Arduino 1.8.1 and Teensyduino 1.35.

I'm not sure if how familiar you are with these SID network players. You can see from the code that ACID64 sends 4 bytes. All my LabView code does is stream these bytes from the localhost port that ACID64 uses to the serial port with as little overhead as possible. The first two bytes are the 16 bit number that is the delay before the write and the next two are the address and data. The delay can vary. I've seen as little as 5us between pulses and as much as 20,000us for the long delays. The short delays look like they stay well enough in time. It's the long delays that look like they have drifted. They don't appear to drift much if I keep issuing the FTM1_CNT = 0 command, but if I don't, the clock is always just sliding by the CS pulse.

Thanks again!
David
 
Paul,

I figured it out. Based on your reply here, I realized that I need to subtract 1 from this line so the counter can end on -1 instead of 0.

Code:
      PIT_LDVAL0 = timetonext * cycles_per_us - 1;     // reset the PIT timer in myTimer to the delay until the next write

I can take some stuff out of the program and the rest becomes:

Code:
#define RESET 17
#define RW 16
#define CLK 4
#define CS1 3
#define NOP __asm__("nop\n\t")

volatile unsigned int nexttime;
volatile unsigned int timetonext;
volatile byte address;
volatile byte data;
volatile byte dataavailable;
volatile byte serdatlow;
volatile byte serdathigh;
volatile byte serdataddress;
volatile byte serdatdata;
volatile byte testdata;
volatile int cycles_per_us;
byte x = 1;

IntervalTimer myTimer;

void setup() {
  Serial.begin(115200);
  pinMode(RESET, OUTPUT);
  digitalWrite(RESET, HIGH);
  pinMode(RW, OUTPUT);
  digitalWrite(RW, HIGH);
  pinMode(CLK, OUTPUT);
  digitalWrite(CLK, HIGH);
  pinMode(CS1, OUTPUT);
  digitalWrite(CS1, HIGH);

  pinMode(2, OUTPUT);  //DATA0
  pinMode(14, OUTPUT);  //DATA1
  pinMode(7, OUTPUT);  //DATA2
  pinMode(8, OUTPUT);  //DATA3
  pinMode(6, OUTPUT);  //DATA4
  pinMode(20, OUTPUT);  //DATA5
  pinMode(21, OUTPUT);  //DATA6
  pinMode(5, OUTPUT);  //DATA7

  pinMode(15, OUTPUT);  //ADDRESS0
  pinMode(22, OUTPUT);  //ADDRESS1
  pinMode(23, OUTPUT);  //ADDRESS2
  pinMode(9, OUTPUT);  //ADDRESS3
  pinMode(10, OUTPUT);  //ADDRESS4
  pinMode(13, OUTPUT);  //SID 2 Enable

  analogWriteFrequency(CLK, 1000000);               // set SID clock frequency to 1MHz
  analogWriteResolution(5);                         // PWM resolution
  analogWrite(CLK, 15);                             // PWM dutycycle

  myTimer.begin(DATA, 5);
  myTimer.priority(16);
  cycles_per_us = F_BUS / 1000000;


}

FASTRUN void loop() {                               // get serial data and pass to device
  digitalWriteFast(RESET, LOW);                     // Reset SID
  delayMicroseconds(20);                            // Reset SID
  digitalWriteFast(RESET, HIGH);                    // Reset SID

  PIT_LDVAL0 = 75;                                  // shift phase of myTimer about 1.25ms wrt PWM CLK
  
  while (x == 1) {
    if (Serial.available() >= 4) {
      serdathigh = Serial.read();
      serdatlow = Serial.read();
      timetonext = serdathigh * 256 + serdatlow + 1;
      PIT_LDVAL0 = timetonext * cycles_per_us - 1;     // reset the PIT timer in myTimer to the delay until the next write
      serdataddress = Serial.read();
      serdatdata = Serial.read();
      dataavailable = 1;
      while (dataavailable == 1) {}                // wait for myTimer to fire interrupt
    }
  }
}

FASTRUN void DATA(void)
{
  if (dataavailable >= 1) {
    GPIOC_PDOR = serdataddress;
    digitalWriteFast(RW, LOW);
    digitalWriteFast(CS1, LOW);
    for (int i = 0; i <= 3; i++) {
      NOP;
    }
    GPIOD_PDOR = serdatdata;
    for (int i = 0; i <= 4; i++) {
      NOP;
    }
    dataavailable = 0;
    digitalWriteFast(RW, HIGH);
    digitalWriteFast(CS1, HIGH);
  }
}

No bugs in your stuff...

David
 
Just a quick update...

I have reworked the board and I now have STEREO! I added 220ohm resistors to all the high speed outputs (to redue or eliminate ringing) and a 74HC139 to decode the CS and Left/Right bit into a CS signal for each chip. It works pretty well. There are bugs, I think. Some sounds aren't quite like the emulators I listen to. That could be that the Acid64 -> Labview -> serial input is just a bit slow in some cases. It could also be that SIDs from 1983 are really just like that and emulators are too good. I don't know. Anyway, some pictures of the board...

The front of the board:

Sid-Front.jpg

And the rat's nest on the back:

Sid_Back.jpg

I will try and upload some example audio to youtube in the next few days.

I also plan on adding SID test code to the Labview program. All the hard work is done and it just needs a GUI and some thoughts on what to test. That is why I splurged on ZIF sockets for the SIDs. Also, I use Labview all day at work, so I will figure something out.

David
 
Status
Not open for further replies.
Back
Top