#pragma once #include #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[0].dropCache(); state.bufferHalf[1].samplesUsed = 0; state.bufferHalf[1].dropCache(); } /** 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 = std::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; } };