worked on teensy i2s

This commit is contained in:
2021-02-27 11:29:58 +01:00
parent fb18709e2c
commit 2f35ecdfbf
2 changed files with 158 additions and 101 deletions

View File

@@ -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

View File

@@ -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 <DMAChannel.h>
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 <DMAChannel.h>
#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