Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 6 of 6

Thread: David's MOS6581 SID network player (Teensy 3.2)

  1. #1
    Junior Member
    Join Date
    Jul 2013
    Posts
    19

    David's MOS6581 SID network player (Teensy 3.2)

    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 by Dave47; 03-11-2017 at 04:05 AM. Reason: Teensy speed is actually 96MHz...

  2. #2
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,679
    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.

  3. #3
    Junior Member
    Join Date
    Jul 2013
    Posts
    19
    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

  4. #4
    Junior Member
    Join Date
    Jul 2013
    Posts
    19
    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

  5. #5
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,679
    Thanks for the followup! I'm just now (finally) getting back to looking into this and many other reported issues.

  6. #6
    Junior Member
    Join Date
    Jul 2013
    Posts
    19
    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:

    Click image for larger version. 

Name:	Sid-Front.jpg 
Views:	78 
Size:	213.6 KB 
ID:	11281

    And the rat's nest on the back:

    Click image for larger version. 

Name:	Sid_Back.jpg 
Views:	35 
Size:	229.0 KB 
ID:	11282

    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

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •