refactored I2S (base class)
This commit is contained in:
192
io/teensy/I2S2.h
192
io/teensy/I2S2.h
@@ -7,90 +7,39 @@
|
|||||||
|
|
||||||
#include "../../Debug.h"
|
#include "../../Debug.h"
|
||||||
|
|
||||||
#define I2S2_USE_DMA
|
#define IS2_MODE_DMA 1
|
||||||
|
#define I2S_MODE_IRQ 2
|
||||||
#ifdef I2S2_USE_DMA
|
#ifndef I2S_MODE
|
||||||
#include <DMAChannel.h>
|
#error "I2S_MODE must be defined: IS2_MODE_DMA or IS2_MODE_IRQ"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// the buffer has a size of X samples,
|
// the buffer has a size of X samples,
|
||||||
// and we receive an interrupt whenever ONE HALF was transmitted
|
// and we receive an interrupt whenever ONE HALF was transmitted
|
||||||
// 1152 = MP3 decode size, *2 (stereo), *2 (2 buffer halfs)
|
// 1152 = MP3 decode size, *2 (stereo), *2 (2 buffer halfs)
|
||||||
static constexpr const uint32_t I2S2_BUFFER_SAMPLES = 1152*2*2;
|
static constexpr const uint32_t I2S2_BUFFER_SAMPLES = 1152*2*2;
|
||||||
|
|
||||||
|
|
||||||
|
#if I2S_MODE == IS2_MODE_DMA
|
||||||
#include "I2SBase.h"
|
#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<I2S2> {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
static constexpr const char* NAME = "I2S2";
|
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:
|
public:
|
||||||
|
|
||||||
/** start the i2s transmission with the given values */
|
/** start the i2s transmission with the given values */
|
||||||
static void setup(uint8_t channels, uint32_t sampleRate_hz) {
|
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
|
// zero out the audio data buffer
|
||||||
clearBuffer();
|
clearBuffer();
|
||||||
@@ -98,120 +47,27 @@ public:
|
|||||||
// configure the I2S unit
|
// configure the I2S unit
|
||||||
config_sai2(sampleRate_hz);
|
config_sai2(sampleRate_hz);
|
||||||
|
|
||||||
#ifdef I2S2_USE_DMA
|
#if I2S_MODE == IS2_MODE_DMA
|
||||||
state.dma.begin(true); // Allocate the DMA channel first
|
state.dma.begin(true); // Allocate the DMA channel first
|
||||||
state.dma.TCD->SADDR = buffer; // source address
|
state.dma.TCD->SADDR = buffer; // source address
|
||||||
state.dma.TCD->SOFF = 2; // source buffer address increment per transfer in bytes
|
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->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->NBYTES_MLNO = 2; // bytes to transfer for each service request///////////////////////////////////////////////////////////////////
|
||||||
state.dma.TCD->SLAST = -sizeof(buffer); // last source address adjustment
|
state.dma.TCD->SLAST = -sizeof(buffer); // last source address adjustment
|
||||||
state.dma.TCD->DOFF = 0; // increments at destination
|
state.dma.TCD->DOFF = 0; // increments at destination
|
||||||
state.dma.TCD->CITER_ELINKNO = sizeof(buffer) / 2;
|
state.dma.TCD->CITER_ELINKNO = sizeof(buffer) / 2;
|
||||||
state.dma.TCD->DLASTSGA = 0; // destination address offset
|
state.dma.TCD->DLASTSGA = 0; // destination address offset
|
||||||
state.dma.TCD->BITER_ELINKNO = sizeof(buffer) / 2;
|
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->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.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI2_TX); // i2s channel that will trigger the DMA transfer when ready for data
|
||||||
state.dma.enable();
|
state.dma.enable();
|
||||||
state.dma.attachInterrupt(I2S2::isrDMA);
|
state.dma.attachInterrupt(isrDMA);
|
||||||
Log::addInfo(NAME, "DMA configured");
|
Log::addInfo(NAME, "DMA configured");
|
||||||
#else
|
#endif
|
||||||
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:
|
private:
|
||||||
|
|
||||||
static void config_sai2(uint32_t sampleRate_hz) {
|
static void config_sai2(uint32_t sampleRate_hz) {
|
||||||
@@ -270,9 +126,11 @@ private:
|
|||||||
CORE_PIN2_CONFIG = 2; // TX_DATA0
|
CORE_PIN2_CONFIG = 2; // TX_DATA0
|
||||||
I2S2_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE;
|
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 ???
|
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;
|
I2S2_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
115
io/teensy/I2SBaseDMA.h
Normal file
115
io/teensy/I2SBaseDMA.h
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#include <DMAChannel.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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[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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
57
io/teensy/I2SBaseIRQ.h
Normal file
57
io/teensy/I2SBaseIRQ.h
Normal file
@@ -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;
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user