diff --git a/io/teensy/I2S.h b/io/teensy/I2S.h index 6f02e39..0991540 100644 --- a/io/teensy/I2S.h +++ b/io/teensy/I2S.h @@ -3,7 +3,23 @@ // 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 "../base.h" + + + + + + + + +// OLD +// TODO: adjust to match I2S2.h + + + + + + + #define USE_DMA diff --git a/io/teensy/I2S2.h b/io/teensy/I2S2.h index c0dd027..e305b67 100644 --- a/io/teensy/I2S2.h +++ b/io/teensy/I2S2.h @@ -1,73 +1,120 @@ +#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 -#pragma once #include "../../Debug.h" -#define USE_DMA +#define I2S2_USE_DMA -//#define SAMPLES_PER_BUFFER 1024 -#define SAMPLES_PER_BUFFER 1152*2 // MP3 -#define NUM_BUFFERS 4 -#define NUM_ENTRIES (SAMPLES_PER_BUFFER * NUM_BUFFERS) - -#ifdef USE_DMA - #include - static DMAChannel dma; - 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; -#else - static uint16_t bufHead = 0; - static uint16_t bufTail = 0; - static int16_t audioBuffer[NUM_ENTRIES]; - static volatile uint16_t freeEntries = NUM_ENTRIES; +#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 { -class I2S2 { - private: static constexpr const char* NAME = "I2S2"; - /** hidden ctor, the class is all static */ - 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 start(uint8_t channels, uint32_t sampleRate_hz, uint32_t bufferSize) { - - Log::addInfo(NAME, "start(%d, %d, %d, %d)", channels, sampleRate_hz, bufferSize) - + static void setup(uint8_t channels, uint32_t sampleRate_hz) { - - config_sai2(); + Log::addInfo(NAME, "start(%d, %d)", channels, sampleRate_hz); - #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)&I2S2_TDR0 + 2); // I2S2 register DMA writes to - dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI2_TX); // i2s channel that will trigger the DMA transfer when ready for data - dma.enable(); - dma.attachInterrupt(isrDMA); + // 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); @@ -77,17 +124,36 @@ public: } - static bool add(int16_t* samples) { + /** zero-out the audio buffer */ + static void clearBuffer() { + memset(buffer, 0, sizeof(buffer)); + state.bufferHalf[0].samplesUsed = 0; + state.bufferHalf[1].samplesUsed = 0; + } - #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; + /** 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]; @@ -108,41 +174,35 @@ public: return true; #endif } - - - void reset() { - for (uint32_t i = 0; i < NUM_ENTRIES; ++i) {audioBuffer[i] = 0;} - arm_dcache_flush_delete(audioBuffer, sizeof(audioBuffer)); - } private: - - //uint16_t pos = 0; - #ifdef USE_DMA + #ifdef I2S2_USE_DMA FASTRUN static void isrDMA() { - dma.clearInterrupt(); - if (freeBuffers < NUM_BUFFERS) {++freeBuffers;} - + + // one buffer half processed -> next + state.dma.clearInterrupt(); + state.halfTransmitted(); + } #else // interrupt service routine - FASTRUN static void isrAudio() { + 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]; - + const uint16_t sample1 = audioBuffer[bufTail]; //*txReg = sample1; bufTail = (bufTail + 1) & (NUM_ENTRIES-1); if (freeEntries < NUM_ENTRIES) {++freeEntries;} *txReg = sample1; - //} } @@ -154,11 +214,11 @@ private: private: - FLASHMEM static void config_sai2() { + static void config_sai2(uint32_t sampleRate_hz) { CCM_CCGR5 |= CCM_CCGR5_SAI2(CCM_CCGR_ON); - double fs = SAMPLE_RATE_HZ; + 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); @@ -204,45 +264,18 @@ private: 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)); - - - - //Serial.println(CORE_PIN2_PADCONFIG); - //Serial.println(CORE_PIN3_PADCONFIG); - //Serial.println(CORE_PIN4_PADCONFIG); - //Serial.println("--------"); - - //uint32_t s = 0b0000010111000; - //CORE_PIN2_PADCONFIG = s; - //CORE_PIN2_PADCONFIG = s; - //CORE_PIN2_PADCONFIG = s; - - // 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 USE_DMA - I2S2_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; // | I2S_TCSR_FR ??? + #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 - /* - uint32_t pke = 0; - uint32_t ode = 0; - uint32_t speed = 0b00; - uint32_t dse = 0b010; - uint32_t sre = 0b0; - uint32_t s = (pke<<12)|(ode<<11)|(sre<<0)|(dse<<3)|(speed<<6); - - CORE_PIN2_PADCONFIG = s; - CORE_PIN3_PADCONFIG = s; - CORE_PIN4_PADCONFIG = s; - */ - } FLASHMEM static void setAudioClock(int nfact, int32_t nmult, uint32_t ndiv, bool force) {// sets PLL4 @@ -269,3 +302,11 @@ private: } }; + + +// init static class members + +#ifdef I2S2_IMPLEMENTATION + DMAMEM __attribute__((aligned(32))) int16_t I2S2::buffer[I2S2_BUFFER_SAMPLES]; + I2S2::State I2S2::state; +#endif