Voting resources, early voting, and poll worker information - VOTE. ... Adafruit is open and shipping.
0

Metro M4 - I2S Slave Mode
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Metro M4 - I2S Slave Mode

by klw1971 on Thu Jul 16, 2020 12:26 pm

Hi everyone,

Does anyone have I2S slave mode running on a Metro M4 or M4 Airlift? Metro M4 is slave and the codec is master supplying bit clock and frame select / LRCL. As far as I know master clock shouldn't be needed by Metro M4. I can get slave mode to work on Raspberry Pi 3B+ using overlays and the device tree and on the Feather M0 with the older I2S library... but not on the M4.

- The older I2S library in .arduino15/packages/adafruit/hardware/samd/1.5.14/libraries/I2S works in slave mode for the Feather M0... and works with the external codec.
- The newer Adafruit_ZeroI2S() works great for the SPH0645 in master mode - with DMA and without.
- Adafruit_ZeroI2S library doesn't support slave mode yet but I have tried to modify it by not starting the clock and selecting the FS / SCK pins as inputs.
- I varied the setting to turn off/on DMA, turn off/on clock select, turn off/on serializers, turn off/on interrupts, etc.
- I see a slave implementation in Atmel Start and CMSIS but haven't tried to load those yet - new environment for me and I'd rather stick with Arduino at the moment. :-/

No luck so far but this works on Feather M0 and the datasheet promises it to work for Metro M4. Any help would be greatly appreciated!!!

Thanks!
klw1971

Code: Select all | TOGGLE FULL SIZE
#define SLAVE true

