The following code is a simple I2S input example that runs on T4.1.
It can be configured to have multi channel TDM input for ether 16 or 32 bit for (nearly) arbitrary sampling frequency.
It runs without Audio library and is only a demonstration on how it is or can be done.
Edit the lines marked with <<<<<
Obviously using the Audio library is shorter (but maybe less flexible) but here it comes
It can be configured to have multi channel TDM input for ether 16 or 32 bit for (nearly) arbitrary sampling frequency.
It runs without Audio library and is only a demonstration on how it is or can be done.
Edit the lines marked with <<<<<
Obviously using the Audio library is shorter (but maybe less flexible) but here it comes
Code:
#define FSAMP 96000
#define NSAMP 128
#define N_BITS 16 // <<<<< 16 or 32
#define N_WORD (N_BITS>>3)
#define N_SYNC 1 // <<<<<< 1 for TDM is equal to N_BITS for SAI protocol
// assume to have two audio channels with 3 TDM samples per channel
#define NPORT_I2S 2 // <<<<<< 1,2,3,4
#define NCHAN_I2S 3 // <<<<<<
#define NBUF_I2S (NPORT_I2S*NCHAN_I2S*NSAMP)
// assume we want all I2S channels
#define NCHAN_ACQ 6 // <<<<<<<
#define NBUF_ACQ (NCHAN_ACQ*NSAMP)
// assume we want to digitally shift data by 8 bits (e.g. to bring 24 MSB to LSB)
#define adc_shift 0 // <<<<<<< useful for 32 bit data transfer
// define the mapping between I2S and acq buffer (size of NCHA_ACQ)
#if NPORT_I2S == 1
static int i2sIndex[NCHAN_ACQ]={0,1,2,3,4,5};
#elif NPORT_I2S == 2
static int i2sIndex[NCHAN_ACQ]={0,2,4,1,3,5};
#elif NPORT_I2S == 3
static int i2sIndex[NCHAN_ACQ]={0,3,1,4,2,5};
#elif NPORT_I2S == 4
static int i2sIndex[NCHAN_ACQ]={0,1,2,3,4,5};
#endif
static void acq_isr(void);
#if N_BITS==16
typedef uint16_t data_t;
#elif N_BITS==32
typedef uint32_t data_t;
#endif
DMAMEM __attribute__((aligned(32)))
static data_t i2s_rx_buffer[2*NBUF_I2S];
static data_t acq_rx_buffer[2*NBUF_ACQ];
#ifndef I2S_DMA_PRIO
#define I2S_DMA_PRIO 5 // give DMA ISR a higher priority than default (8)
#endif
#include "DMAChannel.h"
static DMAChannel dma;
#if defined(__IMXRT1062__)
#define IMXRT_CACHE_ENABLED 2 // 0=disabled, 1=WT, 2= WB
PROGMEM
void set_audioClock(int nfact, int32_t nmult, uint32_t ndiv, bool force) // sets PLL4
{
if (!force && (CCM_ANALOG_PLL_AUDIO & CCM_ANALOG_PLL_AUDIO_ENABLE)) return;
CCM_ANALOG_PLL_AUDIO = CCM_ANALOG_PLL_AUDIO_BYPASS | CCM_ANALOG_PLL_AUDIO_ENABLE
| CCM_ANALOG_PLL_AUDIO_POST_DIV_SELECT(2) // 0: 1/4; 1: 1/2; 2: 1/1
| CCM_ANALOG_PLL_AUDIO_DIV_SELECT(nfact);
CCM_ANALOG_PLL_AUDIO_NUM = nmult & CCM_ANALOG_PLL_AUDIO_NUM_MASK;
CCM_ANALOG_PLL_AUDIO_DENOM = ndiv & CCM_ANALOG_PLL_AUDIO_DENOM_MASK;
CCM_ANALOG_PLL_AUDIO &= ~CCM_ANALOG_PLL_AUDIO_POWERDOWN;//Switch on PLL
while (!(CCM_ANALOG_PLL_AUDIO & CCM_ANALOG_PLL_AUDIO_LOCK)) {}; //Wait for pll-lock
const int div_post_pll = 1; // other values: 2,4
CCM_ANALOG_MISC2 &= ~(CCM_ANALOG_MISC2_DIV_MSB | CCM_ANALOG_MISC2_DIV_LSB);
if(div_post_pll>1) CCM_ANALOG_MISC2 |= CCM_ANALOG_MISC2_DIV_LSB;
if(div_post_pll>3) CCM_ANALOG_MISC2 |= CCM_ANALOG_MISC2_DIV_MSB;
CCM_ANALOG_PLL_AUDIO &= ~CCM_ANALOG_PLL_AUDIO_BYPASS; //Disable Bypass
Serial.printf("PLL %f\r\n",24.0f*((float)nfact+(float)nmult/(float)ndiv));
}
PROGMEM
void acq_init(int fsamp)
{
CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON);
// if either transmitter or receiver is enabled, do nothing
if (I2S1_RCSR & I2S_RCSR_RE) return;
//PLL:
int fs = fsamp;
int ovr = 2*(NCHAN_I2S*N_BITS);
// PLL between 27*24 = 648MHz und 54*24=1296MHz
int n0 = 26; // targeted PLL frequency (n0*24 MHz) n0>=27 && n0<54
int n1, n2;
do
{ n0++;
n1=0;
do
{ n1++;
n2 = 1 + (24'000'000 * n0) / (fs * ovr * n1);
} while ((n2>64) && (n1<=8));
} while ((n2>64 && n0<54));
Serial.printf("fs=%d, n1=%d, n2=%d, %d (>=27 && <54) ", fs, n1,n2,n1*n2*(fs/1000)*ovr/24000);
double C = ((double)fs * ovr * n1 * n2) / (24000000.0f);
Serial.printf(" C=%f\r\n",C);
int c0 = C;
int c2 = 10'000;
int c1 = C * c2 - (c0 * c2);
set_audioClock(c0, c1, c2, true);
// clear SAI1_CLK register locations
CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI1_CLK_SEL_MASK))
| CCM_CSCMR1_SAI1_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4
CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI1_CLK_PRED_MASK | CCM_CS1CDR_SAI1_CLK_PODF_MASK))
| CCM_CS1CDR_SAI1_CLK_PRED((n1-1)) // &0x07 // <8
| CCM_CS1CDR_SAI1_CLK_PODF((n2-1)); // &0x3f // <64
IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 & ~(IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL_MASK))
| (IOMUXC_GPR_GPR1_SAI1_MCLK_DIR | IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL(0)); //Select MCLK
// configure I2S
CORE_PIN23_CONFIG = 3; //1:MCLK // connected to GPIO0 not used
CORE_PIN21_CONFIG = 3; //1:RX_BCLK
CORE_PIN20_CONFIG = 3; //1:RX_SYNC
I2S1_RMR = 0;
I2S1_RCR1 = I2S_RCR1_RFW(4);
I2S1_RCR2 = I2S_RCR2_SYNC(0) | I2S_TCR2_BCP | I2S_RCR2_MSEL(1)
| I2S_RCR2_BCD | I2S_RCR2_DIV(0);
I2S1_RCR4 = I2S_RCR4_FRSZ((NCHAN_I2S-1)) | I2S_RCR4_SYWD(N_SYNC-1) | I2S_RCR4_MF
| I2S_RCR4_FSE | I2S_RCR4_FSD;
I2S1_RCR5 = I2S_RCR5_WNW(N_BITS-1) | I2S_RCR5_W0W(N_BITS-1) | I2S_RCR5_FBT(N_BITS-1);
// DMA
dma.TCD->SADDR = &I2S1_RDR0;
dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(N_WORD>>1) | DMA_TCD_ATTR_DSIZE(N_WORD>>1);
#if NPORT_I2S == 1
I2S1_RCR3 = I2S_RCR3_RCE;
CORE_PIN8_CONFIG = 3; //RX_DATA0
IOMUXC_SAI1_RX_DATA0_SELECT_INPUT = 2; // GPIO_B1_00_ALT3, pg 873
dma.TCD->SOFF = 0;
dma.TCD->NBYTES_MLOFFNO = N_WORD;
dma.TCD->SLAST = 0;
#elif NPORT_I2S == 2
I2S1_RCR3 = I2S_RCR3_RCE_2CH;
CORE_PIN8_CONFIG = 3; //RX_DATA0
CORE_PIN6_CONFIG = 3; //RX_DATA1
IOMUXC_SAI1_RX_DATA0_SELECT_INPUT = 2; // GPIO_B1_00_ALT3, pg 873
IOMUXC_SAI1_RX_DATA1_SELECT_INPUT = 1; // GPIO_B0_10_ALT3, pg 873
dma.TCD->SOFF = 4;
dma.TCD->NBYTES_MLOFFYES = DMA_TCD_NBYTES_SMLOE |
DMA_TCD_NBYTES_MLOFFYES_MLOFF(-NPORT_I2S*4) |
DMA_TCD_NBYTES_MLOFFYES_NBYTES(NPORT_I2S*N_WORD);
dma.TCD->SLAST = -NPORT_I2S*4;
#elif NPORT_I2S == 3
I2S1_RCR3 = I2S_RCR3_RCE_3CH;
CORE_PIN8_CONFIG = 3; //RX_DATA0
CORE_PIN6_CONFIG = 3; //RX_DATA1
CORE_PIN9_CONFIG = 3; //RX_DATA2
IOMUXC_SAI1_RX_DATA0_SELECT_INPUT = 2; // GPIO_B1_00_ALT3, pg 873
IOMUXC_SAI1_RX_DATA1_SELECT_INPUT = 1; // GPIO_B0_10_ALT3, pg 873
IOMUXC_SAI1_RX_DATA2_SELECT_INPUT = 1; // GPIO_B0_11_ALT3, pg 874
dma.TCD->SOFF = 4;
dma.TCD->NBYTES_MLOFFYES = DMA_TCD_NBYTES_SMLOE |
DMA_TCD_NBYTES_MLOFFYES_MLOFF(-NPORT_I2S*4) |
DMA_TCD_NBYTES_MLOFFYES_NBYTES(NPORT_I2S*N_WORD);
dma.TCD->SLAST = -NPORT_I2S*4;
#elif NPORT_I2S == 4
I2S1_RCR3 = I2S_RCR3_RCE_4CH;
CORE_PIN8_CONFIG = 3; //RX_DATA0
CORE_PIN6_CONFIG = 3; //RX_DATA1
CORE_PIN9_CONFIG = 3; //RX_DATA2
CORE_PIN32_CONFIG = 3; //RX_DATA3
IOMUXC_SAI1_RX_DATA0_SELECT_INPUT = 2; // GPIO_B1_00_ALT3, pg 873
IOMUXC_SAI1_RX_DATA1_SELECT_INPUT = 1; // GPIO_B0_10_ALT3, pg 873
IOMUXC_SAI1_RX_DATA2_SELECT_INPUT = 1; // GPIO_B0_11_ALT3, pg 874
IOMUXC_SAI1_RX_DATA3_SELECT_INPUT = 1; // GPIO_B0_12_ALT3, pg 875
dma.TCD->SOFF = 4;
dma.TCD->NBYTES_MLOFFYES = DMA_TCD_NBYTES_SMLOE |
DMA_TCD_NBYTES_MLOFFYES_MLOFF(-NPORT_I2S*4) |
DMA_TCD_NBYTES_MLOFFYES_NBYTES(NPORT_I2S*N_WORD);
dma.TCD->SLAST = -NPORT_I2S*4;
#endif
dma.TCD->DADDR = i2s_rx_buffer;
dma.TCD->DOFF = N_WORD;
dma.TCD->CITER_ELINKNO = dma.TCD->BITER_ELINKNO = 2*NBUF_I2S/NPORT_I2S;
dma.TCD->DLASTSGA = -sizeof(i2s_rx_buffer);
dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_RX);
dma.enable();
I2S1_RCSR = I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR;
dma.attachInterrupt(acq_isr, I2S_DMA_PRIO*16);
}
uint32_t acq_count=0;
void extract(void *out, void *inp);
void acq_isr(void)
{
uint32_t daddr;
data_t *src;
dma.clearInterrupt();
daddr = (uint32_t)(dma.TCD->DADDR);
acq_count++;
if (daddr < (uint32_t)(i2s_rx_buffer + sizeof(i2s_rx_buffer) / 2)) {
// DMA is receiving to the first half of the buffer
// need to remove data from the second half
src = &i2s_rx_buffer[NBUF_I2S];
} else {
// DMA is receiving to the second half of the buffer
// need to remove data from the first half
src = &i2s_rx_buffer[0];
}
#if IMXRT_CACHE_ENABLED >=1
arm_dcache_delete((void*)src, sizeof(i2s_rx_buffer) / 2);
#endif
extract((void *) acq_rx_buffer, (void *) src);
}
#endif
void extract(void *out, void *inp)
{ data_t *dout = (data_t *) out;
data_t *din = (data_t *) inp;
for(int ii=0; ii < NSAMP; ii++)
{
for(int jj=0; jj<NCHAN_ACQ; jj++)
{ data_t *dst=&dout[ii*NCHAN_ACQ];
data_t *src=&din[ii*NPORT_I2S*NCHAN_I2S];
dst[jj]=src[i2sIndex[jj]]>>adc_shift;
}
}
}
void setup() {
// put your setup code here, to run once:
while(!Serial);
Serial.print(" NPorts "); Serial.print(NPORT_I2S);
Serial.print("; NChan "); Serial.print(NCHAN_I2S);
Serial.print("; NBits "); Serial.print(N_BITS);
Serial.print("; NWord "); Serial.print(N_WORD);
Serial.println();
acq_init(FSAMP);
}
void loop() {
// put your main code here, to run repeatedly:
static uint32_t to=0;
if(millis()-to>1000)
{
Serial.println(acq_count);
acq_count=0;
to=millis();
}
}