230 lines
6.9 KiB
C++
230 lines
6.9 KiB
C++
// https://github.com/Jean-MarcHarvengt/VGA_t4
|
|
// https://forum.pjrc.com/threads/63243-Writing-Directly-to-SGTL5000-CODEC-DACs
|
|
// https://community.nxp.com/t5/Kinetis-Microcontrollers/is-there-any-demo-code-for-using-I2S/m-p/196195/highlight/true
|
|
// https://github.com/PaulStoffregen/Audio/blob/99b9472afd24bea13efc799742c0ea432ef2303a/output_i2s.cpp
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// OLD
|
|
// TODO: adjust to match I2S2.h
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define USE_DMA
|
|
|
|
#ifdef USE_DMA
|
|
#include <DMAChannel.h>
|
|
static DMAChannel dma;
|
|
#endif
|
|
|
|
#define SAMPLES_PER_BUFFER 1024
|
|
#define NUM_BUFFERS 4
|
|
|
|
static DMAMEM __attribute__((aligned(32))) int16_t audioBuffer[SAMPLES_PER_BUFFER*NUM_BUFFERS];
|
|
static volatile uint8_t freeBuffers = NUM_BUFFERS;
|
|
static volatile uint8_t curBuffer = 0;
|
|
|
|
template <int CHANNELS, int SAMPLE_RATE_HZ> class I2S {
|
|
|
|
static constexpr const char* NAME = "I2S";
|
|
|
|
|
|
|
|
public:
|
|
|
|
I2S() {
|
|
log_d(NAME, "ctor()");
|
|
//config_sai1();
|
|
}
|
|
|
|
void start() {
|
|
|
|
log_d(NAME, "start()");
|
|
|
|
// clear
|
|
//arm_dcache_flush_delete(txBuffer, txBufferSize); // ??
|
|
|
|
config_sai1();
|
|
|
|
#ifdef USE_DMA
|
|
dma.begin(true); // Allocate the DMA channel first
|
|
dma.TCD->SADDR = audioBuffer; //source address
|
|
dma.TCD->SOFF = 2; // source buffer address increment per transfer in bytes
|
|
dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); // specifies 16 bit source and destination
|
|
dma.TCD->NBYTES_MLNO = 2; // bytes to transfer for each service request///////////////////////////////////////////////////////////////////
|
|
dma.TCD->SLAST = -sizeof(audioBuffer); // last source address adjustment
|
|
dma.TCD->DOFF = 0; // increments at destination
|
|
dma.TCD->CITER_ELINKNO = sizeof(audioBuffer) / 2;
|
|
dma.TCD->DLASTSGA = 0; // destination address offset
|
|
dma.TCD->BITER_ELINKNO = sizeof(audioBuffer) / 2;
|
|
dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; // enables interrupt when transfers half complete. SET TO 0 to disable DMA interrupts
|
|
dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2); // I2S1 register DMA writes to
|
|
dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX); // i2s channel that will trigger the DMA transfer when ready for data
|
|
dma.enable();
|
|
dma.attachInterrupt(isrDMA);
|
|
#else
|
|
attachInterruptVector(IRQ_SAI1, isrAudio);
|
|
NVIC_ENABLE_IRQ(IRQ_SAI1);
|
|
NVIC_SET_PRIORITY(IRQ_SAI1, 127);
|
|
I2S1_TCSR |= 1<<8; // start generating TX FIFO interrupts
|
|
#endif
|
|
|
|
|
|
|
|
pinMode(13, OUTPUT);
|
|
|
|
}
|
|
|
|
bool add(int16_t* samples) {
|
|
#ifdef USE_DMA
|
|
if (freeBuffers == 0) {return false;}
|
|
--freeBuffers;
|
|
int16_t* dst = &audioBuffer[curBuffer * SAMPLES_PER_BUFFER];
|
|
memcpy(dst, samples, SAMPLES_PER_BUFFER*sizeof(int16_t));
|
|
arm_dcache_flush_delete(dst, SAMPLES_PER_BUFFER*sizeof(int16_t));
|
|
curBuffer = (curBuffer + 1) % NUM_BUFFERS;
|
|
return true;
|
|
#else
|
|
//return audioBuffer.add(samples);
|
|
#endif
|
|
}
|
|
|
|
|
|
private:
|
|
|
|
//uint16_t pos = 0;
|
|
|
|
FASTRUN static void isrDMA() {
|
|
dma.clearInterrupt();
|
|
if (freeBuffers < NUM_BUFFERS) {++freeBuffers;}
|
|
|
|
}
|
|
|
|
/*
|
|
// interrupt service routine
|
|
FASTRUN static void isrAudio() {
|
|
|
|
|
|
static constexpr volatile uint16_t* txReg = (uint16_t *)((uint32_t)&I2S1_TDR0 + 2);
|
|
//static constexpr uint16_t* txReg = (uint16_t *)((uint32_t)&I2S1_TDR0 );
|
|
|
|
if (CHANNELS == 2) {
|
|
const int16_t sample1 = audioBuffer.getSample();
|
|
*txReg = sample1;
|
|
const int16_t sample2 = audioBuffer.getSample();
|
|
*txReg = sample2;
|
|
} else if (CHANNELS == 1) {
|
|
const int16_t sample1 = audioBuffer.getSample();
|
|
*txReg = sample1;
|
|
*txReg = sample1;
|
|
}
|
|
|
|
//arm_dcache_flush_delete(txBuffer, txBufferSize);
|
|
//digitalWriteFast(13, (pos / 2048) & 1);
|
|
|
|
}
|
|
*/
|
|
|
|
|
|
private:
|
|
|
|
FLASHMEM static void config_sai1() {
|
|
|
|
CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON);
|
|
|
|
double fs = SAMPLE_RATE_HZ;
|
|
// 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 = (fs * 256 * n1 * n2) / 24000000;
|
|
int c0 = C;
|
|
int c2 = 10000;
|
|
int c1 = C * c2 - (c0 * c2);
|
|
setAudioClock(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
|
|
|
|
//n1 = n1 / 2; //Double Speed for TDM
|
|
|
|
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
|
|
|
|
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 transmitter
|
|
const int rsync = 0;
|
|
const int tsync = 1;
|
|
|
|
uint16_t by = 32; // ??
|
|
|
|
// configure transmitter
|
|
I2S1_TMR = 0;
|
|
I2S1_TCR1 = I2S_TCR1_RFW(1);
|
|
I2S1_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1)); // sync=0; tx is async;
|
|
I2S1_TCR3 = I2S_TCR3_TCE;
|
|
I2S1_TCR4 = I2S_TCR4_FRSZ((2-1)) | I2S_TCR4_SYWD((by-1)) | I2S_TCR4_MF | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP;
|
|
I2S1_TCR5 = I2S_TCR5_WNW((by-1)) | I2S_TCR5_W0W((by-1)) | I2S_TCR5_FBT((by-1));
|
|
|
|
I2S1_RMR = 0;
|
|
I2S1_RCR1 = I2S_RCR1_RFW(1);
|
|
I2S1_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1)); // sync=0; rx is async;
|
|
I2S1_RCR3 = I2S_RCR3_RCE;
|
|
I2S1_RCR4 = I2S_RCR4_FRSZ((2-1)) | I2S_RCR4_SYWD((by-1)) | I2S_RCR4_MF | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD;
|
|
I2S1_RCR5 = I2S_RCR5_WNW((by-1)) | I2S_RCR5_W0W((by-1)) | I2S_RCR5_FBT((by-1));
|
|
|
|
//CORE_PIN23_CONFIG = 3; // MCLK
|
|
CORE_PIN21_CONFIG = 3; // RX_BCLK
|
|
CORE_PIN20_CONFIG = 3; // RX_SYNC
|
|
CORE_PIN7_CONFIG = 3; // TX_DATA0
|
|
I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE;
|
|
|
|
#ifdef USE_DMA
|
|
I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE;
|
|
#else
|
|
I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE;
|
|
#endif
|
|
|
|
}
|
|
|
|
FLASHMEM static void setAudioClock(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
|
|
|
|
}
|
|
|
|
};
|