bool Adafruit_ZeroI2S::begin(I2SSlotSize width, int fs_freq, int mck_mult) {
#if defined(__SAMD51__)

  pinPeripheral(_fs, PIO_I2S);
  pinPeripheral(_sck, PIO_I2S);
  pinPeripheral(_rx, PIO_I2S);
  pinPeripheral(_tx, PIO_I2S);
  // pinPeripheral(PIN_I2S_MCK, PIO_I2S);

  I2S->CTRLA.bit.ENABLE = 0;

  if ( !SLAVE ) {
    // initialize clock control
    MCLK->APBDMASK.reg |= MCLK_APBDMASK_I2S;

    uint32_t mckFreq = (fs_freq * mck_mult);
    uint32_t sckFreq = fs_freq * I2S_NUM_SLOTS * ((width + 1) << 3);

    uint32_t gclkval = GCLK_PCHCTRL_GEN_GCLK1_Val;
    uint32_t gclkFreq = VARIANT_GCLK1_FREQ;
    uint8_t mckoutdiv = min((gclkFreq / mckFreq) - 1, 63);
    uint8_t mckdiv = min((gclkFreq / sckFreq) - 1, 63);

    Serial.print("gclkval "); Serial.println(gclkval);
    Serial.print("gclkFreq "); Serial.println(gclkFreq);
    Serial.print("mckoutdiv "); Serial.println(mckoutdiv);
    Serial.print("mckdiv "); Serial.println(mckdiv);

    if (((VARIANT_GCLK1_FREQ / mckFreq) - 1) > 63) {
      gclkval = GCLK_PCHCTRL_GEN_GCLK4_Val;
      gclkFreq = 12000000;
    }

    GCLK->PCHCTRL[I2S_GCLK_ID_0].reg = gclkval | (1 << GCLK_PCHCTRL_CHEN_Pos);
    GCLK->PCHCTRL[I2S_GCLK_ID_1].reg = gclkval | (1 << GCLK_PCHCTRL_CHEN_Pos);

    // software reset
    I2S->CTRLA.bit.SWRST = 1;
    while (I2S->SYNCBUSY.bit.SWRST || I2S->SYNCBUSY.bit.ENABLE)
      ; // wait for sync

     // CLKCTRL[0] is used for the tx channel
     I2S->CLKCTRL[0].reg =
         I2S_CLKCTRL_MCKSEL_GCLK | I2S_CLKCTRL_MCKOUTDIV(mckoutdiv) |
         I2S_CLKCTRL_MCKDIV(mckdiv) | I2S_CLKCTRL_SCKSEL_MCKDIV |
         I2S_CLKCTRL_MCKEN | I2S_CLKCTRL_FSSEL_SCKDIV | I2S_CLKCTRL_BITDELAY_I2S |
         I2S_CLKCTRL_FSWIDTH_HALF | I2S_CLKCTRL_NBSLOTS(I2S_NUM_SLOTS - 1) |
         I2S_CLKCTRL_SLOTSIZE(width);
  }
  else {
    // software reset
    I2S->CTRLA.bit.SWRST = 1;
    while (I2S->SYNCBUSY.bit.SWRST || I2S->SYNCBUSY.bit.ENABLE)
      ; // wait for sync

    // CLKCTRL[0] is used for the tx channel
    I2S->CLKCTRL[0].reg =
        // I2S_CLKCTRL_MCKSEL_MCKPIN |
        I2S_CLKCTRL_SCKSEL_SCKPIN |
        I2S_CLKCTRL_FSSEL_FSPIN |
        I2S_CLKCTRL_BITDELAY_I2S |
        I2S_CLKCTRL_FSWIDTH_HALF |
        I2S_CLKCTRL_NBSLOTS(I2S_NUM_SLOTS - 1) |
        I2S_CLKCTRL_SLOTSIZE(width);

    // /* -------- I2S_CLKCTRL : (I2S Offset: 0x04) (R/W 32) Clock Unit n Control -------- */
    // typedef union {
    //   struct {
    //     uint32_t SLOTSIZE:2;       /*!< bit:  0.. 1  Slot Size                          */
    //     uint32_t NBSLOTS:3;        /*!< bit:  2.. 4  Number of Slots in Frame           */
    //     uint32_t FSWIDTH:2;        /*!< bit:  5.. 6  Frame Sync Width                   */
    //     uint32_t BITDELAY:1;       /*!< bit:      7  Data Delay from Frame Sync         */
    //     uint32_t FSSEL:1;          /*!< bit:      8  Frame Sync Select                  */
    //     uint32_t FSINV:1;          /*!< bit:      9  Frame Sync Invert                  */
    //     uint32_t FSOUTINV:1;       /*!< bit:     10  Frame Sync Output Invert           */
    //     uint32_t SCKSEL:1;         /*!< bit:     11  Serial Clock Select                */
    //     uint32_t SCKOUTINV:1;      !< bit:     12  Serial Clock Output Invert         
    //     uint32_t MCKSEL:1;         /*!< bit:     13  Master Clock Select                */
    //     uint32_t MCKEN:1;          /*!< bit:     14  Master Clock Enable                */
    //     uint32_t MCKOUTINV:1;      /*!< bit:     15  Master Clock Output Invert         */
    //     uint32_t MCKDIV:6;         /*!< bit: 16..21  Master Clock Division Factor       */
    //     uint32_t :2;               /*!< bit: 22..23  Reserved                           */
    //     uint32_t MCKOUTDIV:6;      /*!< bit: 24..29  Master Clock Output Division Factor */
    //     uint32_t :2;               /*!< bit: 30..31  Reserved                           */
    //   } bit;                       /*!< Structure used for bit  access                  */
    //   uint32_t reg;                /*!< Type      used for register access              */
    // } I2S_CLKCTRL_Type;

   // From SAMD51_I2SDevice.h - old I2S implementation
    // i2s.CLKCTRL[index].bit.SCKSEL = I2S_CLKCTRL_SCKSEL_SCKPIN_Val;
    // i2s.CLKCTRL[index].bit.FSSEL = I2S_CLKCTRL_FSSEL_FSPIN_Val;
    // I2S->CLKCTRL[0].bit.SLOTSIZE = I2S_CLKCTRL_SLOTSIZE_16_Val;
    // I2S->CLKCTRL[0].bit.NBSLOTS = I2S_CLKCTRL_NBSLOTS(I2S_NUM_SLOTS - 1);
    // I2S->CLKCTRL[0].bit.FSWIDTH = I2S_CLKCTRL_FSWIDTH_HALF_Val;
    // I2S->CLKCTRL[0].bit.BITDELAY = I2S_CLKCTRL_BITDELAY_LJ_Val;
    // I2S->CLKCTRL[0].bit.FSSEL = I2S_CLKCTRL_FSSEL_FSPIN_Val;
    // I2S->CLKCTRL[0].bit.SCKSEL = I2S_CLKCTRL_SCKSEL_SCKPIN_Val;
  }

  uint8_t wordSize;

  switch (width) {
  case I2S_8_BIT:
    wordSize = I2S_TXCTRL_DATASIZE_8_Val;
    break;
  case I2S_16_BIT:
    wordSize = I2S_TXCTRL_DATASIZE_16_Val;
    break;
  case I2S_24_BIT:
    wordSize = I2S_TXCTRL_DATASIZE_24_Val;
    break;
  case I2S_32_BIT:
    wordSize = I2S_TXCTRL_DATASIZE_32_Val;
    break;
  }

  I2S->TXCTRL.reg = //I2S_TXCTRL_DMA_SINGLE |
                    I2S_TXCTRL_MONO_STEREO |
                    I2S_TXCTRL_BITREV_MSBIT |
                    I2S_TXCTRL_EXTEND_ZERO |
                    I2S_TXCTRL_WORDADJ_RIGHT |
                    I2S_TXCTRL_DATASIZE(wordSize) |
                    I2S_TXCTRL_TXSAME_ZERO |
                    I2S_TXCTRL_TXDEFAULT_ZERO;

  I2S->RXCTRL.reg = //I2S_RXCTRL_DMA_SINGLE |
                    I2S_RXCTRL_MONO_STEREO |
                    I2S_RXCTRL_BITREV_MSBIT |
                    I2S_RXCTRL_EXTEND_ZERO |
                    I2S_RXCTRL_WORDADJ_RIGHT |
                    I2S_RXCTRL_DATASIZE(wordSize) |
                    I2S_RXCTRL_SLOTADJ_RIGHT |
                    I2S_RXCTRL_CLKSEL_CLK0 |
                    I2S_RXCTRL_SERMODE_RX;

  while (I2S->SYNCBUSY.bit.ENABLE)
    ; // wait for sync
  I2S->CTRLA.bit.ENABLE = 1;

  Serial.println("I2S::begin() finished");

  return true;

#else // SAMD21
...
#endif
}

klw1971
 
Posts: 4
Joined: Fri Jun 22, 2018 1:12 pm

Please be positive and constructive with your questions and comments.