Code:
#include <Audio.h>
#include <SPI.h>
// demo will create a cache-coherency problem between in the Audio pool buffers by perming SPI DMA
// on them. Here's what this demo does
// 1) Use AudioInputI2S for audio input which will DMA audio from SAI into the audio buffers
// 2) Pass it through AudioEffectFixedDelay
// 3) Send the output of AudioEffectFixedDelay to the AudioOutputI2S
//
// Each time update() is called, AudioEffectDelay will read the contents of SPI RAM into the new buffer, then write
// the input audio block out to SPI RAM using DMA transfers.
// The misaligned cache maintenance operations will corrupt the audio buffers. If there is no actual SPI ram, the data
// will always read back as zeros. As result you many hear corrupted audio or zero'd audio.
// SPI Memory definitions
#define SPI_WRITE_CMD 0x2
#define SPI_READ_CMD 0x3
#define SPI_ADDR_2_MASK 0xFF0000
#define SPI_ADDR_2_SHIFT 16
#define SPI_ADDR_1_MASK 0x00FF00
#define SPI_ADDR_1_SHIFT 8
#define SPI_ADDR_0_MASK 0x0000FF
// SPI pinouts
constexpr uint8_t SPI_SCK = 13;
constexpr uint8_t SPI_CS = 10;
constexpr uint8_t SPI_MISO = 12;
constexpr uint8_t SPI_MOSI = 11;
AudioInputI2S i2sIn;
AudioOutputI2S i2sOut;
AudioControlSGTL5000 codec;
// DMA SPI stuff
constexpr size_t AUDIO_BLOCK_BYTES = sizeof(int16_t) * AUDIO_BLOCK_SAMPLES;
constexpr size_t CMD_SIZE = 4;
uint8_t commandBuffer[CMD_SIZE];
SPISettings memSettings(20000000, MSBFIRST, SPI_MODE0);
EventResponder callbackHandler, dummyCallbackHandler;
volatile bool dmaBusy = false;
// DMA Functions
void setSpiCmdAddr(int command, size_t address, volatile uint8_t *dest)
{
dest[0] = command;
dest[1] = ((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
dest[2] = ((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
dest[3] = ((address & SPI_ADDR_0_MASK));
}
void callback(EventResponderRef eventResponder)
{
dmaBusy = false;
}
void mem0DmaWrite(unsigned address, volatile uint8_t *src, size_t count)
{
if (!src) { return; }
// First set the command
setSpiCmdAddr(SPI_WRITE_CMD, address, commandBuffer);
digitalWrite(SPI_CS, LOW);
dmaBusy = true;
SPI.beginTransaction(memSettings);
SPI.transfer((void *)commandBuffer, nullptr, CMD_SIZE, callbackHandler);
SPI.endTransaction();
while (dmaBusy) { yield(); }
dmaBusy = true;
SPI.beginTransaction(memSettings);
SPI.transfer((void *)src, nullptr, count, callbackHandler);
SPI.endTransaction();
while(dmaBusy) { yield(); }
digitalWrite(SPI_CS, HIGH);
}
void mem0DmaRead(unsigned address, volatile uint8_t *dest, size_t count)
{
if (!dest) { return; }
// First set the command
setSpiCmdAddr(SPI_READ_CMD, address, commandBuffer);
digitalWrite(SPI_CS, LOW);
dmaBusy = true;
SPI.beginTransaction(memSettings);
SPI.transfer((void *)commandBuffer, nullptr, CMD_SIZE, callbackHandler);
SPI.endTransaction();
while (dmaBusy) { yield(); }
dmaBusy = true;
SPI.beginTransaction(memSettings);
SPI.transfer(nullptr, dest, count, callbackHandler);
SPI.endTransaction();
while(dmaBusy) { yield(); }
digitalWrite(SPI_CS, HIGH);
}
class AudioEffectFixedDelay : public AudioStream {
public:
AudioEffectFixedDelay() : AudioStream(1, m_inputQueueArray) {}
virtual void update(void) {
audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples
if (!inputAudioBlock) { return; }
audio_block_t *outputAudioBlock = allocate();
if (!outputAudioBlock) {
transmit(inputAudioBlock);
release(inputAudioBlock);
return;
}
// read the previous block from SPI memory to output block.
// Comment out this line to avoid the problem or else fix the audio pool buffers (data field) to be 32-byte aligned
mem0DmaRead(0, (uint8_t*)outputAudioBlock->data, AUDIO_BLOCK_BYTES);
// write the current block out to SPI memory
mem0DmaWrite(0, (uint8_t*)inputAudioBlock->data, AUDIO_BLOCK_BYTES);
transmit(inputAudioBlock); // If you have SPI RAM installed you can comment out this line
//transmit(outputAudioBlock); // and uncomment this once. You should get clean audio if the cache fix is applied to the audio buffers
release(inputAudioBlock);
release(outputAudioBlock);
}
private:
audio_block_t *m_inputQueueArray[1];
};
AudioEffectFixedDelay fixedDelay;
AudioConnection input0(i2sIn, 0, fixedDelay, 0);
AudioConnection output0(fixedDelay, 0, i2sOut, 0);
AudioConnection output1(fixedDelay, 0, i2sOut, 1);
void setup() {
// put your setup code here, to run once:
delay(100); // wait a bit for serial to be available
Serial.begin(57600); // Start the serial port
while(!Serial) { yield(); }
delay(100);
// Setup the SPI stuff
pinMode(SPI_CS, OUTPUT);
digitalWrite(SPI_CS, HIGH);
SPI.setMOSI(SPI_MOSI);
SPI.setMISO(SPI_MISO);
SPI.setSCK(SPI_SCK);
SPI.begin();
// Setup the callback
callbackHandler.attachImmediate(&callback);
codec.disable();
AudioMemory(32);
Serial.println("Enabling codec...");
codec.enable();
codec.volume(0.3);
codec.inputSelect(AUDIO_INPUT_LINEIN);
}
size_t loopCounter = 0;
void loop() {
// put your main code here, to run repeatedly:
if (loopCounter == 0) {
Serial.println("First loop");
}
loopCounter++;
if (loopCounter % (65536*64) == 0) {
Serial.println("Still alive");
}
}