Getting the registers right for SPDIF Tx using EXT_CLK on Teensy 4.0

rkmaddox

Member
Hi there,

I have a project for which I want the Teensy 4 to take in an external master audio clock generated by a different component on the SPDIF_EXT_CLK pin and transmit audio synchronized to that clock on SPDIF_OUT. I only need that audio for sending to a sound card so that it can synchronize its clock to it, so transmitting zeros on the SPDIF signal is OK. The sampling rate should be 48000, and the master clock is coming in at 48000 * 256 = 12.288 MHz. This project is not using the audio library.

I have read throught the audio section of the IMXRT manual multiple times, ctrl+f'ed for SPDIF, EXT_CLK, and all other related strings I can think of, checked the forums, and dug into the audio library code. I am hitting a wall.

The clock signal is confirmed fine on the scope, and I think my muxing is fine, but there must be something off with my register set up (I think). On the SPDIF_OUT pin I get a constant low signal, rather than a SPDIF signal encoding zero or anything else.

Any help would be appreciated. Code is below (it hasn't gone through it's final prettifying yet since it isn't working--sorry about that), and I'm sure some of the SPDIF stuff that's in there doesn't need to be. There is some GPT stuff in there as well, but that doesn't have to do with this directly.

C:
#include <Arduino.h>

#define IN_SAMPLE_TEST_MODE 0 // 0 uses the actual incoming clock, 1 uses the PIT to create the clock interrupts
#define CLKSRC 2  // 1 is peripheral clock, 2 is HF clock (which defaults to 24 MHz--figure out how to do 150 MHz)
#define TPS 204000000  // 150000000 or 24000000; or 180000000 (720 MHz) 204000000 (816 MHz) overclocking
#define EPOCH_PIN 19
#define TEST_PIN_16384 2
#define TXB0104_OE_PIN 4
#define RESET_4096_PIN 5
#define FS_PIN 8  // high is 48000, low is 44100

#if defined(__IMXRT1062__)
extern "C" uint32_t set_arm_clock(uint32_t frequency);
#endif

#define SPDIF_DPLL_GAIN24 0
#define SPDIF_DPLL_GAIN16 1
#define SPDIF_DPLL_GAIN12 2
#define SPDIF_DPLL_GAIN8 3
#define SPDIF_DPLL_GAIN6 4
#define SPDIF_DPLL_GAIN4 5
#define SPDIF_DPLL_GAIN3 6
#define SPDIF_DPLL_GAIN1 7
#define SPDIF_DPLL_GAIN SPDIF_DPLL_GAIN8 //Actual Gain

// for some reason there are long gaps when GCF is 2 or 4. They don't seem to happen when it's 64, though tough to say for sure. Should try to found out why they happen
const uint32_t FS_IN = 16384;
const uint32_t GCF = 4;  // this MUST be integer factor of FS_IN and fs_out  (4 is good for 44100 and 48000)
const uint32_t FS_OPTIONS[] = {44100, 48000};
const uint32_t N_IN = FS_IN / GCF;
volatile uint32_t fs_out;  // will be read from a pin
volatile uint32_t n_out;  // will be set once fs_out is read

const float IN_GAP_TOL = .025;
const uint32_t IN_GAP_NOM = TPS / GCF;
const uint32_t IN_GAP_MIN = (1. - IN_GAP_TOL) * IN_GAP_NOM;
const uint32_t IN_GAP_MAX = (1. + IN_GAP_TOL) * IN_GAP_NOM;

volatile uint32_t in_times[N_IN] = {0};  // ring buffer of in sample times
volatile uint32_t calc_time;  // ring buffer of in sample times
volatile uint32_t in_gap = 0;  // the time since N_IN samples ago
volatile uint32_t idx_in = 0;  // which input sample did we just receive
volatile bool zeroed = false;  // are the epoch onsets aligned?
volatile uint32_t out_step = 0;  // the time since N_IN samples ago
void isr_samples();

void setup()
{
  // enable the GPT2 clock https://forum.pjrc.com/index.php?threads/teensy-4-1-gpt2-failures.69684/
  CCM_CCGR0 |= CCM_CCGR0_GPT2_BUS(CCM_CCGR_ON);  // | CCM_CCGR0_GPT2_SERIAL(CCM_CCGR_ON);
  if (TPS >= 150000000) {
    CCM_CSCMR1 &= ~64;  // run clock in fast mode for better precision
    set_arm_clock(TPS * 4);
    delay(100);
  }

  // Setup USB serial port so we can print the timer value
  Serial.begin(115200);
  Serial.println("Initializing...");

  // Set up the SPDIF output ======================================================================
  // see: https://github.com/PaulStoffregen/Audio/blob/master/output_spdif3.cpp
  pinMode(14, OUTPUT);  // SPDIF OUT
  pinMode(16, INPUT);  // SPDIF EXT_CLK
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = 3;  // SPDIF_OUT on PIN 14
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_07 = 3;  // SPDIF_EXT_CLK on PIN 16
  IOMUXC_SPDIF_IN_SELECT_INPUT = 0;  // 0 = GPIO_AD_B1_03_ALT3;
 
  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 -- transfer clock controlled by this
    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

  SPDIF_SCR =
    ~SPDIF_SCR_RXFIFO_CTR |  // 0 normal operation, 1 read zeros
    ~SPDIF_SCR_RXFIFO_OFF_ON |  // 0 is on, 1 is off
        SPDIF_SCR_RXFIFOFULL_SEL(0) |    // Full interrupt if at least 1 sample in Rx left and right FIFOs -- can use this instead of comparing last input FIFO samples?
        SPDIF_SCR_RXAUTOSYNC |
        SPDIF_SCR_TXAUTOSYNC |
        SPDIF_SCR_TXFIFOEMPTY_SEL(2) |    // Empty interrupt if at most 8 samples in Tx left and right FIFOs
        SPDIF_SCR_TXFIFO_CTRL(0) |    // 0:Send zeros 1: normal operation  // can eventually poll for SPDIF_SIS_TXEM in SPDIF_SIS to see if a new output sample is needed
        SPDIF_SCR_VALCTRL |        // Outgoing Validity always clear
        SPDIF_SCR_TXSEL(5) |        // 0:off and output 0, 1:Feed-though SPDIFIN, 5:Tx Normal operation
        SPDIF_SCR_USRC_SEL(3);

    SPDIF_SRPC =
        SPDIF_SRPC_CLKSRC_SEL(8) |    // 8 is ext_clk
        SPDIF_SRPC_GAINSEL(SPDIF_DPLL_GAIN);
 
  SPDIF_STC = SPDIF_STC_SYSCLK_DF(0) |  // no master clock signal
              SPDIF_STC_TXCLK_SOURCE(3) |  // 3 is EXT_CLK
              SPDIF_STC_TX_ALL_CLK_EN |  // enable the transfer clock
              SPDIF_STC_TXCLK_DF(3);  // clock divider (-1, such that 0 is 1, 1 is 2...).
              // Signal on EXT_CLK pin is 48000 * 256 = 12.288 MHz
              // I think this should either be 2 (6.144) or 4 (3.072--but transmits on each clock change)? Page 2041.
              // The recovered clock from Rx is 128 * fs (so 6.144). page 2049

  // SPDIFTxLeft, SPDIFTxRight registers are audio data called SPDIF_STL and SPDIF_SRL
  // The Tx left and right FIFOs are also 16-deep and 24-width (equal to the audio data width)

  // Set up the timer stuff for the sample syncing ================================================
  // output a ~16384 test signal on TEST_PIN_16384
  // would be better to do this with the PIT with the 32768 clock
  analogWriteFrequency(TEST_PIN_16384, FS_IN);
  analogWrite(TEST_PIN_16384, 127);
  digitalWriteFast(TXB0104_OE_PIN, LOW);
  pinMode(TXB0104_OE_PIN, OUTPUT);
  pinMode(RESET_4096_PIN, OUTPUT);
  pinMode(FS_PIN, INPUT_PULLUP);
  digitalWriteFast(RESET_4096_PIN, HIGH);  // HIGH is reset, LOW is enabled
  fs_out = FS_OPTIONS[digitalReadFast(FS_PIN)];
  n_out = fs_out * 2 / GCF;  // times 2 because it needs to both SET and CLEAR

  // Set up the GPT
  pinMode(15, INPUT_PULLDOWN);  // capture pin
  pinMode(17, OUTPUT);  // output clock
  pinMode(EPOCH_PIN, OUTPUT);  // Toggle on each n_out samples (GCF Hz toggle)
  IOMUXC_GPT2_IPP_IND_CAPIN1_SELECT_INPUT = 1;  // 1 = GPIO_AD_B1_03_ALT8
  IOMUXC_GPT2_IPP_IND_CAPIN2_SELECT_INPUT = 1;  // 1 = GPIO_AD_B1_04_ALT8
 
  /// Set pin functions
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_03 = 8;  // GPT2 CAPTURE1 on PIN 15
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_06 = 8;  // GPT2 COMPARE2 on PIN 17
 
  // Set up the pads
  IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_03 = 0x13000;  // Pulldown & Hyst
  IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_06 = (7 << 3) +  // off-high drive strength (1-7)
                                        (3 << 6) +  // low-high speed (0-3)
                                        (1 << 0);  // slow-fast slew (0-1)
  // Those pin modes are instead of setting IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_03 and 07, but lots of options for configuring there

  GPT2_CR = 0;  // Disable for configuration 
  GPT2_PR = 0;  // No prescaler
  GPT2_SR = 0x3F;  // clear all prior status
  GPT2_CR |= GPT_CR_CLKSRC(1)  // HF clock as clock source
          | GPT_CR_FRR  // Free-Run, do not reset
          | GPT_CR_IM1(1);  // capture 1 on rising edge
  GPT2_IR = GPT_IR_OF2IE; // enable interrupt for output compare event only, it will also keep track of input samples as they come in

  attachInterruptVector(IRQ_GPT2, isr_samples);  // declare which routine performs the ISR function
  NVIC_SET_PRIORITY(IRQ_GPT2, 48);  // set it it to a pretty high priority (may not be needed though, since its not directly driving the pins)  32 is systick, 64 is serial
  NVIC_ENABLE_IRQ(IRQ_GPT2);

  Serial.println("Running...");
  GPT2_CR |= GPT_CR_EN;  // Enable timer
  GPT2_OCR2 = GPT2_CNT + TPS * 0.25;  // first event will be a quarter second from now, then should hopefully be self-sustaining
  GPT2_CR &= ~GPT_CR_OM3(7) & ~GPT_CR_OM2(7);  // clear the three output mode bits
  GPT2_CR |= GPT_CR_OM3(1) | GPT_CR_OM2(1);  // output TOGGLE mode (1)
 
  delay(50);
  digitalWriteFast(TXB0104_OE_PIN, HIGH);  // enable the logic level translater
}

void loop()
{
 delay(1);
}
 
Maybe I missed it but it seems you're setting the SPDIF0 clk to use PLL4 (AKA audio PLL) as a source but never configuring it? That PLL is powered off/disabled by default.
 
I don't like the look of this:
C:
  SPDIF_SCR =
    ~SPDIF_SCR_RXFIFO_CTR |  // 0 normal operation, 1 read zeros
    ~SPDIF_SCR_RXFIFO_OFF_ON |  // 0 is on, 1 is off
        SPDIF_SCR_RXFIFOFULL_SEL(0) |    // Full interrupt if at least 1 sample in Rx left and right FIFOs -- can use this instead of comparing last input FIFO samples?
        SPDIF_SCR_RXAUTOSYNC |
        SPDIF_SCR_TXAUTOSYNC |
        SPDIF_SCR_TXFIFOEMPTY_SEL(2) |    // Empty interrupt if at most 8 samples in Tx left and right FIFOs
        SPDIF_SCR_TXFIFO_CTRL(0) |    // 0:Send zeros 1: normal operation  // can eventually poll for SPDIF_SIS_TXEM in SPDIF_SIS to see if a new output sample is needed
        SPDIF_SCR_VALCTRL |        // Outgoing Validity always clear
        SPDIF_SCR_TXSEL(5) |        // 0:off and output 0, 1:Feed-though SPDIFIN, 5:Tx Normal operation
        SPDIF_SCR_USRC_SEL(3);
You appear to be setting every bit in SPDIF_SCR, which I'm fairly sure you don't intend to...
 
Maybe I missed it but it seems you're setting the SPDIF0 clk to use PLL4 (AKA audio PLL) as a source but never configuring it? That PLL is powered off/disabled by default.
Ahhhh, the last time I did a SPDIF project it had a signal coming in and extracted its clock from that, so it worked without turning that PLL on.

The code for turning on the PLL is here, right? https://github.com/PaulStoffregen/Audio/blob/master/utility/imxrt_hw.cpp#L34

I'll try that when I get home tonight and report back. (Though I'll be honest I don't totally understand why PLL4 is necessary, since it has a good signal coming in on EXT_CLK)
 
I added the code from that function and still don't see anything on the SPDIF_OUT pin. I also tried to set the registers so that it clocked from the PLL rather than EXT_CLK and still did not see anything, though I am not at all confident I did it correctly.
 
I don't like the look of this:
C:
  SPDIF_SCR =
    ~SPDIF_SCR_RXFIFO_CTR |  // 0 normal operation, 1 read zeros
    ~SPDIF_SCR_RXFIFO_OFF_ON |  // 0 is on, 1 is off
        SPDIF_SCR_RXFIFOFULL_SEL(0) |    // Full interrupt if at least 1 sample in Rx left and right FIFOs -- can use this instead of comparing last input FIFO samples?
        SPDIF_SCR_RXAUTOSYNC |
        SPDIF_SCR_TXAUTOSYNC |
        SPDIF_SCR_TXFIFOEMPTY_SEL(2) |    // Empty interrupt if at most 8 samples in Tx left and right FIFOs
        SPDIF_SCR_TXFIFO_CTRL(0) |    // 0:Send zeros 1: normal operation  // can eventually poll for SPDIF_SIS_TXEM in SPDIF_SIS to see if a new output sample is needed
        SPDIF_SCR_VALCTRL |        // Outgoing Validity always clear
        SPDIF_SCR_TXSEL(5) |        // 0:off and output 0, 1:Feed-though SPDIFIN, 5:Tx Normal operation
        SPDIF_SCR_USRC_SEL(3);
You appear to be setting every bit in SPDIF_SCR, which I'm fairly sure you don't intend to...
Any ideas which config options are incorrect? I set them to things I thought made sense, but clearly have not gotten it right yet (unless the problem is somewhere else).
 
Any ideas which config options are incorrect? I set them to things I thought made sense, but clearly have not gotten it right yet (unless the problem is somewhere else).
The logic doesn't look right. If a bit is to be unset, ORing with its inverse will set every other bit in the register.
Instead just leave those bits out of the assignment and they will implicitly be set to zero/cleared.
 
Oh my gosh I see it now. If I were going to do it that way I guess it would need to be & on those ~ lines, rather than |. I've taken them out and I'm getting a signal on SPDIF_OUT now. Thank you!
 
Back
Top