#pragma once // 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 #include "../../Debug.h" #define I2S2_USE_DMA #ifdef I2S2_USE_DMA #include #endif // the buffer has a size of X samples, // and we receive an interrupt whenever ONE HALF was transmitted // 1152 = MP3 decode size, *2 (stereo), *2 (2 buffer halfs) static constexpr const uint32_t I2S2_BUFFER_SAMPLES = 1152*2*2; #include "I2SBase.h" class I2S2 { private: static constexpr const char* NAME = "I2S2"; /** the actual PCM data buffer */ static DMAMEM __attribute__((aligned(32))) int16_t buffer[I2S2_BUFFER_SAMPLES]; /** helper class to map one half of above buffer */ struct BufferHalf { int16_t* mem; volatile uint16_t samplesUsed = 0; uint16_t samplesFree() const {return I2S2_BUFFER_SAMPLES/2 - samplesUsed;} BufferHalf(int16_t* mem) : mem(mem) {} bool isFilled() const {return samplesFree() == 0;} void dropCache() {arm_dcache_flush_delete(mem, I2S2_BUFFER_SAMPLES/2*sizeof(int16_t));} }; struct State { #ifdef I2S2_USE_DMA DMAChannel dma; volatile uint8_t transmittingHalf = 0; BufferHalf bufferHalf[2] = { BufferHalf(&buffer[0]), BufferHalf(&buffer[I2S2_BUFFER_SAMPLES/2]), }; /** the current half was transmitted, switch to the next one */ void halfTransmitted() { bufferHalf[transmittingHalf].samplesUsed = 0; //memset(bufferHalf[transmittingHalf].mem, 0, I2S2_BUFFER_SAMPLES/2*sizeof(int16_t)); transmittingHalf = (transmittingHalf + 1) % 2; bufferHalf[transmittingHalf].dropCache(); } /** the BufferHalf we are currently filling when adding new data */ BufferHalf& fillingHalf() { return bufferHalf[(transmittingHalf+1)%2]; } #else // TODO #endif }; static State state; public: /** start the i2s transmission with the given values */ static void setup(uint8_t channels, uint32_t sampleRate_hz) { Log::addInfo(NAME, "start(%d, %d)", channels, sampleRate_hz); // zero out the audio data buffer clearBuffer(); // configure the I2S unit config_sai2(sampleRate_hz); #ifdef I2S2_USE_DMA state.dma.begin(true); // Allocate the DMA channel first state.dma.TCD->SADDR = buffer; // source address state.dma.TCD->SOFF = 2; // source buffer address increment per transfer in bytes state.dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); // specifies 16 bit source and destination state.dma.TCD->NBYTES_MLNO = 2; // bytes to transfer for each service request/////////////////////////////////////////////////////////////////// state.dma.TCD->SLAST = -sizeof(buffer); // last source address adjustment state.dma.TCD->DOFF = 0; // increments at destination state.dma.TCD->CITER_ELINKNO = sizeof(buffer) / 2; state.dma.TCD->DLASTSGA = 0; // destination address offset state.dma.TCD->BITER_ELINKNO = sizeof(buffer) / 2; state.dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; // enables interrupt when transfers half complete. SET TO 0 to disable DMA interrupts state.dma.TCD->DADDR = (void *)((uint32_t)&I2S2_TDR0 + 2); // I2S2 register DMA writes to state.dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI2_TX); // i2s channel that will trigger the DMA transfer when ready for data state.dma.enable(); state.dma.attachInterrupt(I2S2::isrDMA); Log::addInfo(NAME, "DMA configured"); #else attachInterruptVector(IRQ_SAI2, isrAudio); NVIC_ENABLE_IRQ(IRQ_SAI2); NVIC_SET_PRIORITY(IRQ_SAI2, 127); I2S2_TCSR |= 1<<8; // start generating TX FIFO interrupts #endif } /** zero-out the audio buffer */ static void clearBuffer() { memset(buffer, 0, sizeof(buffer)); state.bufferHalf[0].samplesUsed = 0; state.bufferHalf[1].samplesUsed = 0; } /** block until the given number of samples were added to the internal buffer */ static void addBlocking(const int16_t* pcm, uint16_t numSamples) { while(numSamples) { const uint32_t samplesAdded = addNonBlocking(pcm, numSamples); numSamples -= samplesAdded; pcm += samplesAdded; } } /** add the given number of samples to the internal buffer, returns the number of actually added samples, dependent on how much space there was */ static uint16_t addNonBlocking(const int16_t* pcm, uint16_t numSamples) { #ifdef I2S2_USE_DMA BufferHalf& bh = state.fillingHalf(); const uint16_t addable = min(numSamples, bh.samplesFree()); uint8_t* dst = (uint8_t*) (&bh.mem[bh.samplesUsed]); uint16_t numBytes = addable * sizeof(int16_t); memcpy(dst, pcm, numBytes); bh.samplesUsed += addable; return addable; #else // TODO, NOT YET IMPLEMENTED if (freeEntries < SAMPLES_PER_BUFFER) {return false;} //int16_t* dst = &audioBuffer[bufHead]; //memcpy(dst, samples, SAMPLES_PER_BUFFER*sizeof(int16_t)); //bufHead = (bufHead + SAMPLES_PER_BUFFER) % NUM_ENTRIES; //curBuffer = (curBuffer + 1) % NUM_BUFFERS; for (uint16_t i = 0; i < SAMPLES_PER_BUFFER; ++i) { audioBuffer[bufHead] = samples[i]; bufHead = (bufHead + 1) % NUM_ENTRIES; } freeEntries -= SAMPLES_PER_BUFFER; arm_dcache_flush_delete(audioBuffer, NUM_ENTRIES*sizeof(int16_t)); return true; #endif } private: #ifdef I2S2_USE_DMA FASTRUN static void isrDMA() { // one buffer half processed -> next state.dma.clearInterrupt(); state.halfTransmitted(); } #else // interrupt service routine static void isrAudio() { // TODO, not yet implemented static constexpr volatile uint16_t* txReg = (uint16_t *)((uint32_t)&I2S2_TDR0 + 2); //static constexpr uint16_t* txReg = (uint16_t *)((uint32_t)&I2S2_TDR0 ); //if (CHANNELS == 2) { const uint16_t sample1 = audioBuffer[bufTail]; //*txReg = sample1; bufTail = (bufTail + 1) & (NUM_ENTRIES-1); if (freeEntries < NUM_ENTRIES) {++freeEntries;} *txReg = sample1; //} } #endif private: static void config_sai2(uint32_t sampleRate_hz) { CCM_CCGR5 |= CCM_CCGR5_SAI2(CCM_CCGR_ON); double fs = sampleRate_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 SAI2_CLK register locations CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI2_CLK_SEL_MASK)) | CCM_CSCMR1_SAI2_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4 //n1 = n1 / 2; //Double Speed for TDM CCM_CS2CDR = (CCM_CS2CDR & ~(CCM_CS2CDR_SAI2_CLK_PRED_MASK | CCM_CS2CDR_SAI2_CLK_PODF_MASK)) | CCM_CS2CDR_SAI2_CLK_PRED(n1 - 1) | CCM_CS2CDR_SAI2_CLK_PODF(n2 - 1); // kazu: OK WITHOUT?? IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 & ~(IOMUXC_GPR_GPR1_SAI2_MCLK3_SEL_MASK)) | (IOMUXC_GPR_GPR1_SAI2_MCLK_DIR | IOMUXC_GPR_GPR1_SAI2_MCLK3_SEL(0)); //Select MCLK // configure transmitter const int rsync = 0; const int tsync = 1; uint16_t by = 32; // ?? // configure transmitter I2S2_TMR = 0; I2S2_TCR1 = I2S_TCR1_RFW(1); I2S2_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1)); // sync=0; tx is async; I2S2_TCR3 = I2S_TCR3_TCE; I2S2_TCR4 = I2S_TCR4_FRSZ((2-1)) | I2S_TCR4_SYWD((by-1)) | I2S_TCR4_MF | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP; I2S2_TCR5 = I2S_TCR5_WNW((by-1)) | I2S_TCR5_W0W((by-1)) | I2S_TCR5_FBT((by-1)); // page 1995 I2S2_RMR = 0; I2S2_RCR1 = I2S_RCR1_RFW(1); I2S2_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1)); // sync=0; rx is async; I2S2_RCR3 = I2S_RCR3_RCE; I2S2_RCR4 = I2S_RCR4_FRSZ((2-1)) | I2S_RCR4_SYWD((by-1)) | I2S_RCR4_MF | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; I2S2_RCR5 = I2S_RCR5_WNW((by-1)) | I2S_RCR5_W0W((by-1)) | I2S_RCR5_FBT((by-1)); // configure pins (2,3,4) to their I2S functionality CORE_PIN4_CONFIG = 2; // RX_BCLK CORE_PIN3_CONFIG = 2; // RX_SYNC (left/right) CORE_PIN2_CONFIG = 2; // TX_DATA0 I2S2_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; #ifdef I2S2_USE_DMA I2S2_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; // | I2S_TCSR_FR ??? #else I2S2_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 } }; // init static class members #ifdef I2S2_IMPLEMENTATION DMAMEM __attribute__((aligned(32))) int16_t I2S2::buffer[I2S2_BUFFER_SAMPLES]; I2S2::State I2S2::state; #endif