I2S MCLK output on SAMD21

Hi, I’ve been scratching my head for days trying to output MCLK from a SAMD21 board.

I have a DAC that requires MCLK clocked at around 8MHz (it has PLL so exact freq is not important).

The trouble I’m having is that the I2S library works great, if I do not need MCLK. ArdafruitZeroI2S unfortunately also does not output MCLK on SAMD21 (for mysterious reason).

Has anyone out there had success with outputting MCLK through the I2S peripheral I2SMC pin?

Hi tanvach,

I don't know that much about the I2S library, but you can activate the I2S/MCLK[0] and I2S/MCLK[1] outputs on port pins PA09 and PB10 respectively, (on D3 and SPI MOSI on the Arduino Zero) with the following code.

I2S/MCLK[0] on PA09:

PORT->Group[PORTA].PINCFG[9].bit.PMUXEN = 1;                // Activate the port multiplexer for pin PA09
PORT->Group[PORTA].PMUX[9 >> 1].reg |= PORT_PMUX_PMUXO_G;   // Move mux switch to position G or I2S/MCLK[0] for ODD port PA09

I2S/MCLK[1] on PB10:

PORT->Group[PORTB].PINCFG[10].bit.PMUXEN = 1;                // Activate the port multiplexer for pin PB10
PORT->Group[PORTB].PMUX[10 >> 1].reg |= PORT_PMUX_PMUXE_G;   // Move mux switch to position G or I2S/MCLK[1] for EVEN port PB10

Hi tanvach,

I had a look at the AdafruitZeroI2S library. It's strange that they chose to activate the MCLK output and division enable bit on the SAMD51, but not on the SAMD21.

To activate this bit on channel 0 for example:

I2S->CLKCTRL[0].bit.MCKEN = 1;    // Set the MCLK ENABLE bit on channel 0

This line can be called immediately after the library's begin() function, but before the I2S peripheral itself is enabled.

Thank you for replying so quickly!

I'm using Adafruit QtPy which has PA09 connected to the MISO pin (schematic). I got your example to work for Adafruit Zero I2S library by combining the two suggestions in tone_generator example:

void setup() {
  // Configure serial port.
  Serial.begin(115200);
  Serial.println("Zero I2S Audio Tone Generator");

  // Initialize the I2S transmitter.
  if (!i2s.begin(I2S_32_BIT, SAMPLERATE_HZ)) {
    Serial.println("Failed to initialize I2S transmitter!");
    while (1);
  }

  // Enable MCLK
  I2S->CLKCTRL[0].bit.MCKEN = 1;
  PORT->Group[PORTA].PINCFG[9].bit.PMUXEN = 1;
  PORT->Group[PORTA].PMUX[9 >> 1].reg |= PORT_PMUX_PMUXO_G; 
  
  i2s.enableTx();

  // Generate waveforms.
  generateSine(AMPLITUDE, sine, WAV_SIZE);
  generateSawtooth(AMPLITUDE, sawtooth, WAV_SIZE);
  generateTriangle(AMPLITUDE, triangle, WAV_SIZE);
  generateSquare(AMPLITUDE, square, WAV_SIZE);
}

The MCLK output is unfortunately the same as BCLK (~2.7MHz in the example).

I would appreciate any pointers if you know how to change the MCLK to arbitrary frequency, but keep BCLK at the correct rate.

Edit: I found the relevant section on page 581 in the datasheet, but my brain exploded.
MCKn Clock frequency
When the I2S is in Master mode, writing a one to CLKCTRLn.MCKEN will output GCLK_I2S_n as Master Clock to the MCKn pin. The Master Clock to MCKn pin can be divided by configuring CLKCTRLn.MCKSEL and CLKCTRLn.MCKOUTOUT. The Master Clock (MCKn) frequency is GCLK_I2S_n frequency divided by (MCLKOUTDIV+1).
SCKn Clock frequency
When the Serial Clock (SCKn) is generated from GCLK_I2S_n, by setting CLKCTRLn.MCKSEL and CLKCTRLn.SCKSEL to zero, the Serial Clock (SCKn) frequency is GCLK_I2S_n frequency divided by (MCKDIV+1). i.e. f(SCKn) = f(GCLK_I2S_n)/(MCKDIV +1)

I found the solution, so documenting it here as reference.

When calling i2s.begin(), for SAMD21 boards Adafruit_ZeroI2S library will set clock speed to:

 uint32_t divider = fs_freq * 2 * (width + 1) * 8;
...
  GCLK->GENDIV.bit.ID = I2S_CLOCK_GENERATOR;
  GCLK->GENDIV.bit.DIV = SystemCoreClock / divider;

So I had to override GCLK->GENDIV.bit.DIV = 4 since I wanted 12MHz clock (48MHz / 4 = 12MHz).

Then I have to set the SCK (which is the bit clock rate, aka BCLK) divisor, confusingly names MCKDIV. I played around with this value until I got it right, but you might need to figure out the right value for your application.

Summary, this is the setup code to generate 12MHz MCLK and 1.5MHz SCK/BCLK:

void setup() {
  
  if (!i2s.begin(BITWIDTH, SAMPLERATE_HZ)) {
    while (1);            
  }

  // Custom I2S control to output correct freq
  // Enable MCLK to PA09 (MISO pin on QtPy)
  I2S->CLKCTRL[0].bit.MCKEN = 1;
  PORT->Group[PORTA].PINCFG[9].bit.PMUXEN = 1;
  PORT->Group[PORTA].PMUX[9 >> 1].reg |= PORT_PMUX_PMUXO_G; 
  
  // Change MCLK divider = 48MHz / (256 * 0.048) = 3.9 (round to 4)
  // Target: 12.288MHz clock
  // Achieved: 12MHz clock
  GCLK->GENDIV.bit.DIV = 4;
  
  // Then change BCLK to match required for sampling rate
  // This example outputs 12MHz / (7+1) = 1.5MHz to P10 (MOSI)
  I2S->CLKCTRL[0].bit.MCKDIV = 7;
  
  while (GCLK->STATUS.bit.SYNCBUSY);

  // BONUS: Enable this to let the I2S clocks continue to run in standby/deep sleep mode
  // SYSCTRL->DFLLCTRL.bit.RUNSTDBY = 1;
  
  i2s.enableTx();
}

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.