From 889830e9b8ceafad16bdfe0066e2464a1503f9fc Mon Sep 17 00:00:00 2001 From: kazu Date: Sat, 27 Feb 2021 15:39:26 +0100 Subject: [PATCH] refactored I2S (base class) --- io/teensy/I2S2.h | 192 ++++++----------------------------------- io/teensy/I2SBaseDMA.h | 115 ++++++++++++++++++++++++ io/teensy/I2SBaseIRQ.h | 57 ++++++++++++ 3 files changed, 197 insertions(+), 167 deletions(-) create mode 100644 io/teensy/I2SBaseDMA.h create mode 100644 io/teensy/I2SBaseIRQ.h diff --git a/io/teensy/I2S2.h b/io/teensy/I2S2.h index e305b67..6c26403 100644 --- a/io/teensy/I2S2.h +++ b/io/teensy/I2S2.h @@ -7,90 +7,39 @@ #include "../../Debug.h" -#define I2S2_USE_DMA - -#ifdef I2S2_USE_DMA - #include +#define IS2_MODE_DMA 1 +#define I2S_MODE_IRQ 2 +#ifndef I2S_MODE + #error "I2S_MODE must be defined: IS2_MODE_DMA or IS2_MODE_IRQ" #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" +#if I2S_MODE == IS2_MODE_DMA + #include "I2SBaseDMA.h" +#elif I2S_MODE == I2S_MODE_IRQ + #include "I2SBaseIRQ.h" +#else + #error "invalid value for I2S_MODE" +#endif -class I2S2 { +class I2S2 : public I2SBase { 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); + Log::addInfo(NAME, "setup(%d, %d)", channels, sampleRate_hz); // zero out the audio data buffer clearBuffer(); @@ -98,120 +47,27 @@ public: // 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 + #if I2S_MODE == IS2_MODE_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->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.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); + state.dma.attachInterrupt(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 + #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) { @@ -270,9 +126,11 @@ private: CORE_PIN2_CONFIG = 2; // TX_DATA0 I2S2_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; - #ifdef I2S2_USE_DMA + + // START + #if I2S_MODE == IS2_MODE_DMA I2S2_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; // | I2S_TCSR_FR ??? - #else + #elif I2S_MODE == IS2_MODE_IRQ I2S2_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE; #endif diff --git a/io/teensy/I2SBaseDMA.h b/io/teensy/I2SBaseDMA.h new file mode 100644 index 0000000..d491ba6 --- /dev/null +++ b/io/teensy/I2SBaseDMA.h @@ -0,0 +1,115 @@ +#pragma once + + +#include + +/** + * base-class for I2S1 and I2S2 when using DMA + * the template parameter is only needed to allocate the static variables TWICE + * once for I2S1 and once for I2S2 + */ +template class I2SBase { + + +protected: + + /** the actual PCM data buffer, DMA throws an interrupt whenever a half of this buffer was transmitted */ + static DMAMEM __attribute__((aligned(32))) int16_t buffer[I2S2_BUFFER_SAMPLES]; + + /** helper class to map one half of above buffer */ + struct BufferHalf { + + /** start of the data */ + int16_t* mem; + + /** number of samples that have been added during filling */ + volatile uint16_t samplesUsed = 0; + + /** ctor with the start of the memory for this half */ + BufferHalf(int16_t* mem) : mem(mem) {} + + /** number of samples that can be added befor the half is full */ + uint16_t samplesFree() const {return I2S2_BUFFER_SAMPLES/2 - samplesUsed;} + + + //bool isFilled() const {return samplesFree() == 0;} + + void dropCache() {arm_dcache_flush_delete(mem, I2S2_BUFFER_SAMPLES/2*sizeof(int16_t));} + + }; + + struct State { + + 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]; + } + + }; + + static State state; + + /** ISR, called whenever one half of the buffer was transmitted */ + FASTRUN static void isrDMA() { + + state.dma.clearInterrupt(); + state.halfTransmitted(); + + } + +public: + + /** 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) { + + // determine the half of the buffer we are currently writing to + BufferHalf& bh = state.fillingHalf(); + + // determine how many samples to add + const uint16_t addable = min(numSamples, bh.samplesFree()); + + // actually copy the data + uint8_t* dst = (uint8_t*) (&bh.mem[bh.samplesUsed]); + uint16_t numBytes = addable * sizeof(int16_t); + memcpy(dst, pcm, numBytes); + + // adjust the buffer half's information + bh.samplesUsed += addable; + return addable; + + } + +}; diff --git a/io/teensy/I2SBaseIRQ.h b/io/teensy/I2SBaseIRQ.h new file mode 100644 index 0000000..b61ccfe --- /dev/null +++ b/io/teensy/I2SBaseIRQ.h @@ -0,0 +1,57 @@ + + + void addSamples() { + + // 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 + +} + +void setup() { + + attachInterruptVector(IRQ_SAI2, isrAudio); + NVIC_ENABLE_IRQ(IRQ_SAI2); + NVIC_SET_PRIORITY(IRQ_SAI2, 127); + I2S2_TCSR |= 1<<8; + +} + + + + + +// 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; + //} + + }