I figured it out. In the end, I had to add the set_audioClock() function and 1-2 other minor mods.
Here's my complete sketch, tested on T4.1. I changed the sound to a square wave sweep, which is generated inside the ISR.
Code:
#include <Audio.h>
#include <Wire.h>
// Set up codec and DMA channel
AudioControlSGTL5000 sgtl5000_1;
DMAMEM __attribute__((aligned(32))) static uint16_t myi2s_tx_buffer[2] = {0};
static DMAChannel CodecDAC_dma;
// Audio square wave synth variables
int8_t sign = 1; // Sign of current sample (+ or -)
uint32_t signCounter = 0; // How many samples since last sign flip
uint32_t Threshold = 441; // Sign flip threshold (at 44.1kHz, 441 = 50Hz square wave)
boolean readyForNewSample = true; // Push new data on every second call to the interrupt service routine
void setup()
{
CodecDAC_begin();
sgtl5000_1.enable(); // sets up the CODEC chip
sgtl5000_1.volume(.4); // Half volume for a waveform without saturation
}
void loop() {
}
FLASHMEM static 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) // 2: 1/4; 1: 1/2; 0: 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
}
void CodecDAC_config_i2s(void)
{
CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON); //enables SAI1 clock in CCM_CCGR5 register
//PLL:
int fs = AUDIO_SAMPLE_RATE_EXACT;
// PLL between 27*24 = 648MHz und 54*24=1296MHz
int n1 = 4; //SAI prescaler 4 => (n1*n2) = multiple of 4
int n2 = 1 + (24000000 * 27) / (fs * 256 * n1);
double C = ((double)fs * 256 * n1 * n2) / 24000000;
int c0 = C;
int c2 = 10000;
int c1 = C * c2 - (c0 * c2);
set_audioClock(c0, c1, c2, false);
// 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; first part clears all bits except SAI1_CLK_SEL; second part choosing PLL4 currently
CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI1_CLK_PRED_MASK | CCM_CS1CDR_SAI1_CLK_PODF_MASK))
| CCM_CS1CDR_SAI1_CLK_PRED(n1-1) // &0x07
| CCM_CS1CDR_SAI1_CLK_PODF(n2-1); // &0x3f
// Select MCLK
IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 // master clock is an output and something else?
& ~(IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL_MASK))
| (IOMUXC_GPR_GPR1_SAI1_MCLK_DIR | IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL(0));
CORE_PIN23_CONFIG = 3; //1:MCLK
CORE_PIN21_CONFIG = 3; //1:RX_BCLK
CORE_PIN20_CONFIG = 3; //1:RX_SYNC
int rsync = 0;
int tsync = 1;
I2S1_TMR = 0; // no masking
//I2S1_TCSR = (1<<25); //Reset
I2S1_TCR1 = I2S_TCR1_RFW(1);
I2S1_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP // sync=0; tx is async;
| (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1));
I2S1_TCR3 = I2S_TCR3_TCE;
I2S1_TCR4 = I2S_TCR4_FRSZ((2-1)) | I2S_TCR4_SYWD((32-1)) | I2S_TCR4_MF
| I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP;
I2S1_TCR5 = I2S_TCR5_WNW((32-1)) | I2S_TCR5_W0W((32-1)) | I2S_TCR5_FBT((32-1));
I2S1_RMR = 0;
//I2S1_RCSR = (1<<25); //Reset
I2S1_RCR1 = I2S_RCR1_RFW(1);
I2S1_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP // sync=0; rx is async;
| (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1));
I2S1_RCR3 = I2S_RCR3_RCE;
I2S1_RCR4 = I2S_RCR4_FRSZ((2-1)) | I2S_RCR4_SYWD((32-1)) | I2S_RCR4_MF
| I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD;
I2S1_RCR5 = I2S_RCR5_WNW((32-1)) | I2S_RCR5_W0W((32-1)) | I2S_RCR5_FBT((32-1));
}
void CodecDAC_begin(void)
{
CodecDAC_dma.begin(true); // Allocate the DMA channel first
CodecDAC_config_i2s();
CORE_PIN7_CONFIG = 3; //1:TX_DATA0 pin 7 on uP
CodecDAC_dma.TCD->SADDR = myi2s_tx_buffer; //source address
CodecDAC_dma.TCD->SOFF = 2; // source buffer address increment per transfer in bytes
CodecDAC_dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); // specifies 16 bit source and destination
CodecDAC_dma.TCD->NBYTES_MLNO = 2; // bytes to transfer for each service request///////////////////////////////////////////////////////////////////
CodecDAC_dma.TCD->SLAST = -sizeof(myi2s_tx_buffer); // last source address adjustment
CodecDAC_dma.TCD->DOFF = 0; // increments at destination
CodecDAC_dma.TCD->CITER_ELINKNO = sizeof(myi2s_tx_buffer) / 2;
CodecDAC_dma.TCD->DLASTSGA = 0; // destination address offset
CodecDAC_dma.TCD->BITER_ELINKNO = sizeof(myi2s_tx_buffer) / 2;
CodecDAC_dma.TCD->CSR = DMA_TCD_CSR_INTHALF; //| DMA_TCD_CSR_INTMAJOR; // enables interrupt when transfers half complete. SET TO 0 to disable DMA interrupts
CodecDAC_dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2); // I2S1 register DMA writes to
CodecDAC_dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX); // i2s channel that will trigger the DMA transfer when ready for data
CodecDAC_dma.enable();
I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE;
I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE;
CodecDAC_dma.attachInterrupt(CodecDAC_isr);
}
void CodecDAC_isr(void) // DMA interrupt, called twice per sample. Audio data is pushed into the DMA channel source array on every second call.
{
if (readyForNewSample) {
// Compute square waveform
signCounter++;
if (signCounter == Threshold) {
sign *= -1;
signCounter = 0;
Threshold += 1;
}
if (Threshold == 441) {
Threshold = 44;
}
int16_t sineVal = sign*32767;
// Pass current sample to L+R audio buffers
myi2s_tx_buffer[0] = sineVal; // Left Channel
myi2s_tx_buffer[1] = sineVal; // Right Channel
// Flush buffer and clear interrupt
arm_dcache_flush_delete(myi2s_tx_buffer, sizeof(myi2s_tx_buffer));
CodecDAC_dma.clearInterrupt();
}
readyForNewSample = 1-readyForNewSample;
}