I2S Input Quad BCK Query

Status
Not open for further replies.
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-SPDIF-Input-and-Output
 

Attachments

  • FMP_Final.zip
    759.6 KB · Views: 81
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....
 
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.
 
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
 
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?
 
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.
 
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?
 
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.
 
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.
 
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.
 
@WMXZ that might be something I need. To get my thing working (https://forum.pjrc.com/threads/55963-Quad-channel-input-problem).

But it exactly does compile, but doesn't work. And it seems incomplete (comparing to the input_i2s_quad.cpp, there is at least transmit() call missing).
Do you by any chance have an updated version? Or perhaps you might be able to help fixing it or tell where to seek the knowledge...
 
Status
Not open for further replies.
Back
Top