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

Thread: Realtime S/PDIF decoding on Teensy 4.0

  1. #1
    Junior Member
    Join Date
    Apr 2021
    Posts
    8

    Realtime S/PDIF decoding on Teensy 4.0

    Hello,

    I work in a neuroscience lab studying the auditory system, and we use TTL trigger pulses to sync our audio stimuli with the EEG system. The way I do this is through simultaneous analog and S/PDIF audio from the same soundcard, with analog going to headphones and S/PDIF int values being split by a custom box into separate TTL bit lines. My current device requires a custom PCB to be made and populated, but I'm hoping the Teensy 4.0's S/PDIF input could replace it.

    Basically, every time a new S/PDIF sample arrives, the device would write its bits (or a subset of them) out to the GPIO. It would need to do this in real time, but I am hopeful it could work since that is all the system needs to do. It would need to work with 44.1 or 48 kHz and 16 or 24 bit audio.

    Is this possible? Is there any example code that reads the latest sample, perhaps triggered by an interrupt? Or perhaps the S/PDIF samples go to a register I can poll? I have read up on the audio library, but I don't believe there is a way to use that in real time since it's designed to do more than just pass samples through.

    Any guidance appreciated!

  2. #2
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    8,325
    Well, first it would be needed to define what "realtime" is.

  3. #3
    Junior Member
    Join Date
    Apr 2021
    Posts
    8
    It needs to do the operation on every incoming sample (so 44.1k or 48k times per second). So no frames like in normal applications. Latency, as long as it's consistent (jitter < .05 ms), is not a concern, since it can be measured and compensated.

  4. #4
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    8,325
    IMXRT1060 Reference Manual Rev2, Page 2028 shows that it can gegerate an interrupt depending on the fill-state of the FIFO.

    Of course there is no example code to do that, as the audio library is used, normally...


    https://www.pjrc.com/teensy/IMXRT1060RM_rev2.pdf

  5. #5
    Junior Member
    Join Date
    Apr 2021
    Posts
    8
    Thanks. From reading it seems like it should be possible. I've never programmed at this level before, but I'll have a go at it. Time to read up on interrupts.

  6. #6
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    8,325
    That's the easy part. attachInterrupt()
    Then, you can take a look at the audio-library SPDIF input and look at the code. Remove all the DMA stuff, and it is almost done..

  7. #7
    Junior Member
    Join Date
    Apr 2021
    Posts
    8
    This is probably a dumb question--but how do I access the specific interrupts and registers that are named in the manual? Since I'm sure this has been described a million times a link is fine.

    From the manual it looks as if the relevant interrupt is called "QChannelRxFull," but that doesn't show up anywhere in the code, nor does the name of the register "QChannelReceive" or maybe "UChannelReceive."

    I'll keep digging. Thanks for the help so far.

  8. #8
    Junior Member
    Join Date
    Apr 2021
    Posts
    8
    Ahhh those names aren't exactly how things are accessed in the code. For example the SPDIFRxLeft Register is read from SPDIF_SRL.

  9. #9
    Junior Member
    Join Date
    Apr 2021
    Posts
    8
    So I have my teensy and have made some progress in understanding the registers. However, when I set any of them I get upload errors. The code (which at this point just sends a byte out to a shift register through SPI to control some LEDs) works fine if all the lines that set SPDIF_SCR are commented out. But if any of them are uncommented then I get upload errors. Specifically, it uploads one time but the LEDs no longer blink. And any attempts to upload again require closing teensyduino and re-plugging the USB cable with the reset button held down. Is there something wrong I'm doing?

    Code below (in its working state with S/PDIF register lines commented out):
    Code:
    #include <SPI.h>
    
    #if defined(__IMXRT1062__)
    extern "C" uint32_t set_arm_clock(uint32_t frequency);
    #endif
    
    #define SPDIF_PIN_MODE 3
    
    // Set some pin names
    #define REG_CLEAR_PIN 8
    #define SHIFT_ENABLE_PIN 9
    #define LATCH_PIN 10
    #define SPI_MOSI_PIN 11
    #define SPI_CLK_PIN 13
    #define LED_POW_PIN 16
    #define LED_SPDIF_PIN 17
    
    void setup() {
      //set_arm_clock(400000000); // underclock Teensy to 400 MHz to save a bit of power
      
      // Set up S/PDIF ========
      // SPDIF_SCR = 0x0000400; // reset SPDIF configuration register with Tx normal (default)
      //SPDIF_SCR = 0x0; // reset SPDIF configuration register with Tx send digital zeros
      //SPDIF_SCR |= SPDIF_SCR_RXAUTOSYNC; // turn on s/pdif Rx autosync
      //SPDIF_SCR &= ~SPDIF_SCR_RXFIFO_OFF_ON; // want it set to 0: Rx FIFO is on
      
      CORE_PIN15_CONFIG = SPDIF_PIN_MODE; // set pin 15 to s/pdif input
      // RxFIFOFull_Sel This one has four options. Want it set to 00 - 1 sample in FIFO
      
      
      // Set up SPI shift registers ========
      pinMode(LATCH_PIN, OUTPUT);
      pinMode(SHIFT_ENABLE_PIN, OUTPUT);
      pinMode(REG_CLEAR_PIN, OUTPUT);
      pinMode(LED_POW_PIN, OUTPUT);
      pinMode(LED_SPDIF_PIN, OUTPUT);
    
      digitalWrite(SHIFT_ENABLE_PIN, LOW); // enable low
      digitalWrite(REG_CLEAR_PIN, LOW); // clear low
      digitalWrite(REG_CLEAR_PIN, HIGH); // clear low
    
      digitalWrite(LED_POW_PIN, HIGH);
      SPI.begin();
    }
    
    byte shift_val = 0;
    void loop() {
      // Send the bit values out to the shift registers using SPI
      SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));
      digitalWriteFast(LATCH_PIN, LOW);
      SPI.transfer(shift_val++);
      digitalWriteFast(LATCH_PIN, HIGH); 
      SPI.endTransaction();
      if(shift_val & 128) {
        digitalWriteFast(LED_POW_PIN, HIGH);
      } else {
        digitalWriteFast(LED_POW_PIN, LOW);
      }
       
      delay(50);
    }

  10. #10
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    24,251
    You need to turn on the clock to the SPDIF peripheral before you attempt to use it.

    Here's your program with the clock config copied from the Audio library. It starts up with those 4 lines uncommented.

    Code:
    #include <SPI.h>
    
    #if defined(__IMXRT1062__)
    extern "C" uint32_t set_arm_clock(uint32_t frequency);
    #endif
    
    #define SPDIF_PIN_MODE 3
    
    // Set some pin names
    #define REG_CLEAR_PIN 8
    #define SHIFT_ENABLE_PIN 9
    #define LATCH_PIN 10
    #define SPI_MOSI_PIN 11
    #define SPI_CLK_PIN 13
    #define LED_POW_PIN 16
    #define LED_SPDIF_PIN 17
    
    void setup() {
      //set_arm_clock(400000000); // underclock Teensy to 400 MHz to save a bit of power
    
      int n1 = 7; //0: divide by 1 (do not use with high input frequencies), 1:/2, 2: /3, 7:/8
      int n2 = 0; //0: divide by 1, 7: divide by 8
    
      CCM_CCGR5 &= ~CCM_CCGR5_SPDIF(CCM_CCGR_ON); //Clock gate off
    
      CCM_CDCDR = (CCM_CDCDR & ~(CCM_CDCDR_SPDIF0_CLK_SEL_MASK | CCM_CDCDR_SPDIF0_CLK_PRED_MASK | CCM_CDCDR_SPDIF0_CLK_PODF_MASK))
                  | CCM_CDCDR_SPDIF0_CLK_SEL(0) // 0 PLL4, 1 PLL3 PFD2, 2 PLL5, 3 pll3_sw_clk
                  | CCM_CDCDR_SPDIF0_CLK_PRED(n1)
                  | CCM_CDCDR_SPDIF0_CLK_PODF(n2);
    
      CCM_CCGR5 |= CCM_CCGR5_SPDIF(CCM_CCGR_ON); //Clock gate on
    
    
      // Set up S/PDIF ========
      SPDIF_SCR = 0x0000400; // reset SPDIF configuration register with Tx normal (default)
      SPDIF_SCR = 0x0; // reset SPDIF configuration register with Tx send digital zeros
      SPDIF_SCR |= SPDIF_SCR_RXAUTOSYNC; // turn on s/pdif Rx autosync
      SPDIF_SCR &= ~SPDIF_SCR_RXFIFO_OFF_ON; // want it set to 0: Rx FIFO is on
    
      CORE_PIN15_CONFIG = SPDIF_PIN_MODE; // set pin 15 to s/pdif input
      // RxFIFOFull_Sel This one has four options. Want it set to 00 - 1 sample in FIFO
    
    
      // Set up SPI shift registers ========
      pinMode(LATCH_PIN, OUTPUT);
      pinMode(SHIFT_ENABLE_PIN, OUTPUT);
      pinMode(REG_CLEAR_PIN, OUTPUT);
      pinMode(LED_POW_PIN, OUTPUT);
      pinMode(LED_SPDIF_PIN, OUTPUT);
    
      digitalWrite(SHIFT_ENABLE_PIN, LOW); // enable low
      digitalWrite(REG_CLEAR_PIN, LOW); // clear low
      digitalWrite(REG_CLEAR_PIN, HIGH); // clear low
    
      digitalWrite(LED_POW_PIN, HIGH);
      SPI.begin();
      while (!Serial && millis() < 4000) ; // wait (up to 4 seconds) for Arduino Serial Monitor
      Serial.println("started up");
    }
    
    byte shift_val = 0;
    void loop() {
      // Send the bit values out to the shift registers using SPI
      SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));
      digitalWriteFast(LATCH_PIN, LOW);
      SPI.transfer(shift_val++);
      digitalWriteFast(LATCH_PIN, HIGH);
      SPI.endTransaction();
      if (shift_val & 128) {
        digitalWriteFast(LED_POW_PIN, HIGH);
      } else {
        digitalWriteFast(LED_POW_PIN, LOW);
      }
    
      delay(50);
    }
    Maybe look at the Audio library code. There's probably more good examples in there to save you a lot of time.

  11. #11
    Junior Member
    Join Date
    Apr 2021
    Posts
    8
    That works again. The audio code has been very helpful. I missed this because it was in the output code and my application only uses s/pdif input. I should have looked there, too, though.

    Thanks for the help!

  12. #12
    Junior Member
    Join Date
    Apr 2021
    Posts
    8
    I believe I am very close to having this working. The last issue seems to be that my interrupt service routine is not getting called. When I take the code from the interrupt and put it into the loop, it operates as hoped, though that seems like a dodgy way to do it. I've googled extensively and not found the solution, so I apologize if it's something obvious I'm not understanding. Here is the code:

    Code:
    #include <SPI.h>
    
    #if defined(__IMXRT1062__)
    extern "C" uint32_t set_arm_clock(uint32_t frequency);
    #endif
    
    #define SPDIF_PIN_MODE 3
    
    // Set some pin names
    #define REG_CLEAR_PIN 8
    #define SHIFT_ENABLE_PIN 9
    #define LATCH_PIN 10
    #define SPI_MOSI_PIN 11
    #define SPI_CLK_PIN 13
    #define LED_POW_PIN 16
    #define LED_SPDIF_PIN 17
    
    volatile byte fifo_left[3];
    volatile byte fifo_right[3];
    void fifo_isr() {
      fifo_left[0] = SPDIF_SRL >> 0;
      fifo_left[1] = SPDIF_SRL >> 8;
      fifo_left[2] = SPDIF_SRL >> 16;
      fifo_right[0] = SPDIF_SRR >> 0;
      fifo_right[1] = SPDIF_SRR >> 8;
      fifo_right[2] = SPDIF_SRR >> 16;
      
      SPI.beginTransaction(SPISettings(100000000, MSBFIRST, SPI_MODE0));
      digitalWriteFast(LATCH_PIN, LOW);
      
      SPI.transfer(fifo_right[2]);
      SPI.transfer(fifo_right[1]);
      SPI.transfer(fifo_right[0]);
      SPI.transfer(fifo_left[2]);
      SPI.transfer(fifo_left[1]);
      SPI.transfer(fifo_left[0]);
      
      digitalWriteFast(LATCH_PIN, HIGH); 
      SPI.endTransaction();
    
      digitalWriteFast(LED_POW_PIN, LOW); // to test if the ISR is getting called
    };
    
    void setup() {
      set_arm_clock(500000000); // underclock Teensy to 400 MHz to save a bit of power
    
      // Initialize the S/PDIF clock ======== This might need to be modified to take the clock from S/PDIF in
      int n1 = 7; //0: divide by 1 (do not use with high input frequencies), 1:/2, 2: /3, 7:/8
      int n2 = 0; //0: divide by 1, 7: divide by 8
    
      CCM_CCGR5 &= ~CCM_CCGR5_SPDIF(CCM_CCGR_ON); //Clock gate off
      CCM_CDCDR = (CCM_CDCDR & ~(CCM_CDCDR_SPDIF0_CLK_SEL_MASK | CCM_CDCDR_SPDIF0_CLK_PRED_MASK | CCM_CDCDR_SPDIF0_CLK_PODF_MASK))
                  | CCM_CDCDR_SPDIF0_CLK_SEL(0) // 0 PLL4, 1 PLL3 PFD2, 2 PLL5, 3 pll3_sw_clk
                  | CCM_CDCDR_SPDIF0_CLK_PRED(n1)
                  | CCM_CDCDR_SPDIF0_CLK_PODF(n2);
      CCM_CCGR5 |= CCM_CCGR5_SPDIF(CCM_CCGR_ON); //Clock gate on
      
      // Set up S/PDIF registers ========
      SPDIF_SRPC = SPDIF_SRPC_CLKSRC_SEL(1); //if (DPLL Locked) SPDIF_RxClk else tx_clk (SPDIF0_CLK_ROOT)
      // SPDIF_SCR = 0x0000400; // reset SPDIF configuration register with Tx normal (default)
      SPDIF_SCR = 0x0; // reset SPDIF configuration register with Tx send digital zeros
      SPDIF_SCR |= SPDIF_SCR_RXAUTOSYNC; // turn on s/pdif Rx autosync
      SPDIF_SCR &= ~SPDIF_SCR_RXFIFO_OFF_ON; // want it set to 0: Rx FIFO is on
      
      CORE_PIN15_CONFIG = SPDIF_PIN_MODE; // set pin 15 to s/pdif input
      IOMUXC_SPDIF_IN_SELECT_INPUT = 0; // GPIO_AD_B1_03_ALT3 - taken from input_spdif
      // RxFIFOFull_Sel This one has four options. Want it set to 00 - 1 sample in FIFO, the default
      // use SPDIF_SCR_RXFIFOFULL_SEL(0);
    
      // Enable S/PDIF FIFO full interrupt (bit 0)
      //SPDIF Rx FIFO full, can't be cleared with reg. IntClear. To clear it, read from Rx FIFO.
      SPDIF_SIE = 1;
      attachInterrupt(IRQ_SPDIF, fifo_isr, HIGH);
      
      // Set up SPI shift registers ========
      pinMode(LATCH_PIN, OUTPUT);
      pinMode(SHIFT_ENABLE_PIN, OUTPUT);
      pinMode(REG_CLEAR_PIN, OUTPUT);
      pinMode(LED_POW_PIN, OUTPUT);
      pinMode(LED_SPDIF_PIN, OUTPUT);
    
      digitalWrite(SHIFT_ENABLE_PIN, LOW); // enable low
      digitalWrite(REG_CLEAR_PIN, LOW); // clear low
      digitalWrite(REG_CLEAR_PIN, HIGH); // clear low
    
      digitalWrite(LED_POW_PIN, HIGH);
      SPI.begin();
      SPI.usingInterrupt(IRQ_SPDIF);
    }
    
    void loop() {
      digitalWriteFast(LED_SPDIF_PIN, (SPDIF_SRPC & SPDIF_SRPC_LOCK) == SPDIF_SRPC_LOCK);
    }

Posting Permissions

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