119 lines
3.1 KiB
C++
119 lines
3.1 KiB
C++
#pragma once
|
|
|
|
|
|
#include <DMAChannel.h>
|
|
#include <algorithm>
|
|
|
|
/**
|
|
* 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 <typename T> 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;
|
|
|
|
}
|
|
|
|
};
|