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

Thread: I2S Input Quad BCK Query

  1. #1
    Junior Member
    Join Date
    Jul 2018
    Posts
    5

    I2S Input Quad BCK Query

    Hello!

    I'm currently working on a project that requires quad I2S input and output. I am using two CS4334-KSZ 24 bit DACs for the output with the quad I2S output object, which is working fine. However, I want to use the quad I2S input object with two UDA1361TS ADCs, which require a BCK of 64. I understand that the I2S quad input uses a BCK of 32, not 64. Although others have discussed modifications to the library that allow a 64 BCK in I2S slave mode, I require the Teensy to be in master mode, as to not cause interference with the ADC clock signals. I was wondering what modifications I would have to make to allow for a BCK of 64 within the quad I2S input object. I am relatively new to embedded software development, so any advice on this matter is greatly appreciated.

    Thanks!

    PS - Thread I refer too: https://forum.pjrc.com/threads/45394...put-and-Output
    Attached Files Attached Files

  2. #2
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    18,511
    Changing this would require digging into the low-level I2S code inside the audio library. Definitely not a beginner level project.

    There 2 parts needed. The easy part is just changing the clock ratio. It's one of the many fields written to the 5 control registers.

    The harder part, and the main reason why quad I2S still has a MCLK/BCLK ratio of 32, involves reprogramming the DMA's TCD registers. That's *definitely* not beginning level material! For ordinary stereo, the DMA was changed to merely do a 16 bit transfer to half of the 32 bit data. But for dual stereo, things are more complex, maybe involving special setup of the DMA minor loop, maybe using a special data packing feature in the I2S peripheral. It's not simple and almost impossible to troubleshoot if you get it wrong, which is the reason I've not done it yet....

  3. #3
    Junior Member
    Join Date
    Jul 2018
    Posts
    5
    Thank you for the quick reply.

    Yeah I had a feeling it would be more complicated then just changing a clock ratio. For me reprogramming DMA registers would probably be a project in itself.

    You wouldn't happen to know if there are any other ADCs that can function with a BCK of 32? I thought maybe I could use the same audio codec chip that is used in audio shield, but that might be slightly overkill for my requirements.

  4. #4
    Senior Member
    Join Date
    Jul 2014
    Posts
    1,912
    The question is, do you only want to interface with 32 bit I2S devices, or do you wanted integration into 16 bit Audio library?
    - the first case is definitely easier
    - integration into audio library is definitely more complicated

  5. #5
    Junior Member
    Join Date
    Jul 2018
    Posts
    5
    Well I am currently utilising a fair number of audio library objects, so integration is preferable, especially as I only have another month to complete this project.

    Would it be easier to build my own I2S driver specific for the project then?

  6. #6
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    18,511
    Quote Originally Posted by OlleyStone View Post
    Would it be easier to build my own I2S driver specific for the project then?
    No, that would be jumping out of the frying pan and into the fire!

    If you already have the UDA1361TS running, maybe give it a try with BCLK/LRCLK ratio of 32. Odds look very good it will "just work" if the UDA1361TS is running in I2S slave mode. Figure 3 on page 6 seems to say it could even do as little as a ratio of only 16 (needing 8 or more BCK per phase of WS).

    Sure, the UDA1361TS datasheet documents a ratio of 64 on page 5, but that's for master mode. Almost all of these I2S chips are quite flexible in slave mode. The MEMS microphones are the exception, with a rigid requirement for a ratio of 64.

    The specs on page 10 also seem to say 64 is fixed for master mode, but in slave mode 64 is merely the maximum and you can choose to use a lower ratio. Strangely the table doesn't show any minimum, even though figure 3 seems to say the minimum ratio is 16.

    If you don't have any I2S ADC hardware up and running already, and especially with a tight project deadline, if those chips don't work out (very soon) I'd suggest just getting 2 of the regular audio shields.

  7. #7
    Junior Member
    Join Date
    Jul 2018
    Posts
    5
    Yeah the reason I selected the UDA1361TS was because it didn't say specifically that it wouldn't work with a BCK of 32 in slave mode.

    I did try it previously using a BCK ratio of 32 as per the I2S quad input object, however signal received by the Teenys seemed to be extremely noisy. I am using the UDA1361TS signal to modulate the frequency of a waveform object, yet it appears the signal is modulating it far too excessively.

    I have tried to replicate the example schematic on the datasheet to the best of my ability, but in the end I assumed it was the BCK causing this issue. I may be wrong.

    If it is the BCK at fault here, would it normally cause this level of noise/excessive modulation in the I2S signal?

  8. #8
    Senior Member
    Join Date
    Jul 2014
    Posts
    1,912
    Quote Originally Posted by OlleyStone View Post
    Yeah the reason I selected the UDA1361TS was because it didn't say specifically that it wouldn't work with a BCK of 32 in slave mode.

    I did try it previously using a BCK ratio of 32 as per the I2S quad input object, however signal received by the Teenys seemed to be extremely noisy. I am using the UDA1361TS signal to modulate the frequency of a waveform object, yet it appears the signal is modulating it far too excessively.

    I have tried to replicate the example schematic on the datasheet to the best of my ability, but in the end I assumed it was the BCK causing this issue. I may be wrong.

    If it is the BCK at fault here, would it normally cause this level of noise/excessive modulation in the I2S signal?
    If you wanted to play have a look into the following

    some ino file
    Code:
    #include "i2s_32_quad.h"
    #include "usb_audio.h"
    
    #define SEL 0
    
    I2S_32_QUAD acq;
    AudioOutputUSB  usb;
    #if SEL==0
    AudioConnection patchCord1(acq,0,usb,0);
    AudioConnection patchCord2(acq,1,usb,1);
    #else
    AudioConnection patchCord1(acq,2,usb,0);
    AudioConnection patchCord2(acq,3,usb,1);
    #endif
    
    void setup() {
      // put your setup code here, to run once:
       AudioMemory(8);
      acq.digitalShift(12);
    }
    
    void loop() {
      // put your main code here, to run repeatedly:
    
    }
    file "i2s_32_quad.h" in same sketch directory as above ino file
    Code:
    #ifndef I2S_32_QUAD_H
    #define I2S_32_QUAD_H
    
    #include "kinetis.h"
    #include "core_pins.h"
    
    #include "AudioStream.h"
    #include "DMAChannel.h"
    
    #define NCH 4
    
    class I2S_32_QUAD : public AudioStream
    {
    public:
    
      I2S_32_QUAD(void) : AudioStream(0, NULL) {begin();}
      void begin(void);
      virtual void update(void);
      void digitalShift(int16_t val){I2S_32_QUAD::shift=val;}
      
    protected:  
      static bool update_responsibility;
      static DMAChannel dma;
      static void isr32(void);
      
    private:
      static int16_t shift;
      static audio_block_t *block_incoming[NCH];
    
      void config_i2s(void);
    };
    
    DMAMEM static uint32_t i2s_rx_buffer_32[2*NCH*AUDIO_BLOCK_SAMPLES];
    
    audio_block_t *I2S_32_QUAD::block_incoming[NCH] = { NULL, NULL, NULL, NULL};
    bool I2S_32_QUAD::update_responsibility = false;
    DMAChannel I2S_32_QUAD::dma(false);
    int16_t I2S_32_QUAD::shift=8; //8 shifts 24 bit data to LSB
    
    void I2S_32_QUAD::begin(void)
    { 
    
      dma.begin(true); // Allocate the DMA channel first
    
      config_i2s();
    
      CORE_PIN13_CONFIG = PORT_PCR_MUX(4); // pin 13, PTC5, I2S0_RXD0
    #ifdef __MK20DX256__
      CORE_PIN30_CONFIG = PORT_PCR_MUX(4); // pin 30, PTC11,I2S0_RXD1
    #endif
    #ifdef __MK66FX1M0__
      CORE_PIN38_CONFIG = PORT_PCR_MUX(4); // pin 38, PTC11,I2S0_RXD1
    #endif
      
      dma.TCD->SADDR = (void *)((uint32_t)&I2S0_RDR0);
      dma.TCD->SOFF = 0;
      dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2);
      dma.TCD->NBYTES_MLNO = (2*4);
      dma.TCD->SLAST = 0;
      dma.TCD->DADDR = i2s_rx_buffer_32;
      dma.TCD->DOFF = 4;
      dma.TCD->CITER_ELINKNO = sizeof(i2s_rx_buffer_32) / (2*4);
      dma.TCD->DLASTSGA = -sizeof(i2s_rx_buffer_32);
      dma.TCD->BITER_ELINKNO = sizeof(i2s_rx_buffer_32) / (2*4);
      dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    
      dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_RX);
      update_responsibility = update_setup();
      dma.enable();
    
      I2S0_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR;
      I2S0_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE; // TX clock enable, because sync'd to TX
    
      dma.attachInterrupt(isr32); 
    }
    
    void I2S_32_QUAD::isr32(void)
    {
    
      uint32_t daddr;
      uint32_t *src;
    
      daddr = (uint32_t)(dma.TCD->DADDR);
      dma.clearInterrupt();
    
      if (daddr < (uint32_t)&i2s_rx_buffer_32[AUDIO_BLOCK_SAMPLES*NCH])
      { // DMA is receiving to the first half of the buffer
        // need to remove data from the second half
        src = &i2s_rx_buffer_32[AUDIO_BLOCK_SAMPLES*NCH];
      }
      else 
      { // DMA is receiving to the second half of the buffer
        // need to remove data from the first half
        src = &i2s_rx_buffer_32[0];
      }
      if (block_incoming[0] != NULL) 
      {
        for(int ii=0;ii<AUDIO_BLOCK_SAMPLES;ii++)
        {
          { block_incoming[0]->data[ii] = (int16_t) (*(src)>>I2S_32_QUAD::shift); src++;}
          { block_incoming[2]->data[ii] = (int16_t) (*(src)>>I2S_32_QUAD::shift); src++;}
          { block_incoming[1]->data[ii] = (int16_t) (*(src)>>I2S_32_QUAD::shift); src++;}
          { block_incoming[3]->data[ii] = (int16_t) (*(src)>>I2S_32_QUAD::shift); src++;}
        }
      }
      if (update_responsibility) update_all();
      
    }
    void I2S_32_QUAD::update(void)
    {
      unsigned int ii, jj;
      audio_block_t *new_block[NCH];
      audio_block_t *out_block[NCH];
    
      // allocate NCH new blocks.  If any fails, allocate none
      for (ii=0; ii < NCH; ii++) {
        new_block[ii] = allocate();
        if (new_block[ii] == NULL) {
          for (jj=0; jj < ii; jj++) {
            release(new_block[jj]);
          }
          memset(new_block, 0, sizeof(new_block));
          break;
        }
      }
    }
    
    // MCLK needs to be 48e6 / 1088 * 256 = 11.29411765 MHz -> 44.117647 kHz sample rate
    //
    #if F_CPU == 96000000 || F_CPU == 48000000 || F_CPU == 24000000
      // PLL is at 96 MHz in these modes
      #define MCLK_MULT 2
      #define MCLK_DIV  17
    #elif F_CPU == 72000000
      #define MCLK_MULT 8
      #define MCLK_DIV  51
    #elif F_CPU == 120000000
      #define MCLK_MULT 8
      #define MCLK_DIV  85
    #elif F_CPU == 144000000
      #define MCLK_MULT 4
      #define MCLK_DIV  51
    #elif F_CPU == 168000000
      #define MCLK_MULT 8
      #define MCLK_DIV  119
    #elif F_CPU == 180000000
      #define MCLK_MULT 16
      #define MCLK_DIV  255
      #define MCLK_SRC  0
    #elif F_CPU == 192000000
      #define MCLK_MULT 1
      #define MCLK_DIV  17
    #elif F_CPU == 216000000
      #define MCLK_MULT 8
      #define MCLK_DIV  153
      #define MCLK_SRC  0
    #elif F_CPU == 240000000
      #define MCLK_MULT 4
      #define MCLK_DIV  85
    #elif F_CPU == 16000000
      #define MCLK_MULT 12
      #define MCLK_DIV  17
    #else
      #error "This CPU Clock Speed is not supported by the Audio library";
    #endif
    
    #ifndef MCLK_SRC
    #if F_CPU >= 20000000
      #define MCLK_SRC  3  // the PLL
    #else
      #define MCLK_SRC  0  // system clock
    #endif
    #endif
    
    void I2S_32_QUAD::config_i2s(void)
    {
      SIM_SCGC6 |= SIM_SCGC6_I2S;
      SIM_SCGC7 |= SIM_SCGC7_DMA;
      SIM_SCGC6 |= SIM_SCGC6_DMAMUX;
    
      // if either transmitter or receiver is enabled, do nothing
      if (I2S0_TCSR & I2S_TCSR_TE) return;
      if (I2S0_RCSR & I2S_RCSR_RE) return;
    
      // enable MCLK output
      I2S0_MCR = I2S_MCR_MICS(MCLK_SRC) | I2S_MCR_MOE;
      while (I2S0_MCR & I2S_MCR_DUF) ;
      I2S0_MDR = I2S_MDR_FRACT((MCLK_MULT-1)) | I2S_MDR_DIVIDE((MCLK_DIV-1));
    
      // configure transmitter
      I2S0_TMR = 0;
      I2S0_TCR1 = I2S_TCR1_TFW(1);  // watermark at half fifo size
      I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1)
        | I2S_TCR2_BCD | I2S_TCR2_DIV(1);
      I2S0_TCR3 = I2S_TCR3_TCE_2CH; // dual tx channel
      I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF
        | I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_TCR4_FSD;
      I2S0_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31);
    
      // configure receiver (sync'd to transmitter clocks)
      I2S0_RMR = 0;
      I2S0_RCR1 = I2S_RCR1_RFW(1);
      I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP | I2S_RCR2_MSEL(1)
        | I2S_RCR2_BCD | I2S_RCR2_DIV(1);
      I2S0_RCR3 = I2S_RCR3_RCE_2CH;
      I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF
        | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD;
      I2S0_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31);
    
      // configure pin mux for 3 clock signals
      CORE_PIN23_CONFIG = PORT_PCR_MUX(6); // pin 23, PTC2, I2S0_TX_FS (LRCLK)
      CORE_PIN9_CONFIG  = PORT_PCR_MUX(6); // pin  9, PTC3, I2S0_TX_BCLK
      CORE_PIN11_CONFIG = PORT_PCR_MUX(6); // pin 11, PTC6, I2S0_MCLK
    }
    
    
    #endif
    compile with Audio or Serial + Midi + Audio
    Caveat: U took from other code, made it only to compile, but have not tested.

  9. #9
    Junior Member
    Join Date
    Jul 2018
    Posts
    5
    Thank you, this is very helpful.

    I'm still not 100% sure of everything that is going on in these I2S drivers.

    Code:
      dma.TCD->SADDR = (void *)((uint32_t)&I2S0_RDR0);
      dma.TCD->SOFF = 0;
      dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2);
      dma.TCD->NBYTES_MLNO = (2*4);
      dma.TCD->SLAST = 0;
      dma.TCD->DADDR = i2s_rx_buffer_32;
      dma.TCD->DOFF = 4;
      dma.TCD->CITER_ELINKNO = sizeof(i2s_rx_buffer_32) / (2*4);
      dma.TCD->DLASTSGA = -sizeof(i2s_rx_buffer_32);
      dma.TCD->BITER_ELINKNO = sizeof(i2s_rx_buffer_32) / (2*4);
      dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    But with these registers, are you essentially doubling the number of bits that are transferred per MCLK?

    I think for now I'm going to just sacrifice some features and use the ADC/DAC objects. But I will definitely come back to this.

  10. #10
    Senior Member
    Join Date
    Jul 2014
    Posts
    1,912
    Quote Originally Posted by OlleyStone View Post
    Thank you, this is very helpful.

    I'm still not 100% sure of everything that is going on in these I2S drivers.

    Code:
      dma.TCD->SADDR = (void *)((uint32_t)&I2S0_RDR0);
      dma.TCD->SOFF = 0;
      dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2);
      dma.TCD->NBYTES_MLNO = (2*4);
      dma.TCD->SLAST = 0;
      dma.TCD->DADDR = i2s_rx_buffer_32;
      dma.TCD->DOFF = 4;
      dma.TCD->CITER_ELINKNO = sizeof(i2s_rx_buffer_32) / (2*4);
      dma.TCD->DLASTSGA = -sizeof(i2s_rx_buffer_32);
      dma.TCD->BITER_ELINKNO = sizeof(i2s_rx_buffer_32) / (2*4);
      dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    But with these registers, are you essentially doubling the number of bits that are transferred per MCLK?

    I think for now I'm going to just sacrifice some features and use the ADC/DAC objects. But I will definitely come back to this.
    The two channels RDX0 and RDX1 are adjacent addresses
    so what you do is to tell DMA to read 8 bytes and then jump back to start (called minor loop) and to repeat this sizeofbuffer/8 times (major loop)

    The best is to take this code, compare it to say what Paul is doing for both the stereo and quad I2S and look into the reference manual, what these numbers mean for the processor.

  11. #11
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    18,511
    Quote Originally Posted by OlleyStone View Post
    I think for now I'm going to just sacrifice some features and use the ADC/DAC objects. But I will definitely come back to this.
    Just curious, did everything work out with the internal ADC & DAC?

Posting Permissions

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