Merge pull request #1 from Blackaddr/dma_spi

Dma spi
master
Blackaddr Audio 6 years ago committed by GitHub
commit d7fdb462d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      README.md
  2. 84
      examples/BA2_TGA_Pro_1MEM/BA2_TGA_Pro_1MEM.ino
  3. 142
      examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino
  4. 19
      examples/Delay/AnalogDelayDemo/name.c
  5. 316
      examples/Tests/DMA_MEM0_test/DMA_MEM0_test.ino
  6. 316
      examples/Tests/DMA_MEM1_test/DMA_MEM1_test.ino
  7. 162
      src/AudioEffectAnalogDelay.h
  8. 6
      src/BAAudioControlWM8731.h
  9. 7
      src/BAGpio.h
  10. 10
      src/BAGuitar.h
  11. 23
      src/BAHardware.h
  12. 155
      src/BASpiMemory.cpp
  13. 162
      src/BASpiMemory.h
  14. 158
      src/BATypes.h
  15. 283
      src/LibBasicFunctions.h
  16. 212
      src/LibMemoryManagement.h
  17. 171
      src/common/AudioDelay.cpp
  18. 78
      src/common/AudioHelpers.cpp
  19. 235
      src/common/ExtMemSlot.cpp
  20. 120
      src/common/ExternalSramManager.cpp
  21. 145
      src/common/IirBiquadFilter.cpp
  22. 279
      src/effects/AudioEffectAnalogDelay.cpp
  23. 0
      src/effects/BAAudioEffectDelayExternal.cpp
  24. 0
      src/peripherals/BAAudioControlWM8731.cpp
  25. 0
      src/peripherals/BAGpio.cpp
  26. 471
      src/peripherals/BASpiMemory.cpp

@ -5,9 +5,12 @@ Tested with:
Arduino IDE: 1.8.4 Arduino IDE: 1.8.4
Teensyduinio: 1.39 Teensyduinio: 1.39
*** This library requires a forked version of DmaSpi. You must download the version from here: ***
*** https://github.com/Blackaddr/DmaSpi ***
*** If this library fails to compile, please ensure you have updated your Arduino IDE/Teensyduino at least to the versions listed above. *** *** If this library fails to compile, please ensure you have updated your Arduino IDE/Teensyduino at least to the versions listed above. ***
This library contains convience C++ classes to allow full and easy access to the features of the Teensy Guitar Audio Series of boards. This library contains convienence C++ classes to allow full and easy access to the features of the Teensy Guitar Audio Series of boards.
"Teensy" is an Arduino compatible series of boards from www.pjrc.com "Teensy" is an Arduino compatible series of boards from www.pjrc.com

@ -8,9 +8,13 @@
* This demo will provide an audio passthrough, as well as exercise the * This demo will provide an audio passthrough, as well as exercise the
* MIDI interface. * MIDI interface.
* *
* It will also perform a sweeo of the SPI MEM0 external RAM. * It will also peform a sweep of SPI MEM0.
*
* NOTE: SPI MEM0 can be used by a Teensy 3.1/3.2/3.5/3.6.
* pins.
* *
*/ */
//#include <i2c_t3.h>
#include <Wire.h> #include <Wire.h>
#include <Audio.h> #include <Audio.h>
#include <MIDI.h> #include <MIDI.h>
@ -19,7 +23,6 @@
MIDI_CREATE_DEFAULT_INSTANCE(); MIDI_CREATE_DEFAULT_INSTANCE();
using namespace midi; using namespace midi;
using namespace BAGuitar; using namespace BAGuitar;
AudioInputI2S i2sIn; AudioInputI2S i2sIn;
@ -32,23 +35,25 @@ AudioConnection patch1(i2sIn,1, i2sOut, 1);
BAAudioControlWM8731 codecControl; BAAudioControlWM8731 codecControl;
BAGpio gpio; // access to User LED BAGpio gpio; // access to User LED
BASpiMemory spiMem0(SpiDeviceId::SPI_DEVICE0); BASpiMemory spiMem0(SpiDeviceId::SPI_DEVICE0);
BASpiMemory spiMem1(SpiDeviceId::SPI_DEVICE1);
unsigned long t=0; unsigned long t=0;
// SPI stuff // SPI stuff
int spiAddress0 = 0; int spiAddress0 = 0;
int spiData0 = 0xff; uint16_t spiData0 = 0xABCD;
int spiErrorCount0 = 0; int spiErrorCount0 = 0;
int spiAddress1 = 0;
int spiData1 = 0xff;
int spiErrorCount1 = 0;
constexpr int mask0 = 0x5555;
constexpr int mask1 = 0xaaaa;
int maskPhase = 0;
int loopPhase = 0;
void setup() { void setup() {
MIDI.begin(MIDI_CHANNEL_OMNI); MIDI.begin(MIDI_CHANNEL_OMNI);
Serial.begin(57600); Serial.begin(57600);
while (!Serial) {}
delay(5); delay(5);
// If the codec was already powered up (due to reboot) power itd own first // If the codec was already powered up (due to reboot) power itd own first
@ -56,26 +61,53 @@ void setup() {
delay(100); delay(100);
AudioMemory(24); AudioMemory(24);
Serial.println("Enabling codec...\n"); Serial.println("Sketch: Enabling codec...\n");
codecControl.enable(); codecControl.enable();
delay(100); delay(100);
Serial.println("Enabling SPI");
spiMem0.begin();
} }
void loop() { int calcData(int spiAddress, int loopPhase, int maskPhase)
{
int data;
int phase = ((loopPhase << 1) + maskPhase) & 0x3;
switch(phase)
{
case 0 :
data = spiAddress ^ mask0;
break;
case 1:
data = spiAddress ^ mask1;
break;
case 2:
data = ~spiAddress ^ mask0;
break;
case 3:
data = ~spiAddress ^ mask1;
}
return (data & 0xffff);
}
void loop() {
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
// Write test data to the SPI Memory 0 // Write test data to the SPI Memory 0
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
for (spiAddress0=0; spiAddress0 <= SPI_MAX_ADDR; spiAddress0++) { maskPhase = 0;
for (spiAddress0=0; spiAddress0 <= SPI_MAX_ADDR; spiAddress0=spiAddress0+2) {
if ((spiAddress0 % 32768) == 0) { if ((spiAddress0 % 32768) == 0) {
//Serial.print("Writing to "); //Serial.print("Writing to ");
//Serial.println(spiAddress0, HEX); //Serial.println(spiAddress0, HEX);
} }
//mem0Write(spiAddress0, spiData0); spiData0 = calcData(spiAddress0, loopPhase, maskPhase);
spiMem0.write(spiAddress0, spiData0); spiMem0.write16(spiAddress0, spiData0);
spiData0 = (spiData0-1) & 0xff; maskPhase = (maskPhase+1) % 2;
} }
Serial.println("SPI0 writing DONE!"); Serial.println("SPI0 writing DONE!");
@ -84,39 +116,41 @@ void loop() {
/////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////
spiErrorCount0 = 0; spiErrorCount0 = 0;
spiAddress0 = 0; spiAddress0 = 0;
spiData0 = 0xff; maskPhase = 0;
for (spiAddress0=0; spiAddress0 <= SPI_MAX_ADDR; spiAddress0++) { for (spiAddress0=0; spiAddress0 <= SPI_MAX_ADDR; spiAddress0=spiAddress0+2) {
if ((spiAddress0 % 32768) == 0) { if ((spiAddress0 % 32768) == 0) {
//Serial.print("Reading "); // Serial.print("Reading ");
//Serial.print(spiAddress0, HEX); // Serial.print(spiAddress0, HEX);
} }
//int data = mem0Read(spiAddress0); spiData0 = calcData(spiAddress0, loopPhase, maskPhase);
int data = spiMem0.read(spiAddress0); int data = spiMem0.read16(spiAddress0);
if (data != spiData0) { if (data != spiData0) {
spiErrorCount0++; spiErrorCount0++;
Serial.println(""); Serial.println("");
Serial.print("ERROR MEM0: (expected) (actual):"); Serial.print("ERROR MEM0: (expected) (actual):");
Serial.print(spiData0, HEX); Serial.print(":"); Serial.print(spiData0, HEX); Serial.print(":");
Serial.println(data, HEX); Serial.print(data, HEX);
delay(100); delay(100);
} }
maskPhase = (maskPhase+1) % 2;
if ((spiAddress0 % 32768) == 0) { if ((spiAddress0 % 32768) == 0) {
//Serial.print(", data = "); // Serial.print(", data = ");
//Serial.println(data, HEX); // Serial.println(data, HEX);
// Serial.println(String(" loopPhase: ") + loopPhase + String(" maskPhase: ") + maskPhase);
} }
spiData0 = (spiData0-1) & 0xff;
// Break out of test once the error count reaches 10 // Break out of test once the error count reaches 10
if (spiErrorCount0 > 10) { break; } if (spiErrorCount0 > 10) { break; }
} }
if (spiErrorCount0 == 0) { Serial.println("SPI0 TEST PASSED!!!"); } if (spiErrorCount0 == 0) { Serial.println("SPI0 TEST PASSED!!!"); }
loopPhase = (loopPhase+1) % 2;
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
// MIDI TESTING // MIDI TESTING

@ -0,0 +1,142 @@
#include <MIDI.h>
#include "BAGuitar.h"
using namespace BAGuitar;
AudioInputI2S i2sIn;
AudioOutputI2S i2sOut;
BAAudioControlWM8731 codec;
/// IMPORTANT /////
// YOU MUST COMPILE THIS DEMO USING Serial + Midi
//#define USE_EXT // uncomment this line to use External MEM0
#define MIDI_DEBUG // uncomment to see raw MIDI info in terminal
#ifdef USE_EXT
// If using external SPI memory, we will instantiance an SRAM
// manager and create an external memory slot to use as the memory
// for our audio delay
ExternalSramManager externalSram;
ExtMemSlot delaySlot; // Declare an external memory slot.
// Instantiate the AudioEffectAnalogDelay to use external memory by
/// passing it the delay slot.
AudioEffectAnalogDelay analogDelay(&delaySlot);
#else
// If using internal memory, we will instantiate the AudioEffectAnalogDelay
// by passing it the maximum amount of delay we will use in millseconds. Note that
// audio delay lengths are very limited when using internal memory due to limited
// internal RAM size.
AudioEffectAnalogDelay analogDelay(200.0f); // max delay of 200 ms.
#endif
AudioFilterBiquad cabFilter; // We'll want something to cut out the highs and smooth the tone, just like a guitar cab.
// Record the audio to the PC
//AudioOutputUSB usb;
// Simply connect the input to the delay, and the output
// to both i2s channels
AudioConnection input(i2sIn,0, analogDelay,0);
AudioConnection delayOut(analogDelay, 0, cabFilter, 0);
AudioConnection leftOut(cabFilter,0, i2sOut, 0);
AudioConnection rightOut(cabFilter,0, i2sOut, 1);
//AudioConnection leftOutUSB(cabFilter,0, usb, 0);
//AudioConnection rightOutUSB(cabFilter,0, usb, 1);
int loopCount = 0;
void setup() {
delay(100);
Serial.begin(57600); // Start the serial port
// Disable the codec first
codec.disable();
delay(100);
AudioMemory(128);
delay(5);
// Enable the codec
Serial.println("Enabling codec...\n");
codec.enable();
delay(100);
// If using external memory request request memory from the manager
// for the slot
#ifdef USE_EXT
Serial.println("Using EXTERNAL memory");
// We have to request memory be allocated to our slot.
externalSram.requestMemory(&delaySlot, 500.0f, MemSelect::MEM0, true);
#else
Serial.println("Using INTERNAL memory");
#endif
// Configure which MIDI CC's will control the effect parameters
analogDelay.mapMidiControl(AudioEffectAnalogDelay::BYPASS,16);
analogDelay.mapMidiControl(AudioEffectAnalogDelay::DELAY,20);
analogDelay.mapMidiControl(AudioEffectAnalogDelay::FEEDBACK,21);
analogDelay.mapMidiControl(AudioEffectAnalogDelay::MIX,22);
analogDelay.mapMidiControl(AudioEffectAnalogDelay::VOLUME,23);
// Besure to enable the delay. When disabled, audio is is completely blocked
// to minimize resources to nearly zero.
analogDelay.enable();
// Set some default values.
// These can be changed by sending MIDI CC messages over the USB using
// the BAMidiTester application.
analogDelay.delay(200.0f); // initial delay of 200 ms
analogDelay.bypass(false);
analogDelay.mix(0.5f);
analogDelay.feedback(0.0f);
// Setup 2-stages of LPF, cutoff 4500 Hz, Q-factor 0.7071 (a 'normal' Q-factor)
cabFilter.setLowpass(0, 4500, .7071);
cabFilter.setLowpass(1, 4500, .7071);
}
void OnControlChange(byte channel, byte control, byte value) {
analogDelay.processMidi(channel, control, value);
#ifdef MIDI_DEBUG
Serial.print("Control Change, ch=");
Serial.print(channel, DEC);
Serial.print(", control=");
Serial.print(control, DEC);
Serial.print(", value=");
Serial.print(value, DEC);
Serial.println();
#endif
}
void loop() {
// usbMIDI.read() needs to be called rapidly from loop(). When
// each MIDI messages arrives, it return true. The message must
// be fully processed before usbMIDI.read() is called again.
if (loopCount % 524288 == 0) {
Serial.print("Processor Usage, Total: "); Serial.print(AudioProcessorUsage());
Serial.print("% ");
Serial.print(" analogDelay: "); Serial.print(analogDelay.processorUsage());
Serial.println("%");
}
loopCount++;
// check for new MIDI from USB
if (usbMIDI.read()) {
// this code entered only if new MIDI received
byte type, channel, data1, data2, cable;
type = usbMIDI.getType(); // which MIDI message, 128-255
channel = usbMIDI.getChannel(); // which MIDI channel, 1-16
data1 = usbMIDI.getData1(); // first data byte of message, 0-127
data2 = usbMIDI.getData2(); // second data byte of message, 0-127
if (type == 3) {
// if type is 3, it's a CC MIDI Message
// Note: the Arduino MIDI library encodes channels as 1-16 instead
// of 0 to 15 as it should, so we must subtract one.
OnControlChange(channel-1, data1, data2);
}
}
}

@ -0,0 +1,19 @@
// To give your project a unique name, this code must be
// placed into a .c file (its own tab). It can not be in
// a .cpp file or your main sketch (the .ino file).
#include "usb_names.h"
// Edit these lines to create your own name. The length must
// match the number of characters in your custom name.
#define MIDI_NAME {'B','l','a','c','k','a','d','d','r',' ','A','u','d','i','o',' ','T','G','A',' ','P','r','o'}
#define MIDI_NAME_LEN 23
// Do not change this part. This exact format is required by USB.
struct usb_string_descriptor_struct usb_string_product_name = {
2 + MIDI_NAME_LEN * 2,
3,
MIDI_NAME
};

@ -0,0 +1,316 @@
/*************************************************************************
* This demo uses the BAGuitar library to provide enhanced control of
* the TGA Pro board.
*
* The latest copy of the BA Guitar library can be obtained from
* https://github.com/Blackaddr/BAGuitar
*
* This demo will perform a DMA memory test on MEM0 attached
* to SPI.
*
* NOTE: SPI MEM0 can be used by a Teensy 3.2/3.5/3.6.
* pins.
*
*/
#include <Wire.h>
#include "BAGuitar.h"
using namespace BAGuitar;
//#define SANITY
#define DMA_SIZE 256
#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
SPISettings memSettings(20000000, MSBFIRST, SPI_MODE0);
const int cs0pin = 15;
BAGpio gpio; // access to User LED
BASpiMemoryDMA spiMem0(SpiDeviceId::SPI_DEVICE0);
bool compareBuffers(uint8_t *a, uint8_t *b, size_t numBytes)
{
bool pass=true;
int errorCount = 0;
for (size_t i=0; i<numBytes; i++) {
if (a[i] != b[i]) {
if (errorCount < 10) {
Serial.print(i); Serial.print(":: a:"); Serial.print(a[i]); Serial.print(" does not match b:"); Serial.println(b[i]);
pass=false;
errorCount++;
} else { return false; }
}
}
return pass;
}
bool compareBuffers16(uint16_t *a, uint16_t *b, size_t numWords)
{
return compareBuffers(reinterpret_cast<uint8_t *>(a), reinterpret_cast<uint8_t*>(b), sizeof(uint16_t)*numWords);
}
constexpr size_t TEST_END = SPI_MAX_ADDR;
void setup() {
Serial.begin(57600);
while (!Serial) {}
delay(5);
Serial.println("Enabling SPI");
spiMem0.begin();
}
bool spi8BitTest(void) {
size_t spiAddress = 0;
int spiPhase = 0;
constexpr uint8_t MASK80 = 0xaa;
constexpr uint8_t MASK81 = 0x55;
uint8_t src8[DMA_SIZE];
uint8_t dest8[DMA_SIZE];
// Write to the memory using 8-bit transfers
Serial.println("\nStarting 8-bit test Write/Read");
while (spiPhase < 4) {
spiAddress = 0;
while (spiAddress < TEST_END) {
// fill the write data buffer
switch (spiPhase) {
case 0 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ( (spiAddress+i) & 0xff) ^ MASK80;
}
break;
case 1 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ((spiAddress+i) & 0xff) ^ MASK81;
}
break;
case 2 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK80);
}
break;
case 3 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK81);
}
break;
}
// Send the DMA transfer
spiMem0.write(spiAddress, src8, DMA_SIZE);
// wait until write is done
while (spiMem0.isWriteBusy()) {}
spiAddress += DMA_SIZE;
}
// Read back the data using 8-bit transfers
spiAddress = 0;
while (spiAddress < TEST_END) {
// generate the golden data
switch (spiPhase) {
case 0 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ( (spiAddress+i) & 0xff) ^ MASK80;
}
break;
case 1 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ((spiAddress+i) & 0xff) ^ MASK81;
}
break;
case 2 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK80);
}
break;
case 3 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK81);
}
break;
}
// Read back the DMA data
spiMem0.read(spiAddress, dest8, DMA_SIZE);
// wait until read is done
while (spiMem0.isReadBusy()) {}
if (!compareBuffers(src8, dest8, DMA_SIZE)) {
Serial.println(String("ERROR @") + spiAddress);
return false; } // stop the test
spiAddress += DMA_SIZE;
}
Serial.println(String("Phase ") + spiPhase + String(": 8-bit Write/Read test passed!"));
spiPhase++;
}
#ifdef SANITY
for (int i=0; i<DMA_SIZE; i++) {
Serial.print(src8[i], HEX); Serial.print("="); Serial.println(dest8[i],HEX);
}
#endif
Serial.println("\nStarting 8-bit test Zero/Read");
// Clear the memory
spiAddress = 0;
memset(src8, 0, DMA_SIZE);
while (spiAddress < SPI_MAX_ADDR) {
// Send the DMA transfer
spiMem0.zero(spiAddress, DMA_SIZE);
// wait until write is done
while (spiMem0.isWriteBusy()) {}
spiAddress += DMA_SIZE;
}
// Check the memory is clear
spiAddress = 0;
while (spiAddress < SPI_MAX_ADDR) {
// Read back the DMA data
spiMem0.read(spiAddress, dest8, DMA_SIZE);
// wait until read is done
while (spiMem0.isReadBusy()) {}
if (!compareBuffers(src8, dest8, DMA_SIZE)) { return false; } // stop the test
spiAddress += DMA_SIZE;
}
Serial.println(String(": 8-bit Zero/Read test passed!"));
return true;
}
bool spi16BitTest(void) {
size_t spiAddress = 0;
int spiPhase = 0;
constexpr uint16_t MASK160 = 0xaaaa;
constexpr uint16_t MASK161 = 0x5555;
constexpr int NUM_DATA = DMA_SIZE / sizeof(uint16_t);
uint16_t src16[NUM_DATA];
uint16_t dest16[NUM_DATA];
// Write to the memory using 16-bit transfers
Serial.println("\nStarting 16-bit test");
while (spiPhase < 4) {
spiAddress = 0;
while (spiAddress < SPI_MAX_ADDR) {
// fill the write data buffer
switch (spiPhase) {
case 0 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ( (spiAddress+i) & 0xffff) ^ MASK160;
}
break;
case 1 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ((spiAddress+i) & 0xffff) ^ MASK161;
}
break;
case 2 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK160);
}
break;
case 3 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK161);
}
break;
}
// Send the DMA transfer
spiMem0.write16(spiAddress, src16, NUM_DATA);
// wait until write is done
while (spiMem0.isWriteBusy()) {}
spiAddress += DMA_SIZE;
}
// Read back the data using 8-bit transfers
spiAddress = 0;
while (spiAddress < SPI_MAX_ADDR) {
// generate the golden data
switch (spiPhase) {
case 0 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ( (spiAddress+i) & 0xffff) ^ MASK160;
}
break;
case 1 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ((spiAddress+i) & 0xffff) ^ MASK161;
}
break;
case 2 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK160);
}
break;
case 3 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK161);
}
break;
}
// Read back the DMA data
spiMem0.read16(spiAddress, dest16, NUM_DATA);
// wait until read is done
while (spiMem0.isReadBusy()) {}
if (!compareBuffers16(src16, dest16, NUM_DATA)) {
Serial.print("ERROR @"); Serial.println(spiAddress, HEX);
return false; } // stop the test
spiAddress += DMA_SIZE;
}
Serial.println(String("Phase ") + spiPhase + String(": 16-bit test passed!"));
spiPhase++;
}
#ifdef SANITY
for (int i=0; i<NUM_DATA; i++) {
Serial.print(src16[i], HEX); Serial.print("="); Serial.println(dest16[i],HEX);
}
#endif
Serial.println("\nStarting 16-bit test Zero/Read");
// Clear the memory
spiAddress = 0;
memset(src16, 0, DMA_SIZE);
while (spiAddress < SPI_MAX_ADDR) {
// Send the DMA transfer
spiMem0.zero16(spiAddress, NUM_DATA);
// wait until write is done
while (spiMem0.isWriteBusy()) {}
spiAddress += DMA_SIZE;
}
// Check the memory is clear
spiAddress = 0;
while (spiAddress < SPI_MAX_ADDR) {
// Read back the DMA data
spiMem0.read16(spiAddress, dest16, NUM_DATA);
// wait until read is done
while (spiMem0.isReadBusy()) {}
if (!compareBuffers16(src16, dest16, NUM_DATA)) { return false; } // stop the test
spiAddress += DMA_SIZE;
}
Serial.println(String(": 16-bit Zero/Read test passed!"));
return true;
}
void loop() {
spi8BitTest();
spi16BitTest();
while(true) {}
}

@ -0,0 +1,316 @@
/*************************************************************************
* This demo uses the BAGuitar library to provide enhanced control of
* the TGA Pro board.
*
* The latest copy of the BA Guitar library can be obtained from
* https://github.com/Blackaddr/BAGuitar
*
* This demo will perform a DMA memory test on MEM1 attached
* to SPI1.
*
* NOTE: SPI MEM1 can be used by a Teensy 3.5/3.6.
* pins.
*
*/
#include <Wire.h>
#include "BAGuitar.h"
using namespace BAGuitar;
//#define SANITY
#define DMA_SIZE 256
#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
SPISettings memSettings(20000000, MSBFIRST, SPI_MODE0);
const int cs0pin = 15;
BAGpio gpio; // access to User LED
BASpiMemoryDMA spiMem1(SpiDeviceId::SPI_DEVICE1);
bool compareBuffers(uint8_t *a, uint8_t *b, size_t numBytes)
{
bool pass=true;
int errorCount = 0;
for (size_t i=0; i<numBytes; i++) {
if (a[i] != b[i]) {
if (errorCount < 10) {
Serial.print(i); Serial.print(":: a:"); Serial.print(a[i]); Serial.print(" does not match b:"); Serial.println(b[i]);
pass=false;
errorCount++;
} else { return false; }
}
}
return pass;
}
bool compareBuffers16(uint16_t *a, uint16_t *b, size_t numWords)
{
return compareBuffers(reinterpret_cast<uint8_t *>(a), reinterpret_cast<uint8_t*>(b), sizeof(uint16_t)*numWords);
}
constexpr size_t TEST_END = SPI_MAX_ADDR;
void setup() {
Serial.begin(57600);
while (!Serial) {}
delay(5);
Serial.println("Enabling SPI");
spiMem1.begin();
}
bool spi8BitTest(void) {
size_t spiAddress = 0;
int spiPhase = 0;
constexpr uint8_t MASK80 = 0xaa;
constexpr uint8_t MASK81 = 0x55;
uint8_t src8[DMA_SIZE];
uint8_t dest8[DMA_SIZE];
// Write to the memory using 8-bit transfers
Serial.println("\nStarting 8-bit test Write/Read");
while (spiPhase < 4) {
spiAddress = 0;
while (spiAddress < TEST_END) {
// fill the write data buffer
switch (spiPhase) {
case 0 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ( (spiAddress+i) & 0xff) ^ MASK80;
}
break;
case 1 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ((spiAddress+i) & 0xff) ^ MASK81;
}
break;
case 2 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK80);
}
break;
case 3 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK81);
}
break;
}
// Send the DMA transfer
spiMem1.write(spiAddress, src8, DMA_SIZE);
// wait until write is done
while (spiMem1.isWriteBusy()) {}
spiAddress += DMA_SIZE;
}
// Read back the data using 8-bit transfers
spiAddress = 0;
while (spiAddress < TEST_END) {
// generate the golden data
switch (spiPhase) {
case 0 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ( (spiAddress+i) & 0xff) ^ MASK80;
}
break;
case 1 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ((spiAddress+i) & 0xff) ^ MASK81;
}
break;
case 2 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK80);
}
break;
case 3 :
for (int i=0; i<DMA_SIZE; i++) {
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK81);
}
break;
}
// Read back the DMA data
spiMem1.read(spiAddress, dest8, DMA_SIZE);
// wait until read is done
while (spiMem1.isReadBusy()) {}
if (!compareBuffers(src8, dest8, DMA_SIZE)) {
Serial.println(String("ERROR @") + spiAddress);
return false; } // stop the test
spiAddress += DMA_SIZE;
}
Serial.println(String("Phase ") + spiPhase + String(": 8-bit Write/Read test passed!"));
spiPhase++;
}
#ifdef SANITY
for (int i=0; i<DMA_SIZE; i++) {
Serial.print(src8[i], HEX); Serial.print("="); Serial.println(dest8[i],HEX);
}
#endif
Serial.println("\nStarting 8-bit test Zero/Read");
// Clear the memory
spiAddress = 0;
memset(src8, 0, DMA_SIZE);
while (spiAddress < SPI_MAX_ADDR) {
// Send the DMA transfer
spiMem1.zero(spiAddress, DMA_SIZE);
// wait until write is done
while (spiMem1.isWriteBusy()) {}
spiAddress += DMA_SIZE;
}
// Check the memory is clear
spiAddress = 0;
while (spiAddress < SPI_MAX_ADDR) {
// Read back the DMA data
spiMem1.read(spiAddress, dest8, DMA_SIZE);
// wait until read is done
while (spiMem1.isReadBusy()) {}
if (!compareBuffers(src8, dest8, DMA_SIZE)) { return false; } // stop the test
spiAddress += DMA_SIZE;
}
Serial.println(String(": 8-bit Zero/Read test passed!"));
return true;
}
bool spi16BitTest(void) {
size_t spiAddress = 0;
int spiPhase = 0;
constexpr uint16_t MASK160 = 0xaaaa;
constexpr uint16_t MASK161 = 0x5555;
constexpr int NUM_DATA = DMA_SIZE / sizeof(uint16_t);
uint16_t src16[NUM_DATA];
uint16_t dest16[NUM_DATA];
// Write to the memory using 16-bit transfers
Serial.println("\nStarting 16-bit test");
while (spiPhase < 4) {
spiAddress = 0;
while (spiAddress < SPI_MAX_ADDR) {
// fill the write data buffer
switch (spiPhase) {
case 0 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ( (spiAddress+i) & 0xffff) ^ MASK160;
}
break;
case 1 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ((spiAddress+i) & 0xffff) ^ MASK161;
}
break;
case 2 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK160);
}
break;
case 3 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK161);
}
break;
}
// Send the DMA transfer
spiMem1.write16(spiAddress, src16, NUM_DATA);
// wait until write is done
while (spiMem1.isWriteBusy()) {}
spiAddress += DMA_SIZE;
}
// Read back the data using 8-bit transfers
spiAddress = 0;
while (spiAddress < SPI_MAX_ADDR) {
// generate the golden data
switch (spiPhase) {
case 0 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ( (spiAddress+i) & 0xffff) ^ MASK160;
}
break;
case 1 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ((spiAddress+i) & 0xffff) ^ MASK161;
}
break;
case 2 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK160);
}
break;
case 3 :
for (int i=0; i<NUM_DATA; i++) {
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK161);
}
break;
}
// Read back the DMA data
spiMem1.read16(spiAddress, dest16, NUM_DATA);
// wait until read is done
while (spiMem1.isReadBusy()) {}
if (!compareBuffers16(src16, dest16, NUM_DATA)) {
Serial.print("ERROR @"); Serial.println(spiAddress, HEX);
return false; } // stop the test
spiAddress += DMA_SIZE;
}
Serial.println(String("Phase ") + spiPhase + String(": 16-bit test passed!"));
spiPhase++;
}
#ifdef SANITY
for (int i=0; i<NUM_DATA; i++) {
Serial.print(src16[i], HEX); Serial.print("="); Serial.println(dest16[i],HEX);
}
#endif
Serial.println("\nStarting 16-bit test Zero/Read");
// Clear the memory
spiAddress = 0;
memset(src16, 0, DMA_SIZE);
while (spiAddress < SPI_MAX_ADDR) {
// Send the DMA transfer
spiMem1.zero16(spiAddress, NUM_DATA);
// wait until write is done
while (spiMem1.isWriteBusy()) {}
spiAddress += DMA_SIZE;
}
// Check the memory is clear
spiAddress = 0;
while (spiAddress < SPI_MAX_ADDR) {
// Read back the DMA data
spiMem1.read16(spiAddress, dest16, NUM_DATA);
// wait until read is done
while (spiMem1.isReadBusy()) {}
if (!compareBuffers16(src16, dest16, NUM_DATA)) { return false; } // stop the test
spiAddress += DMA_SIZE;
}
Serial.println(String(": 16-bit Zero/Read test passed!"));
return true;
}
void loop() {
spi8BitTest();
spi16BitTest();
while(true) {}
}

@ -0,0 +1,162 @@
/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* AudioEffectAnalogDelay is a class for simulating a classic BBD based delay
* like the Boss DM-2. This class works with either internal RAM, or external
* SPI RAM for longer delays. The exteranl ram uses DMA to minimize load on the
* CPU.
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H
#define __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H
#include <Audio.h>
#include "LibBasicFunctions.h"
namespace BAGuitar {
/**************************************************************************//**
* AudioEffectAnalogDelay models BBD based analog delays. It provides controls
* for delay, feedback (or regen), mix and output level. All parameters can be
* controlled by MIDI. The class supports internal memory, or external SPI
* memory by providing an ExtMemSlot. External memory access uses DMA to reduce
* process load.
*****************************************************************************/
class AudioEffectAnalogDelay : public AudioStream {
public:
///< List of AudioEffectAnalogDelay MIDI controllable parameters
enum {
BYPASS = 0, ///< controls effect bypass
DELAY, ///< controls the amount of delay
FEEDBACK, ///< controls the amount of echo feedback (regen)
MIX, ///< controls the the mix of input and echo signals
VOLUME, ///< controls the output volume level
NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls
};
AudioEffectAnalogDelay() = delete;
// *** CONSTRUCTORS ***
/// Construct an analog delay using internal memory by specifying the maximum
/// delay in milliseconds.
/// @param maxDelayMs maximum delay in milliseconds. Larger delays use more memory.
AudioEffectAnalogDelay(float maxDelayMs);
/// Construct an analog delay using internal memory by specifying the maximum
/// delay in audio samples.
/// @param numSamples maximum delay in audio samples. Larger delays use more memory.
AudioEffectAnalogDelay(size_t numSamples);
/// Construct an analog delay using external SPI via an ExtMemSlot. The amount of
/// delay will be determined by the amount of memory in the slot.
/// @param slot A pointer to the ExtMemSlot to use for the delay.
AudioEffectAnalogDelay(ExtMemSlot *slot); // requires sufficiently sized pre-allocated memory
virtual ~AudioEffectAnalogDelay(); ///< Destructor
// *** PARAMETERS ***
/// Set the delay in milliseconds.
/// @param milliseconds the request delay in milliseconds. Must be less than max delay.
void delay(float milliseconds);
/// Set the delay in number of audio samples.
/// @param delaySamples the request delay in audio samples. Must be less than max delay.
void delay(size_t delaySamples);
/// Bypass the effect.
/// @param byp when true, bypass wil disable the effect, when false, effect is enabled.
/// Note that audio still passes through when bypass is enabled.
void bypass(bool byp) { m_bypass = byp; }
/// Set the amount of echo feedback (a.k.a regeneration).
/// @param feedback a floating point number between 0.0 and 1.0.
void feedback(float feedback) { m_feedback = feedback; }
/// Set the amount of blending between dry and wet (echo) at the output.
/// @param mix When 0.0, output is 100% dry, when 1.0, output is 100% wet. When
/// 0.5, output is 50% Dry, 50% Wet.
void mix(float mix) { m_mix = mix; }
/// Set the output volume. This affect both the wet and dry signals.
/// @details The default is 1.0.
/// @param vol Sets the output volume between -1.0 and +1.0
void volume(float vol) {m_volume = vol; }
// ** ENABLE / DISABLE **
/// Enables audio processing. Note: when not enabled, CPU load is nearly zero.
void enable() { m_enable = true; }
/// Disables audio process. When disabled, CPU load is nearly zero.
void disable() { m_enable = false; }
// ** MIDI **
/// Sets whether MIDI OMNI channel is processig on or off. When on,
/// all midi channels are used for matching CCs.
/// @param isOmni when true, all channels are processed, when false, channel
/// must match configured value.
void setMidiOmni(bool isOmni) { m_isOmni = isOmni; }
/// Configure an effect parameter to be controlled by a MIDI CC
/// number on a particular channel.
/// @param parameter one of the parameter names in the class enum
/// @param midiCC the CC number from 0 to 127
/// @param midiChannel the effect will only response to the CC on this channel
/// when OMNI mode is off.
void mapMidiControl(int parameter, int midiCC, int midiChannel = 0);
/// process a MIDI Continous-Controller (CC) message
/// @param channel the MIDI channel from 0 to 15)
/// @param midiCC the CC number from 0 to 127
/// @param value the CC value from 0 to 127
void processMidi(int channel, int midiCC, int value);
virtual void update(void); ///< update automatically called by the Teesny Audio Library
private:
audio_block_t *m_inputQueueArray[1];
bool m_isOmni = false;
bool m_bypass = true;
bool m_enable = false;
bool m_externalMemory = false;
AudioDelay *m_memory = nullptr;
size_t m_maxDelaySamples = 0;
audio_block_t *m_previousBlock = nullptr;
audio_block_t *m_blockToRelease = nullptr;
IirBiQuadFilterHQ *m_iir = nullptr;
// Controls
int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping
size_t m_delaySamples = 0;
float m_feedback = 0.0f;
float m_mix = 0.0f;
float m_volume = 1.0f;
void m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet);
void m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet);
size_t m_callCount = 0;
};
}
#endif /* __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H */

@ -22,8 +22,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/ *****************************************************************************/
#ifndef __INC_BAAUDIOCONTROLWM8731_H #ifndef __BAGUITAR__BAAUDIOCONTROLWM8731_H
#define __INC_BAAUDIOCONTROLWM8731_H #define __BAGUITAR__BAAUDIOCONTROLWM8731_H
namespace BAGuitar { namespace BAGuitar {
@ -128,4 +128,4 @@ private:
} /* namespace BAGuitar */ } /* namespace BAGuitar */
#endif /* __INC_BAAUDIOCONTROLWM8731_H */ #endif /* __BAGUITAR__BAAUDIOCONTROLWM8731_H */

@ -20,8 +20,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/ *****************************************************************************/
#ifndef __SRC_BAGPIO_H #ifndef __BAGUITAR_BAGPIO_H
#define __SRC_BAGPIO_H #define __BAGUITAR_BAGPIO_H
#include "BAHardware.h" #include "BAHardware.h"
@ -35,6 +35,7 @@ namespace BAGuitar {
*****************************************************************************/ *****************************************************************************/
class BAGpio { class BAGpio {
public: public:
/// Construct a GPIO object for controlling the various GPIO and user pins
BAGpio(); BAGpio();
virtual ~BAGpio(); virtual ~BAGpio();
@ -72,4 +73,4 @@ private:
} /* namespace BAGuitar */ } /* namespace BAGuitar */
#endif /* __SRC_BAGPIO_H */ #endif /* __BAGUITAR_BAGPIO_H */

@ -18,14 +18,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef __SRC_BATGUITAR_H #ifndef __BATGUITAR_H
#define __SRC_BATGUITAR_H #define __BATGUITAR_H
#include "BAHardware.h" // contains the Blackaddr hardware board definitions #include "BAHardware.h" // contains the Blackaddr hardware board definitions
#include "BATypes.h"
#include "BAAudioControlWM8731.h" // Codec Control #include "BAAudioControlWM8731.h" // Codec Control
#include "BASpiMemory.h" #include "BASpiMemory.h"
#include "BAGpio.h" #include "BAGpio.h"
#include "BAAudioEffectDelayExternal.h" #include "BAAudioEffectDelayExternal.h"
#include "AudioEffectAnalogDelay.h"
#include "LibBasicFunctions.h"
#include "LibMemoryManagement.h"
#endif /* __SRC_BATGUITAR_H */ #endif /* __BATGUITAR_H */

@ -20,8 +20,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/ *****************************************************************************/
#ifndef SRC_BAHARDWARE_H_ #ifndef __BAGUTIAR_BAHARDWARE_H
#define SRC_BAHARDWARE_H_ #define __BAGUTIAR_BAHARDWARE_H
#include <cstdint>
/**************************************************************************//** /**************************************************************************//**
* BAGuitar is a namespace/Library for Guitar processing from Blackaddr Audio. * BAGuitar is a namespace/Library for Guitar processing from Blackaddr Audio.
@ -57,19 +59,24 @@ enum class GPIO : uint8_t {
/**************************************************************************//** /**************************************************************************//**
* Optionally installed SPI RAM * Optionally installed SPI RAM
*****************************************************************************/ *****************************************************************************/
constexpr unsigned NUM_MEM_SLOTS = 2;
enum MemSelect : unsigned { enum MemSelect : unsigned {
MEM0 = 0, ///< SPI RAM MEM0 MEM0 = 0, ///< SPI RAM MEM0
MEM1 = 1 ///< SPI RAM MEM1 MEM1 = 1 ///< SPI RAM MEM1
}; };
constexpr int MEM0_MAX_ADDR = 131071; ///< Max address size per chip
constexpr int MEM1_MAX_ADDR = 131071; ///< Max address size per chip /**************************************************************************//**
* Set the maximum address (byte-based) in the external SPI memories
*****************************************************************************/
constexpr size_t MEM_MAX_ADDR[NUM_MEM_SLOTS] = { 131071, 131071 };
/**************************************************************************//** /**************************************************************************//**
* General Purpose SPI Interfaces * General Purpose SPI Interfaces
*****************************************************************************/ *****************************************************************************/
enum class SpiDeviceId { enum SpiDeviceId : unsigned {
SPI_DEVICE0, ///< Arduino SPI device SPI_DEVICE0 = 0, ///< Arduino SPI device
SPI_DEVICE1 ///< Arduino SPI1 device SPI_DEVICE1 = 1 ///< Arduino SPI1 device
}; };
constexpr int SPI_MAX_ADDR = 131071; ///< Max address size per chip constexpr int SPI_MAX_ADDR = 131071; ///< Max address size per chip
@ -83,4 +90,4 @@ constexpr int SPI_MAX_ADDR = 131071; ///< Max address size per chip
} // namespace BAGuitar } // namespace BAGuitar
#endif /* SRC_BAHARDWARE_H_ */ #endif /* __BAGUTIAR_BAHARDWARE_H */

@ -1,155 +0,0 @@
/*
* BASpiMemory.cpp
*
* Created on: May 22, 2017
* Author: slascos
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Arduino.h"
#include "BASpiMemory.h"
namespace BAGuitar {
// MEM0 Settings
constexpr int SPI_CS_MEM0 = 15;
constexpr int SPI_MOSI_MEM0 = 7;
constexpr int SPI_MISO_MEM0 = 8;
constexpr int SPI_SCK_MEM0 = 14;
// MEM1 Settings
constexpr int SPI_CS_MEM1 = 31;
constexpr int SPI_MOSI_MEM1 = 21;
constexpr int SPI_MISO_MEM1 = 5;
constexpr int SPI_SCK_MEM1 = 20;
// SPI Constants
constexpr int SPI_WRITE_CMD = 0x2;
constexpr int SPI_READ_CMD = 0x3;
constexpr int SPI_ADDR_2_MASK = 0xFF0000;
constexpr int SPI_ADDR_2_SHIFT = 16;
constexpr int SPI_ADDR_1_MASK = 0x00FF00;
constexpr int SPI_ADDR_1_SHIFT = 8;
constexpr int SPI_ADDR_0_MASK = 0x0000FF;
BASpiMemory::BASpiMemory(SpiDeviceId memDeviceId)
{
m_memDeviceId = memDeviceId;
m_settings = {20000000, MSBFIRST, SPI_MODE0};
}
BASpiMemory::BASpiMemory(SpiDeviceId memDeviceId, uint32_t speedHz)
{
m_memDeviceId = memDeviceId;
m_settings = {speedHz, MSBFIRST, SPI_MODE0};
}
// Intitialize the correct Arduino SPI interface
void BASpiMemory::begin()
{
switch (m_memDeviceId) {
case SpiDeviceId::SPI_DEVICE0 :
m_csPin = SPI_CS_MEM0;
m_spi = &SPI;
m_spi->setMOSI(SPI_MOSI_MEM0);
m_spi->setMISO(SPI_MISO_MEM0);
m_spi->setSCK(SPI_SCK_MEM0);
m_spi->begin();
break;
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
case SpiDeviceId::SPI_DEVICE1 :
m_csPin = SPI_CS_MEM1;
m_spi = &SPI1;
m_spi->setMOSI(SPI_MOSI_MEM1);
m_spi->setMISO(SPI_MISO_MEM1);
m_spi->setSCK(SPI_SCK_MEM1);
m_spi->begin();
break;
#endif
default :
// unreachable since memDeviceId is an enumerated class
return;
}
pinMode(m_csPin, OUTPUT);
digitalWrite(m_csPin, HIGH);
}
BASpiMemory::~BASpiMemory() {
}
// Single address write
void BASpiMemory::write(int address, int data)
{
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer(SPI_WRITE_CMD);
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
m_spi->transfer((address & SPI_ADDR_0_MASK));
m_spi->transfer(data);
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
void BASpiMemory::write16(int address, uint16_t data)
{
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer16((SPI_WRITE_CMD << 8) | (address >> 16) );
m_spi->transfer16(address & 0xFFFF);
m_spi->transfer16(data);
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
// single address read
int BASpiMemory::read(int address)
{
int data;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer(SPI_READ_CMD);
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
m_spi->transfer((address & SPI_ADDR_0_MASK));
data = m_spi->transfer(0);
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
return data;
}
uint16_t BASpiMemory::read16(int address)
{
uint16_t data;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer16((SPI_READ_CMD << 8) | (address >> 16) );
m_spi->transfer16(address & 0xFFFF);
data = m_spi->transfer16(0);
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
return data;
}
} /* namespace BAGuitar */

@ -4,7 +4,8 @@
* @company Blackaddr Audio * @company Blackaddr Audio
* *
* BASpiMemory is convenience class for accessing the optional SPI RAMs on * BASpiMemory is convenience class for accessing the optional SPI RAMs on
* the GTA Series boards. * the GTA Series boards. BASpiMemoryDma works the same but uses DMA to reduce
* load on the processor.
* *
* @copyright This program is free software: you can redistribute it and/or modify * @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -19,11 +20,13 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/ *****************************************************************************/
#ifndef __SRC_BASPIMEMORY_H #ifndef __BAGUITAR_BASPIMEMORY_H
#define __SRC_BASPIMEMORY_H #define __BAGUITAR_BASPIMEMORY_H
#include <SPI.h> #include <SPI.h>
#include <DmaSpi.h>
#include "BATypes.h"
#include "BAHardware.h" #include "BAHardware.h"
namespace BAGuitar { namespace BAGuitar {
@ -39,43 +42,174 @@ public:
BASpiMemory() = delete; BASpiMemory() = delete;
/// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2). /// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2).
/// @details default is 20 Mhz /// @details default is 20 Mhz
/// @param memDeviceId specify which MEM to controlw with SpiDeviceId. /// @param memDeviceId specify which MEM to control with SpiDeviceId.
BASpiMemory(SpiDeviceId memDeviceId); BASpiMemory(SpiDeviceId memDeviceId);
/// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2) /// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2)
/// @param memDeviceId specify which MEM to controlw with SpiDeviceId. /// @param memDeviceId specify which MEM to control with SpiDeviceId.
/// @param speedHz specify the desired speed in Hz. /// @param speedHz specify the desired speed in Hz.
BASpiMemory(SpiDeviceId memDeviceId, uint32_t speedHz); BASpiMemory(SpiDeviceId memDeviceId, uint32_t speedHz);
virtual ~BASpiMemory(); virtual ~BASpiMemory();
void begin(void); /// initialize and configure the SPI peripheral
virtual void begin();
/// write a single data word to the specified address /// write a single 8-bit word to the specified address
/// @param address the address in the SPI RAM to write to /// @param address the address in the SPI RAM to write to
/// @param data the value to write /// @param data the value to write
void write(int address, int data); void write(size_t address, uint8_t data);
void write16(int address, uint16_t data); /// Write a block of 8-bit data to the specified address
/// @param address the address in the SPI RAM to write to
/// @param src pointer to the source data block
/// @param numBytes size of the data block in bytes
virtual void write(size_t address, uint8_t *src, size_t numBytes);
/// Write a block of zeros to the specified address
/// @param address the address in the SPI RAM to write to
/// @param numBytes size of the data block in bytes
virtual void zero(size_t address, size_t numBytes);
/// write a single 16-bit word to the specified address
/// @param address the address in the SPI RAM to write to
/// @param data the value to write
void write16(size_t address, uint16_t data);
/// Write a block of 16-bit data to the specified address
/// @param address the address in the SPI RAM to write to
/// @param src pointer to the source data block
/// @param numWords size of the data block in 16-bit words
virtual void write16(size_t address, uint16_t *src, size_t numWords);
/// Write a block of 16-bit zeros to the specified address
/// @param address the address in the SPI RAM to write to
/// @param numWords size of the data block in 16-bit words
virtual void zero16(size_t address, size_t numWords);
/// read a single 8-bit data word from the specified address /// read a single 8-bit data word from the specified address
/// @param address the address in the SPI RAM to read from /// @param address the address in the SPI RAM to read from
/// @return the data that was read /// @return the data that was read
int read(int address); uint8_t read(size_t address);
/// Read a block of 8-bit data from the specified address
/// @param address the address in the SPI RAM to write to
/// @param dest pointer to the destination
/// @param numBytes size of the data block in bytes
virtual void read(size_t address, uint8_t *dest, size_t numBytes);
/// read a single 16-bit data word from the specified address /// read a single 16-bit data word from the specified address
/// @param address the address in the SPI RAM to read from /// @param address the address in the SPI RAM to read from
/// @return the data that was read /// @return the data that was read
uint16_t read16(int address); uint16_t read16(size_t address);
private: /// read a block 16-bit data word from the specified address
/// @param address the address in the SPI RAM to read from
/// @param dest the pointer to the destination
/// @param numWords the number of 16-bit words to transfer
virtual void read16(size_t address, uint16_t *dest, size_t numWords);
/// Check if the class has been configured by a previous begin() call
/// @returns true if initialized, false if not yet initialized
bool isStarted() const { return m_started; }
protected:
SPIClass *m_spi = nullptr; SPIClass *m_spi = nullptr;
SpiDeviceId m_memDeviceId; // the MEM device being control with this instance SpiDeviceId m_memDeviceId; // the MEM device being control with this instance
uint8_t m_csPin; // the IO pin number for the CS on the controlled SPI device uint8_t m_csPin; // the IO pin number for the CS on the controlled SPI device
SPISettings m_settings; // the Wire settings for this SPI port SPISettings m_settings; // the Wire settings for this SPI port
bool m_started = false;
};
class BASpiMemoryDMA : public BASpiMemory {
public:
BASpiMemoryDMA() = delete;
/// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2).
/// @details default is 20 Mhz
/// @param memDeviceId specify which MEM to control with SpiDeviceId.
BASpiMemoryDMA(SpiDeviceId memDeviceId);
/// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2)
/// @param memDeviceId specify which MEM to control with SpiDeviceId.
/// @param speedHz specify the desired speed in Hz.
BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz);
virtual ~BASpiMemoryDMA();
/// initialize and configure the SPI peripheral
void begin() override;
/// Write a block of 8-bit data to the specified address
/// @param address the address in the SPI RAM to write to
/// @param src pointer to the source data block
/// @param numBytes size of the data block in bytes
void write(size_t address, uint8_t *src, size_t numBytes) override;
/// Write a block of zeros to the specified address
/// @param address the address in the SPI RAM to write to
/// @param numBytes size of the data block in bytes
void zero(size_t address, size_t numBytes) override;
/// Write a block of 16-bit data to the specified address
/// @param address the address in the SPI RAM to write to
/// @param src pointer to the source data block
/// @param numWords size of the data block in 16-bit words
void write16(size_t address, uint16_t *src, size_t numWords) override;
/// Write a block of 16-bit zeros to the specified address
/// @param address the address in the SPI RAM to write to
/// @param numWords size of the data block in 16-bit words
void zero16(size_t address, size_t numWords) override;
/// Read a block of 8-bit data from the specified address
/// @param address the address in the SPI RAM to write to
/// @param dest pointer to the destination
/// @param numBytes size of the data block in bytes
void read(size_t address, uint8_t *dest, size_t numBytes) override;
/// read a block 16-bit data word from the specified address
/// @param address the address in the SPI RAM to read from
/// @param dest the pointer to the destination
/// @param numWords the number of 16-bit words to transfer
void read16(size_t address, uint16_t *dest, size_t numWords) override;
/// Check if a DMA write is in progress
/// @returns true if a write DMA is in progress, else false
bool isWriteBusy() const;
/// Check if a DMA read is in progress
/// @returns true if a read DMA is in progress, else false
bool isReadBusy() const;
/// Readout the 8-bit contents of the DMA storage buffer to the specified destination
/// @param dest pointer to the destination
/// @param numBytes number of bytes to read out
/// @param byteOffset, offset from the start of the DMA buffer in bytes to begin reading
void readBufferContents(uint8_t *dest, size_t numBytes, size_t byteOffset = 0);
/// Readout the 8-bit contents of the DMA storage buffer to the specified destination
/// @param dest pointer to the destination
/// @param numWords number of 16-bit words to read out
/// @param wordOffset, offset from the start of the DMA buffer in words to begin reading
void readBufferContents(uint16_t *dest, size_t numWords, size_t wordOffset = 0);
private:
DmaSpiGeneric *m_spiDma = nullptr;
AbstractChipSelect *m_cs = nullptr;
uint8_t *m_txCommandBuffer = nullptr;
DmaSpi::Transfer *m_txTransfer;
uint8_t *m_rxCommandBuffer = nullptr;
DmaSpi::Transfer *m_rxTransfer;
uint16_t m_txXferCount;
uint16_t m_rxXferCount;
void m_setSpiCmdAddr(int command, size_t address, uint8_t *dest);
}; };
class BASpiMemoryException {};
} /* namespace BAGuitar */ } /* namespace BAGuitar */
#endif /* __SRC_BASPIMEMORY_H */ #endif /* __BAGUITAR_BASPIMEMORY_H */

@ -0,0 +1,158 @@
/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* This file contains some custom types used by the rest of the BAGuitar library.
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __BAGUITAR_BATYPES_H
#define __BAGUITAR_BATYPES_H
namespace BAGuitar {
#define UNUSED(x) (void)(x)
/**************************************************************************//**
* Customer RingBuffer with random access
*****************************************************************************/
template <class T>
class RingBuffer {
public:
RingBuffer() = delete;
/// Construct a RingBuffer of specified max size
/// @param maxSize number of entries in ring buffer
RingBuffer(const size_t maxSize) : m_maxSize(maxSize) {
m_buffer = new T[maxSize]();
}
virtual ~RingBuffer(){
if (m_buffer) delete [] m_buffer;
}
/// Add an element to the back of the queue
/// @param element element to add to queue
/// returns 0 if success, otherwise error
int push_back(T element) {
//Serial.println(String("RingBuffer::push_back...") + m_head + String(":") + m_tail + String(":") + m_size);
if ( (m_head == m_tail) && (m_size > 0) ) {
// overflow
Serial.println("RingBuffer::push_back: overflow");
return -1;
}
m_buffer[m_head] = element;
if (m_head < (m_maxSize-1) ) {
m_head++;
} else {
m_head = 0;
}
m_size++;
return 0;
}
/// Remove the element at teh front of the queue
/// @returns 0 if success, otherwise error
int pop_front() {
if (m_size == 0) {
// buffer is empty
//Serial.println("RingBuffer::pop_front: buffer is empty\n");
return -1;
}
if (m_tail < m_maxSize-1) {
m_tail++;
} else {
m_tail = 0;
}
m_size--;
//Serial.println(String("RingBuffer::pop_front: ") + m_head + String(":") + m_tail + String(":") + m_size);
return 0;
}
/// Get the element at the front of the queue
/// @returns element at front of queue
T front() const {
return m_buffer[m_tail];
}
/// get the element at the back of the queue
/// @returns element at the back of the queue
T back() const {
return m_buffer[m_head-1];
}
/// Get a previously pushed elememt
/// @param offset zero is last pushed, 1 is second last, etc.
/// @returns the absolute index corresponding to the requested offset.
size_t get_index_from_back(size_t offset = 0) const {
// the target at m_head - 1 - offset or m_maxSize + m_head -1 - offset;
size_t idx = (m_maxSize + m_head -1 - offset);
if ( idx >= m_maxSize) {
idx -= m_maxSize;
}
return idx;
}
/// get the current size of the queue
/// @returns size of the queue
size_t size() const {
return m_size;
}
/// get the maximum size the queue can hold
/// @returns maximum size of the queue
size_t max_size() const {
return m_maxSize;
}
/// get the element at the specified absolute index
/// @param index element to retrieve from absolute queue position
/// @returns the request element
T& operator[] (size_t index) {
return m_buffer[index];
}
/// get the element at the specified absolute index
/// @param index element to retrieve from absolute queue position
/// @returns the request element
T at(size_t index) const {
return m_buffer[index];
}
/// DEBUG: Prints the status of the Ringbuffer. NOte using this much printing will usually cause audio glitches
void print() const {
for (int idx=0; idx<m_maxSize; idx++) {
Serial.print(idx + String(" address: ")); Serial.print((uint32_t)m_buffer[idx], HEX);
Serial.print(" data: "); Serial.println((uint32_t)m_buffer[idx]->data, HEX);
}
}
private:
size_t m_head=0; ///< back of the queue
size_t m_tail=0; ///< front of the queue
size_t m_size=0; ///< current size of the qeueu
T *m_buffer = nullptr; ///< pointer to the allocated buffer array
const size_t m_maxSize; ///< maximum size of the queue
};
} // BAGuitar
#endif /* __BAGUITAR_BATYPES_H */

@ -0,0 +1,283 @@
/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* LibBasicFunctions is a collection of helpful functions and classes that make
* it easier to perform common tasks in Audio applications.
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#include <cstddef>
#include <new>
#include <arm_math.h>
#include "Arduino.h"
#include "Audio.h"
#include "BATypes.h"
#include "LibMemoryManagement.h"
#ifndef __BAGUITAR_LIBBASICFUNCTIONS_H
#define __BAGUITAR_LIBBASICFUNCTIONS_H
namespace BAGuitar {
/**************************************************************************//**
* QueuePosition is used for storing the index (in an array of queues) and the
* offset within an audio_block_t data buffer. Useful for dealing with large
* windows of audio spread across multiple audio data blocks.
*****************************************************************************/
struct QueuePosition {
int offset; ///< offset in samples within an audio_block_t data buffer
int index; ///< index in an array of audio data blocks
};
/// Calculate the exact sample position in an array of audio blocks that corresponds
/// to a particular offset given as time.
/// @param milliseconds length of the interval in milliseconds
/// @returns a struct containing the index and offset
QueuePosition calcQueuePosition(float milliseconds);
/// Calculate the exact sample position in an array of audio blocks that corresponds
/// to a particular offset given as a number of samples
/// @param milliseconds length of the interval in milliseconds
/// @returns a struct containing the index and offset
QueuePosition calcQueuePosition(size_t numSamples);
/// Calculate the number of audio samples (rounded up) that correspond to a
/// given length of time.
/// @param milliseconds length of the interval in milliseconds
/// @returns the number of corresonding audio samples.
size_t calcAudioSamples(float milliseconds);
/// Calculate a length of time in milliseconds from the number of audio samples.
/// @param numSamples Number of audio samples to convert to time
/// @return the equivalent time in milliseconds.
float calcAudioTimeMs(size_t numSamples);
/// Calculate the number of audio samples (usually an offset) from
/// a queue position.
/// @param position specifies the index and offset within a queue
/// @returns the number of samples from the start of the queue array to the
/// specified position.
size_t calcOffset(QueuePosition position);
/// Clear the contents of an audio block to zero
/// @param block pointer to the audio block to clear
void clearAudioBlock(audio_block_t *block);
/// Perform an alpha blend between to audio blocks. Performs <br>
/// out = dry*(1-mix) + wet*(mix)
/// @param out pointer to the destination audio block
/// @param dry pointer to the dry audio
/// @param wet pointer to the wet audio
/// @param mix float between 0.0 and 1.0.
void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix);
/// Applies a gain to the audio via fixed-point scaling accoring to <br>
/// out = int * (vol * 2^coeffShift)
/// @param out pointer to output audio block
/// @param in pointer to input audio block
/// @param vol volume cofficient between -1.0 and +1.0
/// @param coeffShift number of bits to shiftt the coefficient
void gainAdjust(audio_block_t *out, audio_block_t *in, float vol, int coeffShift = 0);
template <class T>
class RingBuffer; // forward declare so AudioDelay can use it.
/**************************************************************************//**
* Audio delays are a very common function in audio processing. In addition to
* being used for simply create a delay effect, it can also be used for buffering
* a sliding window in time of audio samples. This is useful when combining
* several audio_block_t data buffers together to form one large buffer for
* FFTs, etc.
* @details The buffer works like a queue. You add new audio_block_t when available,
* and the class will return an old buffer when it is to be discarded from the queue.<br>
* Note that using INTERNAL memory means the class will only store a queue
* of pointers to audio_block_t buffers, since the Teensy Audio uses a shared memory
* approach. When using EXTERNAL memory, data is actually copyied to/from an external
* SRAM device.
*****************************************************************************/
constexpr size_t AUDIO_BLOCK_SIZE = sizeof(int16_t)*AUDIO_BLOCK_SAMPLES;
class AudioDelay {
public:
AudioDelay() = delete;
/// Construct an audio buffer using INTERNAL memory by specifying the max number
/// of audio samples you will want.
/// @param maxSamples equal or greater than your longest delay requirement
AudioDelay(size_t maxSamples);
/// Construct an audio buffer using INTERNAL memory by specifying the max amount of
/// time you will want available in the buffer.
/// @param maxDelayTimeMs max length of time you want in the buffer specified in milliseconds
AudioDelay(float maxDelayTimeMs);
/// Construct an audio buffer using a slot configured with the BAGuitar::ExternalSramManager
/// @param slot a pointer to the slot representing the memory you wish to use for the buffer.
AudioDelay(ExtMemSlot *slot);
~AudioDelay();
/// Add a new audio block into the buffer. When the buffer is filled,
/// adding a new block will push out the oldest once which is returned.
/// @param blockIn pointer to the most recent block of audio
/// @returns the buffer to be discarded, or nullptr if not filled (INTERNAL), or
/// not applicable (EXTERNAL).
audio_block_t *addBlock(audio_block_t *blockIn);
/// When using INTERNAL memory, returns the pointer for the specified index into buffer.
/// @details, the most recent block is 0, 2nd most recent is 1, ..., etc.
/// @param index the specifies how many buffers older than the current to retrieve
/// @returns a pointer to the requested audio_block_t
audio_block_t *getBlock(size_t index);
/// Retrieve an audio block (or samples) from the buffer.
/// @details when using INTERNAL memory, only supported size is AUDIO_BLOCK_SAMPLES. When using
/// EXTERNAL, a size smaller than AUDIO_BLOCK_SAMPLES can be requested.
/// @param dest pointer to the target audio block to write the samples to.
/// @param offsetSamples data will start being transferred offset samples from the start of the audio buffer
/// @param numSamples default value is AUDIO_BLOCK_SAMPLES, so typically you don't have to specify this parameter.
/// @returns true on success, false on error.
bool getSamples(audio_block_t *dest, size_t offsetSamples, size_t numSamples = AUDIO_BLOCK_SAMPLES);
/// When using EXTERNAL memory, this function can return a pointer to the underlying ExtMemSlot object associated
/// with the buffer.
/// @returns pointer to the underlying ExtMemSlot.
ExtMemSlot *getSlot() const { return m_slot; }
/// Ween using INTERNAL memory, thsi function can return a pointer to the underlying RingBuffer that contains
/// audio_block_t * pointers.
/// @returns pointer to the underlying RingBuffer
RingBuffer<audio_block_t*> *getRingBuffer() const { return m_ringBuffer; }
private:
/// enumerates whether the underlying memory buffer uses INTERNAL or EXTERNAL memory
enum class MemType : unsigned {
MEM_INTERNAL = 0, ///< internal audio_block_t from the Teensy Audio Library is used
MEM_EXTERNAL ///< external SPI based ram is used
};
MemType m_type; ///< when 0, INTERNAL memory, when 1, external MEMORY.
RingBuffer<audio_block_t *> *m_ringBuffer = nullptr; ///< When using INTERNAL memory, a RingBuffer will be created.
ExtMemSlot *m_slot = nullptr; ///< When using EXTERNAL memory, an ExtMemSlot must be provided.
};
/**************************************************************************//**
* IIR BiQuad Filter - Direct Form I <br>
* y[n] = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] + a1 * y[n-1] + a2 * y[n-2]<br>
* Some design tools (like Matlab assume the feedback coefficients 'a' are negated. You
* may have to negate your 'a' coefficients.
* @details Note that the ARM CMSIS-DSP library requires an extra zero between first
* and second 'b' coefficients. E.g. <br>
* {b10, 0, b11, b12, a11, a12, b20, 0, b21, b22, a21, a22, ...}
*****************************************************************************/
class IirBiQuadFilter {
public:
IirBiQuadFilter() = delete;
/// Construct a Biquad filter with specified number of stages, coefficients and scaling.
/// @details See CMSIS-DSP documentation for more details
/// @param numStages number of biquad stages. Each stage has 5 coefficients.
/// @param coeffs pointer to an array of Q31 fixed-point coefficients (range -1 to +0.999...)
/// @param coeffShift coeffs are multiplied by 2^coeffShift to support coefficient range scaling
IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift = 0);
virtual ~IirBiQuadFilter();
/// Process the data using the configured IIR filter
/// @details output and input can be the same pointer if in-place modification is desired
/// @param output pointer to where the output results will be written
/// @param input pointer to where the input data will be read from
/// @param numSampmles number of samples to process
bool process(int16_t *output, int16_t *input, size_t numSamples);
private:
const unsigned NUM_STAGES;
int32_t *m_coeffs = nullptr;
// ARM DSP Math library filter instance
arm_biquad_casd_df1_inst_q31 m_iirCfg;
int32_t *m_state = nullptr;
};
/**************************************************************************//**
* A High-precision version of IirBiQuadFilter often necessary for complex, multistage
* filters. This class uses CMSIS-DSP biquads with 64-bit internal precision instead
* of 32-bit.
*****************************************************************************/
class IirBiQuadFilterHQ {
public:
IirBiQuadFilterHQ() = delete;
/// Construct a Biquad filter with specified number of stages, coefficients and scaling.
/// @details See CMSIS-DSP documentation for more details
/// @param numStages number of biquad stages. Each stage has 5 coefficients.
/// @param coeffs pointer to an array of Q31 fixed-point coefficients (range -1 to +0.999...)
/// @param coeffShift coeffs are multiplied by 2^coeffShift to support coefficient range scaling
IirBiQuadFilterHQ(unsigned numStages, const int32_t *coeffs, int coeffShift = 0);
virtual ~IirBiQuadFilterHQ();
/// Process the data using the configured IIR filter
/// @details output and input can be the same pointer if in-place modification is desired
/// @param output pointer to where the output results will be written
/// @param input pointer to where the input data will be read from
/// @param numSampmles number of samples to process
bool process(int16_t *output, int16_t *input, size_t numSamples);
private:
const unsigned NUM_STAGES;
int32_t *m_coeffs = nullptr;
// ARM DSP Math library filter instance
arm_biquad_cas_df1_32x64_ins_q31 m_iirCfg;
int64_t *m_state = nullptr;
};
/**************************************************************************//**
* A single-precision floating-point biquad using CMSIS-DSP hardware instructions.
* @details Use this when IirBiQuadFilterHQ is insufficient, since that version
* is still faster with 64-bit fixed-point arithmetic.
*****************************************************************************/
class IirBiQuadFilterFloat {
public:
IirBiQuadFilterFloat() = delete;
/// Construct a Biquad filter with specified number of stages and coefficients
/// @details See CMSIS-DSP documentation for more details
/// @param numStages number of biquad stages. Each stage has 5 coefficients.
/// @param coeffs pointer to an array of Q31 fixed-point coefficients (range -1 to +0.999...)
IirBiQuadFilterFloat(unsigned numStages, const float *coeffs);
virtual ~IirBiQuadFilterFloat();
/// Process the data using the configured IIR filter
/// @details output and input can be the same pointer if in-place modification is desired
/// @param output pointer to where the output results will be written
/// @param input pointer to where the input data will be read from
/// @param numberSampmles number of samples to process
bool process(float *output, float *input, size_t numSamples);
private:
const unsigned NUM_STAGES;
float *m_coeffs = nullptr;
// ARM DSP Math library filter instance
arm_biquad_cascade_df2T_instance_f32 m_iirCfg;
float *m_state = nullptr;
};
}
#endif /* __BAGUITAR_LIBBASICFUNCTIONS_H */

@ -0,0 +1,212 @@
/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* LibMemoryManagment is a class for providing access to external SPI based
* SRAM with the optional convience of breaking it up into 'slots' which are smaller
* memory entities.
* @details This class treats an external memory as a pool from which the user requests
* a block. When using that block, the user need not be concerned with where the pool
* it came from, they only deal with offsets from the start of their memory block. Ie.
* Your particular slot of memory appears to you to start at 0 and end at whatever size
* was requested.
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __BAGUITAR_LIBMEMORYMANAGEMENT_H
#define __BAGUITAR_LIBMEMORYMANAGEMENT_H
#include <cstddef>
#include "BAHardware.h"
#include "BASpiMemory.h"
namespace BAGuitar {
/**************************************************************************//**
* MemConfig contains the configuration information associated with a particular
* SPI interface.
*****************************************************************************/
struct MemConfig {
size_t size; ///< the total size of the external SPI memory
size_t totalAvailable; ///< the number of bytes available (remaining)
size_t nextAvailable; ///< the starting point for the next available slot
BASpiMemory *m_spi = nullptr; ///< handle to the SPI interface
};
class ExternalSramManager; // forward declare so ExtMemSlot can declared friendship with it
/**************************************************************************//**
* ExtMemSlot provides a convenient interface to a particular slot of an
* external memory.
* @details the memory can be access randomly, as a single word, as a block of
* data, or as circular queue.
*****************************************************************************/
class ExtMemSlot {
public:
/// clear the entire contents of the slot by writing zeros
/// @returns true on success
bool clear();
/// set a new write position (in bytes) for circular operation
/// @param offsetBytes moves the write pointer to the specified offset from the slot start
/// @returns true on success, else false if offset is beyond slot boundaries.
bool setWritePosition(size_t offsetBytes);
/// returns the currently set write pointer pointer
/// @returns the write position value
size_t getWritePosition() const { return m_currentWrPosition-m_start; }
/// set a new read position (in bytes) for circular operation
/// @param offsetBytes moves the read pointer to the specified offset from the slot start
/// @returns true on success, else false if offset is beyond slot boundaries.
bool setReadPosition(size_t offsetBytes);
/// returns the currently set read pointer pointer
/// @returns the read position value
size_t getReadPosition() const { return m_currentRdPosition-m_start; }
/// Write a block of 16-bit data to the memory at the specified offset
/// @param offsetWords offset in 16-bit words from start of slot
/// @param src pointer to start of block of 16-bit data
/// @param numWords number of 16-bit words to transfer
/// @returns true on success, else false on error
bool write16(size_t offsetWords, int16_t *src, size_t numWords);
/// Write a block of zeros (16-bit) to the memory at the specified offset
/// @param offsetWords offset in 16-bit words from start of slot
/// @param numWords number of 16-bit words to transfer
/// @returns true on success, else false on error
bool zero16(size_t offsetWords, size_t numWords);
/// Read a block of 16-bit data from the memory at the specified location
/// @param offsetWords offset in 16-bit words from start of slot
/// @param dest pointer to destination for the read data
/// @param numWords number of 16-bit words to transfer
/// @returns true on success, else false on error
bool read16(size_t offsetWords, int16_t *dest, size_t numWords);
/// Read the next in memory during circular operation
/// @returns the next 16-bit data word in memory
uint16_t readAdvance16();
/// Read the next block of numWords during circular operation
/// @details, dest is ignored when using DMA
/// @param dest pointer to the destination of the read.
/// @param numWords number of 16-bit words to transfer
/// @returns true on success, else false on error
bool readAdvance16(int16_t *dest, size_t numWords);
/// Write a block of 16-bit data from the specified location in circular operation
/// @param src pointer to the start of the block of data to write to memory
/// @param numWords number of 16-bit words to transfer
/// @returns true on success, else false on error
bool writeAdvance16(int16_t *src, size_t numWords);
/// Write a single 16-bit data to the next location in circular operation
/// @param data the 16-bit word to transfer
/// @returns true on success, else false on error
bool writeAdvance16(int16_t data); // write just one data
/// Write a block of 16-bit data zeros in circular operation
/// @param numWords number of 16-bit words to transfer
/// @returns true on success, else false on error
bool zeroAdvance16(size_t numWords);
/// Get the size of the memory slot
/// @returns size of the slot in bytes
size_t size() const { return m_size; }
/// Ensures the underlying SPI interface is enabled
/// @returns true on success, false on error
bool enable() const;
/// Checks whether underlying SPI interface is enabled
/// @returns true if enabled, false if not enabled
bool isEnabled() const;
bool isUseDma() const { return m_useDma; }
bool isWriteBusy() const;
bool isReadBusy() const;
/// DEBUG USE: prints out the slot member variables
void printStatus(void) const;
private:
friend ExternalSramManager; ///< gives the manager access to the private variables
bool m_valid = false; ///< After a slot is successfully configured by the manager it becomes valid
size_t m_start = 0; ///< the external memory address in bytes where this slot starts
size_t m_end = 0; ///< the external memory address in bytes where this slot ends (inclusive)
size_t m_currentWrPosition = 0; ///< current write pointer for circular operation
size_t m_currentRdPosition = 0; ///< current read pointer for circular operation
size_t m_size = 0; ///< size of this slot in bytes
bool m_useDma = false; ///< when TRUE, BASpiMemoryDMA will be used.
SpiDeviceId m_spiId; ///< the SPI Device ID
BASpiMemory *m_spi = nullptr; ///< pointer to an instance of the BASpiMemory interface class
};
/**************************************************************************//**
* ExternalSramManager provides a class to handle dividing an external SPI RAM
* into independent slots for general use.
* @details the class does not support deallocated memory because this would cause
* fragmentation.
*****************************************************************************/
class ExternalSramManager final {
public:
ExternalSramManager();
/// The manager is constructed by specifying how many external memories to handle allocations for
/// @param numMemories the number of external memories
ExternalSramManager(unsigned numMemories);
virtual ~ExternalSramManager();
/// Query the amount of available (unallocated) memory
/// @details note that currently, memory cannot be allocated.
/// @param mem specifies which memory to query, default is memory 0
/// @returns the available memory in bytes
size_t availableMemory(BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0);
/// Request memory be allocated for the provided slot
/// @param slot a pointer to the global slot object to which memory will be allocated
/// @param delayMilliseconds request the amount of memory based on required time for audio samples, rather than number of bytes.
/// @param mem specify which external memory to allocate from
/// @param useDma when true, DMA is used for SPI port, else transfers block until complete
/// @returns true on success, otherwise false on error
bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, bool useDma = false);
/// Request memory be allocated for the provided slot
/// @param slot a pointer to the global slot object to which memory will be allocated
/// @param sizeBytes request the amount of memory in bytes to request
/// @param mem specify which external memory to allocate from
/// @param useDma when true, DMA is used for SPI port, else transfers block until complete
/// @returns true on success, otherwise false on error
bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, bool useDma = false);
private:
static bool m_configured; ///< there should only be one instance of ExternalSramManager in the whole project
static MemConfig m_memConfig[BAGuitar::NUM_MEM_SLOTS]; ///< store the configuration information for each external memory
};
}
#endif /* __LIBMEMORYMANAGEMENT_H */

@ -0,0 +1,171 @@
/*
* AudioDelay.cpp
*
* Created on: January 1, 2018
* Author: slascos
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Audio.h"
#include "LibBasicFunctions.h"
namespace BAGuitar {
////////////////////////////////////////////////////
// AudioDelay
////////////////////////////////////////////////////
AudioDelay::AudioDelay(size_t maxSamples)
: m_slot(nullptr)
{
m_type = (MemType::MEM_INTERNAL);
// INTERNAL memory consisting of audio_block_t data structures.
QueuePosition pos = calcQueuePosition(maxSamples);
m_ringBuffer = new RingBuffer<audio_block_t *>(pos.index+2); // If the delay is in queue x, we need to overflow into x+1, thus x+2 total buffers.
}
AudioDelay::AudioDelay(float maxDelayTimeMs)
: AudioDelay(calcAudioSamples(maxDelayTimeMs))
{
}
AudioDelay::AudioDelay(ExtMemSlot *slot)
{
m_type = (MemType::MEM_EXTERNAL);
m_slot = slot;
}
AudioDelay::~AudioDelay()
{
if (m_ringBuffer) delete m_ringBuffer;
}
audio_block_t* AudioDelay::addBlock(audio_block_t *block)
{
audio_block_t *blockToRelease = nullptr;
if (m_type == (MemType::MEM_INTERNAL)) {
// INTERNAL memory
// purposefully don't check if block is valid, the ringBuffer can support nullptrs
if ( m_ringBuffer->size() >= m_ringBuffer->max_size() ) {
// pop before adding
blockToRelease = m_ringBuffer->front();
m_ringBuffer->pop_front();
}
// add the new buffer
m_ringBuffer->push_back(block);
return blockToRelease;
} else {
// EXTERNAL memory
if (!m_slot) { Serial.println("addBlock(): m_slot is not valid"); }
if (block) {
// this causes pops
m_slot->writeAdvance16(block->data, AUDIO_BLOCK_SAMPLES);
}
blockToRelease = block;
}
return blockToRelease;
}
audio_block_t* AudioDelay::getBlock(size_t index)
{
audio_block_t *ret = nullptr;
if (m_type == (MemType::MEM_INTERNAL)) {
ret = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index));
}
return ret;
}
bool AudioDelay::getSamples(audio_block_t *dest, size_t offsetSamples, size_t numSamples)
{
if (!dest) {
Serial.println("getSamples(): dest is invalid");
return false;
}
if (m_type == (MemType::MEM_INTERNAL)) {
QueuePosition position = calcQueuePosition(offsetSamples);
size_t index = position.index;
audio_block_t *currentQueue0 = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index));
// The latest buffer is at the back. We need index+1 counting from the back.
audio_block_t *currentQueue1 = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index+1));
// check if either queue is invalid, if so just zero the destination buffer
if ( (!currentQueue0) || (!currentQueue0) ) {
// a valid entry is not in all queue positions while it is filling, use zeros
memset(static_cast<void*>(dest->data), 0, numSamples * sizeof(int16_t));
return true;
}
if (position.offset == 0) {
// single transfer
memcpy(static_cast<void*>(dest->data), static_cast<void*>(currentQueue0->data), numSamples * sizeof(int16_t));
return true;
}
// Otherwise we need to break the transfer into two memcpy because it will go across two source queues.
// Audio is stored in reverse order. That means the first sample (in time) goes in the last location in the audio block.
int16_t *destStart = dest->data;
int16_t *srcStart;
// Break the transfer into two. Copy the 'older' data first then the 'newer' data with respect to current time.
//currentQueue = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index+1)); // The latest buffer is at the back. We need index+1 counting from the back.
srcStart = (currentQueue1->data + AUDIO_BLOCK_SAMPLES - position.offset);
size_t numData = position.offset;
memcpy(static_cast<void*>(destStart), static_cast<void*>(srcStart), numData * sizeof(int16_t));
//currentQueue = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); // now grab the queue where the 'first' data sample was
destStart += numData; // we already wrote numData so advance by this much.
srcStart = (currentQueue0->data);
numData = AUDIO_BLOCK_SAMPLES - numData;
memcpy(static_cast<void*>(destStart), static_cast<void*>(srcStart), numData * sizeof(int16_t));
return true;
} else {
// EXTERNAL Memory
if (numSamples*sizeof(int16_t) <= m_slot->size() ) {
int currentPositionBytes = (int)m_slot->getWritePosition() - (int)(AUDIO_BLOCK_SAMPLES*sizeof(int16_t));
size_t offsetBytes = offsetSamples * sizeof(int16_t);
if ((int)offsetBytes <= currentPositionBytes) {
m_slot->setReadPosition(currentPositionBytes - offsetBytes);
} else {
// It's going to wrap around to the end of the slot
int readPosition = (int)m_slot->size() + currentPositionBytes - offsetBytes;
m_slot->setReadPosition((size_t)readPosition);
}
// This causes pops
m_slot->readAdvance16(dest->data, AUDIO_BLOCK_SAMPLES);
return true;
} else {
// numSampmles is > than total slot size
Serial.println("getSamples(): ERROR numSamples > total slot size");
return false;
}
}
}
}

@ -0,0 +1,78 @@
/*
* AudioHelpers.cpp
*
* Created on: January 1, 2018
* Author: slascos
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Audio.h"
#include "LibBasicFunctions.h"
namespace BAGuitar {
size_t calcAudioSamples(float milliseconds)
{
return (size_t)((milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f);
}
float calcAudioTimeMs(size_t numSamples)
{
return ((float)(numSamples) / AUDIO_SAMPLE_RATE_EXACT) * 1000.0f;
}
QueuePosition calcQueuePosition(size_t numSamples)
{
QueuePosition queuePosition;
queuePosition.index = (int)(numSamples / AUDIO_BLOCK_SAMPLES);
queuePosition.offset = numSamples % AUDIO_BLOCK_SAMPLES;
return queuePosition;
}
QueuePosition calcQueuePosition(float milliseconds) {
size_t numSamples = (int)((milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f);
return calcQueuePosition(numSamples);
}
size_t calcOffset(QueuePosition position)
{
return (position.index*AUDIO_BLOCK_SAMPLES) + position.offset;
}
void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix)
{
// ARM DSP optimized
int16_t wetBuffer[AUDIO_BLOCK_SAMPLES];
int16_t dryBuffer[AUDIO_BLOCK_SAMPLES];
int16_t scaleFractWet = (int16_t)(mix * 32767.0f);
int16_t scaleFractDry = 32767-scaleFractWet;
arm_scale_q15(dry->data, scaleFractDry, 0, dryBuffer, AUDIO_BLOCK_SAMPLES);
arm_scale_q15(wet->data, scaleFractWet, 0, wetBuffer, AUDIO_BLOCK_SAMPLES);
arm_add_q15(wetBuffer, dryBuffer, out->data, AUDIO_BLOCK_SAMPLES);
}
void gainAdjust(audio_block_t *out, audio_block_t *in, float vol, int coeffShift)
{
int16_t scale = (int16_t)(vol * 32767.0f);
arm_scale_q15(in->data, scale, coeffShift, out->data, AUDIO_BLOCK_SAMPLES);
}
void clearAudioBlock(audio_block_t *block)
{
memset(block->data, 0, sizeof(int16_t)*AUDIO_BLOCK_SAMPLES);
}
}

@ -0,0 +1,235 @@
/*
* ExtMemSlot.cpp
*
* Created on: Jan 19, 2018
* Author: slascos
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstring>
#include <new>
#include "Audio.h"
#include "LibMemoryManagement.h"
namespace BAGuitar {
/////////////////////////////////////////////////////////////////////////////
// MEM SLOT
/////////////////////////////////////////////////////////////////////////////
bool ExtMemSlot::clear()
{
if (!m_valid) { return false; }
m_spi->zero16(m_start, m_size);
return true;
}
bool ExtMemSlot::setWritePosition(size_t offsetBytes)
{
if (m_start + offsetBytes <= m_end) {
m_currentWrPosition = m_start + offsetBytes;
return true;
} else { return false; }
}
bool ExtMemSlot::write16(size_t offsetWords, int16_t *src, size_t numWords)
{
if (!m_valid) { return false; }
size_t writeStart = m_start + sizeof(int16_t)*offsetWords; // 2x because int16 is two bytes per data
size_t numBytes = sizeof(int16_t)*numWords;
if ((writeStart + numBytes-1) <= m_end) {
m_spi->write16(writeStart, reinterpret_cast<uint16_t*>(src), numWords); // cast audio data to uint
return true;
} else {
// this would go past the end of the memory slot, do not perform the write
return false;
}
}
bool ExtMemSlot::setReadPosition(size_t offsetBytes)
{
if (m_start + offsetBytes <= m_end) {
m_currentRdPosition = m_start + offsetBytes;
return true;
} else {
return false;
}
}
bool ExtMemSlot::zero16(size_t offsetWords, size_t numWords)
{
if (!m_valid) { return false; }
size_t writeStart = m_start + sizeof(int16_t)*offsetWords;
size_t numBytes = sizeof(int16_t)*numWords;
if ((writeStart + numBytes-1) <= m_end) {
m_spi->zero16(writeStart, numWords); // cast audio data to uint
return true;
} else {
// this would go past the end of the memory slot, do not perform the write
return false;
}
}
bool ExtMemSlot::read16(size_t offsetWords, int16_t *dest, size_t numWords)
{
if (!dest) return false; // invalid destination
size_t readOffset = m_start + sizeof(int16_t)*offsetWords;
size_t numBytes = sizeof(int16_t)*numWords;
if ((readOffset + numBytes-1) <= m_end) {
m_spi->read16(readOffset, reinterpret_cast<uint16_t*>(dest), numWords);
return true;
} else {
// this would go past the end of the memory slot, do not perform the read
return false;
}
}
uint16_t ExtMemSlot::readAdvance16()
{
uint16_t val = m_spi->read16(m_currentRdPosition);
if (m_currentRdPosition < m_end-1) {
m_currentRdPosition +=2; // position is in bytes and we read two
} else {
m_currentRdPosition = m_start;
}
return val;
}
bool ExtMemSlot::readAdvance16(int16_t *dest, size_t numWords)
{
if (!m_valid) { return false; }
size_t numBytes = sizeof(int16_t)*numWords;
if (m_currentRdPosition + numBytes-1 <= m_end) {
// entire block fits in memory slot without wrapping
m_spi->read16(m_currentRdPosition, reinterpret_cast<uint16_t*>(dest), numWords); // cast audio data to uint.
m_currentRdPosition += numBytes;
} else {
// this read will wrap the memory slot
size_t rdBytes = m_end - m_currentRdPosition + 1;
size_t rdDataNum = rdBytes >> 1; // divide by two to get the number of data
m_spi->read16(m_currentRdPosition, reinterpret_cast<uint16_t*>(dest), rdDataNum);
size_t remainingData = numWords - rdDataNum;
m_spi->read16(m_start, reinterpret_cast<uint16_t*>(dest + rdDataNum), remainingData); // write remaining bytes are start
m_currentRdPosition = m_start + (remainingData*sizeof(int16_t));
}
return true;
}
bool ExtMemSlot::writeAdvance16(int16_t *src, size_t numWords)
{
if (!m_valid) { return false; }
size_t numBytes = sizeof(int16_t)*numWords;
if (m_currentWrPosition + numBytes-1 <= m_end) {
// entire block fits in memory slot without wrapping
m_spi->write16(m_currentWrPosition, reinterpret_cast<uint16_t*>(src), numWords); // cast audio data to uint.
m_currentWrPosition += numBytes;
} else {
// this write will wrap the memory slot
size_t wrBytes = m_end - m_currentWrPosition + 1;
size_t wrDataNum = wrBytes >> 1; // divide by two to get the number of data
m_spi->write16(m_currentWrPosition, reinterpret_cast<uint16_t*>(src), wrDataNum);
size_t remainingData = numWords - wrDataNum;
m_spi->write16(m_start, reinterpret_cast<uint16_t*>(src + wrDataNum), remainingData); // write remaining bytes are start
m_currentWrPosition = m_start + (remainingData*sizeof(int16_t));
}
return true;
}
bool ExtMemSlot::zeroAdvance16(size_t numWords)
{
if (!m_valid) { return false; }
size_t numBytes = 2*numWords;
if (m_currentWrPosition + numBytes-1 <= m_end) {
// entire block fits in memory slot without wrapping
m_spi->zero16(m_currentWrPosition, numWords); // cast audio data to uint.
m_currentWrPosition += numBytes;
} else {
// this write will wrap the memory slot
size_t wrBytes = m_end - m_currentWrPosition + 1;
size_t wrDataNum = wrBytes >> 1;
m_spi->zero16(m_currentWrPosition, wrDataNum);
size_t remainingWords = numWords - wrDataNum; // calculate the remaining bytes
m_spi->zero16(m_start, remainingWords); // write remaining bytes are start
m_currentWrPosition = m_start + remainingWords*sizeof(int16_t);
}
return true;
}
bool ExtMemSlot::writeAdvance16(int16_t data)
{
if (!m_valid) { return false; }
m_spi->write16(m_currentWrPosition, static_cast<uint16_t>(data));
if (m_currentWrPosition < m_end-1) {
m_currentWrPosition+=2; // wrote two bytes
} else {
m_currentWrPosition = m_start;
}
return true;
}
bool ExtMemSlot::enable() const
{
if (m_spi) {
Serial.println("ExtMemSlot::enable()");
m_spi->begin();
return true;
}
else {
Serial.println("ExtMemSlot m_spi is nullptr");
return false;
}
}
bool ExtMemSlot::isEnabled() const
{
if (m_spi) { return m_spi->isStarted(); }
else return false;
}
bool ExtMemSlot::isWriteBusy() const
{
if (m_useDma) {
return (static_cast<BASpiMemoryDMA*>(m_spi))->isWriteBusy();
} else { return false; }
}
bool ExtMemSlot::isReadBusy() const
{
if (m_useDma) {
return (static_cast<BASpiMemoryDMA*>(m_spi))->isReadBusy();
} else { return false; }
}
void ExtMemSlot::printStatus(void) const
{
Serial.println(String("valid:") + m_valid + String(" m_start:") + m_start + \
String(" m_end:") + m_end + String(" m_currentWrPosition: ") + m_currentWrPosition + \
String(" m_currentRdPosition: ") + m_currentRdPosition + \
String(" m_size:") + m_size);
}
}

@ -0,0 +1,120 @@
/*
* LibMemoryManagement.cpp
*
* Created on: Jan 19, 2018
* Author: slascos
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstring>
#include <new>
#include "Audio.h"
#include "LibMemoryManagement.h"
namespace BAGuitar {
/////////////////////////////////////////////////////////////////////////////
// EXTERNAL SRAM MANAGER
/////////////////////////////////////////////////////////////////////////////
bool ExternalSramManager::m_configured = false;
MemConfig ExternalSramManager::m_memConfig[BAGuitar::NUM_MEM_SLOTS];
ExternalSramManager::ExternalSramManager(unsigned numMemories)
{
// Initialize the static memory configuration structs
if (!m_configured) {
for (unsigned i=0; i < NUM_MEM_SLOTS; i++) {
m_memConfig[i].size = MEM_MAX_ADDR[i];
m_memConfig[i].totalAvailable = MEM_MAX_ADDR[i];
m_memConfig[i].nextAvailable = 0;
m_memConfig[i].m_spi = nullptr;
}
m_configured = true;
}
}
ExternalSramManager::ExternalSramManager()
: ExternalSramManager(1)
{
}
ExternalSramManager::~ExternalSramManager()
{
for (unsigned i=0; i < NUM_MEM_SLOTS; i++) {
if (m_memConfig[i].m_spi) { delete m_memConfig[i].m_spi; }
}
}
size_t ExternalSramManager::availableMemory(BAGuitar::MemSelect mem)
{
return m_memConfig[mem].totalAvailable;
}
bool ExternalSramManager::requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem, bool useDma)
{
// convert the time to numer of samples
size_t delayLengthInt = (size_t)((delayMilliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f);
return requestMemory(slot, delayLengthInt * sizeof(int16_t), mem, useDma);
}
bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem, bool useDma)
{
if (m_memConfig[mem].totalAvailable >= sizeBytes) {
Serial.println(String("Configuring a slot for mem ") + mem);
// there is enough available memory for this request
slot->m_start = m_memConfig[mem].nextAvailable;
slot->m_end = slot->m_start + sizeBytes -1;
slot->m_currentWrPosition = slot->m_start; // init to start of slot
slot->m_currentRdPosition = slot->m_start; // init to start of slot
slot->m_size = sizeBytes;
if (!m_memConfig[mem].m_spi) {
if (useDma) {
m_memConfig[mem].m_spi = new BAGuitar::BASpiMemoryDMA(static_cast<BAGuitar::SpiDeviceId>(mem));
slot->m_useDma = true;
} else {
m_memConfig[mem].m_spi = new BAGuitar::BASpiMemory(static_cast<BAGuitar::SpiDeviceId>(mem));
slot->m_useDma = false;
}
if (!m_memConfig[mem].m_spi) {
} else {
Serial.println("Calling spi begin()");
m_memConfig[mem].m_spi->begin();
}
}
slot->m_spi = m_memConfig[mem].m_spi;
// Update the mem config
m_memConfig[mem].nextAvailable = slot->m_end+1;
m_memConfig[mem].totalAvailable -= sizeBytes;
slot->m_valid = true;
if (!slot->isEnabled()) { slot->enable(); }
Serial.println("Clear the memory\n"); Serial.flush();
slot->clear();
Serial.println("Done Request memory\n"); Serial.flush();
return true;
} else {
// there is not enough memory available for the request
return false;
}
}
}

@ -0,0 +1,145 @@
/*
* IirBiquadFilter.cpp
*
* Created on: January 1, 2018
* Author: slascos
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Audio.h"
#include "LibBasicFunctions.h"
namespace BAGuitar {
////////////////////////////////////////////////////
// IirBiQuadFilter
////////////////////////////////////////////////////
IirBiQuadFilter::IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift)
: NUM_STAGES(numStages)
{
m_coeffs = new int32_t[5*numStages];
memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t));
m_state = new int32_t[4*numStages];
arm_biquad_cascade_df1_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift);
}
IirBiQuadFilter::~IirBiQuadFilter()
{
if (m_coeffs) delete [] m_coeffs;
if (m_state) delete [] m_state;
}
bool IirBiQuadFilter::process(int16_t *output, int16_t *input, size_t numSamples)
{
if (!output) return false;
if (!input) {
// send zeros
memset(output, 0, numSamples * sizeof(int16_t));
} else {
// create convertion buffers on teh stack
int32_t input32[numSamples];
int32_t output32[numSamples];
for (size_t i=0; i<numSamples; i++) {
input32[i] = (int32_t)(input[i]);
}
arm_biquad_cascade_df1_fast_q31(&m_iirCfg, input32, output32, numSamples);
for (size_t i=0; i<numSamples; i++) {
output[i] = (int16_t)(output32[i] & 0xffff);
}
}
return true;
}
// HIGH QUALITY
IirBiQuadFilterHQ::IirBiQuadFilterHQ(unsigned numStages, const int32_t *coeffs, int coeffShift)
: NUM_STAGES(numStages)
{
m_coeffs = new int32_t[5*numStages];
memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t));
m_state = new int64_t[4*numStages];;
arm_biquad_cas_df1_32x64_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift);
}
IirBiQuadFilterHQ::~IirBiQuadFilterHQ()
{
if (m_coeffs) delete [] m_coeffs;
if (m_state) delete [] m_state;
}
bool IirBiQuadFilterHQ::process(int16_t *output, int16_t *input, size_t numSamples)
{
if (!output) return false;
if (!input) {
// send zeros
memset(output, 0, numSamples * sizeof(int16_t));
} else {
// create convertion buffers on teh stack
int32_t input32[numSamples];
int32_t output32[numSamples];
for (size_t i=0; i<numSamples; i++) {
input32[i] = (int32_t)(input[i]);
}
arm_biquad_cas_df1_32x64_q31(&m_iirCfg, input32, output32, numSamples);
for (size_t i=0; i<numSamples; i++) {
output[i] = (int16_t)(output32[i] & 0xffff);
}
}
return true;
}
// FLOAT
IirBiQuadFilterFloat::IirBiQuadFilterFloat(unsigned numStages, const float *coeffs)
: NUM_STAGES(numStages)
{
m_coeffs = new float[5*numStages];
memcpy(m_coeffs, coeffs, 5*numStages * sizeof(float));
m_state = new float[4*numStages];;
arm_biquad_cascade_df2T_init_f32(&m_iirCfg, numStages, m_coeffs, m_state);
}
IirBiQuadFilterFloat::~IirBiQuadFilterFloat()
{
if (m_coeffs) delete [] m_coeffs;
if (m_state) delete [] m_state;
}
bool IirBiQuadFilterFloat::process(float *output, float *input, size_t numSamples)
{
if (!output) return false;
if (!input) {
// send zeros
memset(output, 0, numSamples * sizeof(float));
} else {
arm_biquad_cascade_df2T_f32(&m_iirCfg, input, output, numSamples);
}
return true;
}
}

@ -0,0 +1,279 @@
/*
* AudioEffectAnalogDelay.cpp
*
* Created on: Jan 7, 2018
* Author: slascos
*/
#include <new>
#include "AudioEffectAnalogDelay.h"
namespace BAGuitar {
constexpr int MIDI_CHANNEL = 0;
constexpr int MIDI_CONTROL = 1;
// BOSS DM-3 Filters
constexpr unsigned NUM_IIR_STAGES = 4;
constexpr unsigned IIR_COEFF_SHIFT = 2;
constexpr int32_t DEFAULT_COEFFS[5*NUM_IIR_STAGES] = {
536870912, 988616936, 455608573, 834606945, -482959709,
536870912, 1031466345, 498793368, 965834205, -467402235,
536870912, 1105821939, 573646688, 928470657, -448083489,
2339, 5093, 2776, 302068995, 4412722
};
AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelayMs)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(maxDelayMs);
m_maxDelaySamples = calcAudioSamples(maxDelayMs);
m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast<const int32_t *>(&DEFAULT_COEFFS), IIR_COEFF_SHIFT);
}
AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(numSamples);
m_maxDelaySamples = numSamples;
m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast<const int32_t *>(&DEFAULT_COEFFS), IIR_COEFF_SHIFT);
}
// requires preallocated memory large enough
AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(slot);
m_maxDelaySamples = (slot->size() / sizeof(int16_t));
m_externalMemory = true;
m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast<const int32_t *>(&DEFAULT_COEFFS), IIR_COEFF_SHIFT);
}
AudioEffectAnalogDelay::~AudioEffectAnalogDelay()
{
if (m_memory) delete m_memory;
if (m_iir) delete m_iir;
}
void AudioEffectAnalogDelay::update(void)
{
audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples
// Check is block is disabled
if (m_enable == false) {
// do not transmit or process any audio, return as quickly as possible.
if (inputAudioBlock) release(inputAudioBlock);
// release all held memory resources
if (m_previousBlock) {
release(m_previousBlock); m_previousBlock = nullptr;
}
if (!m_externalMemory) {
// when using internal memory we have to release all references in the ring buffer
while (m_memory->getRingBuffer()->size() > 0) {
audio_block_t *releaseBlock = m_memory->getRingBuffer()->front();
m_memory->getRingBuffer()->pop_front();
if (releaseBlock) release(releaseBlock);
}
}
return;
}
// Check is block is bypassed, if so either transmit input directly or create silence
if (m_bypass == true) {
// transmit the input directly
if (!inputAudioBlock) {
// create silence
inputAudioBlock = allocate();
if (!inputAudioBlock) { return; } // failed to allocate
else {
clearAudioBlock(inputAudioBlock);
}
}
transmit(inputAudioBlock, 0);
release(inputAudioBlock);
return;
}
// Otherwise perform normal processing
// In order to make use of the SPI DMA, we need to request the read from memory first,
// then do other processing while it fills in the back.
audio_block_t *blockToOutput = nullptr; // this will hold the output audio
blockToOutput = allocate();
if (!blockToOutput) return; // skip this update cycle due to failure
// get the data. If using external memory with DMA, this won't be filled until
// later.
m_memory->getSamples(blockToOutput, m_delaySamples);
// If using DMA, we need something else to do while that read executes, so
// move on to input preprocessing
// Preprocessing
audio_block_t *preProcessed = allocate();
// mix the input with the feedback path in the pre-processing stage
m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock);
// consider doing the BBD post processing here to use up more time while waiting
// for the read data to come back
audio_block_t *blockToRelease = m_memory->addBlock(preProcessed);
// BACK TO OUTPUT PROCESSING
// Check if external DMA, if so, we need to be sure the read is completed
if (m_externalMemory && m_memory->getSlot()->isUseDma()) {
// Using DMA
while (m_memory->getSlot()->isReadBusy()) {}
}
// perform the wet/dry mix mix
m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput);
transmit(blockToOutput);
release(inputAudioBlock);
release(m_previousBlock);
m_previousBlock = blockToOutput;
// if (m_externalMemory && m_memory->getSlot()->isUseDma()) {
// // Using DMA
// if (m_blockToRelease) release(m_blockToRelease);
// m_blockToRelease = blockToRelease;
// }
if (m_blockToRelease) release(m_blockToRelease);
m_blockToRelease = blockToRelease;
}
void AudioEffectAnalogDelay::delay(float milliseconds)
{
size_t delaySamples = calcAudioSamples(milliseconds);
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); }
if (!m_externalMemory) {
// internal memory
QueuePosition queuePosition = calcQueuePosition(milliseconds);
Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset);
} else {
// external memory
Serial.println(String("CONFIG: delay:") + delaySamples);
ExtMemSlot *slot = m_memory->getSlot();
if (!slot) { Serial.println("ERROR: slot ptr is not valid"); }
if (!slot->isEnabled()) {
slot->enable();
Serial.println("WEIRD: slot was not enabled");
}
}
m_delaySamples = delaySamples;
}
void AudioEffectAnalogDelay::delay(size_t delaySamples)
{
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); }
if (!m_externalMemory) {
// internal memory
QueuePosition queuePosition = calcQueuePosition(delaySamples);
Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset);
} else {
// external memory
Serial.println(String("CONFIG: delay:") + delaySamples);
ExtMemSlot *slot = m_memory->getSlot();
if (!slot->isEnabled()) {
slot->enable();
}
}
m_delaySamples= delaySamples;
}
void AudioEffectAnalogDelay::m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet)
{
if ( out && dry && wet) {
alphaBlend(out, dry, wet, m_feedback);
m_iir->process(out->data, out->data, AUDIO_BLOCK_SAMPLES);
} else if (dry) {
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES);
}
}
void AudioEffectAnalogDelay::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet)
{
if (!out) return; // no valid output buffer
if ( out && dry && wet) {
// Simulate the LPF IIR nature of the analog systems
//m_iir->process(wet->data, wet->data, AUDIO_BLOCK_SAMPLES);
alphaBlend(out, dry, wet, m_mix);
} else if (dry) {
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES);
}
// Set the output volume
gainAdjust(out, out, m_volume, 1);
}
void AudioEffectAnalogDelay::processMidi(int channel, int control, int value)
{
float val = (float)value / 127.0f;
if ((m_midiConfig[DELAY][MIDI_CHANNEL] == channel) &&
(m_midiConfig[DELAY][MIDI_CONTROL] == control)) {
// Delay
if (m_externalMemory) { m_maxDelaySamples = m_memory->getSlot()->size() / sizeof(int16_t); }
size_t delayVal = (size_t)(val * (float)m_maxDelaySamples);
delay(delayVal);
Serial.println(String("AudioEffectAnalogDelay::delay (ms): ") + calcAudioTimeMs(delayVal)
+ String(" (samples): ") + delayVal + String(" out of ") + m_maxDelaySamples);
return;
}
if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) &&
(m_midiConfig[BYPASS][MIDI_CONTROL] == control)) {
// Bypass
if (value >= 65) { bypass(false); Serial.println(String("AudioEffectAnalogDelay::not bypassed -> ON") + value); }
else { bypass(true); Serial.println(String("AudioEffectAnalogDelay::bypassed -> OFF") + value); }
return;
}
if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) &&
(m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) {
// Feedback
Serial.println(String("AudioEffectAnalogDelay::feedback: ") + 100*val + String("%"));
feedback(val);
return;
}
if ((m_midiConfig[MIX][MIDI_CHANNEL] == channel) &&
(m_midiConfig[MIX][MIDI_CONTROL] == control)) {
// Mix
Serial.println(String("AudioEffectAnalogDelay::mix: Dry: ") + 100*(1-val) + String("% Wet: ") + 100*val );
mix(val);
return;
}
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) &&
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) {
// Volume
Serial.println(String("AudioEffectAnalogDelay::volume: ") + 100*val + String("%"));
volume(val);
return;
}
}
void AudioEffectAnalogDelay::mapMidiControl(int parameter, int midiCC, int midiChannel)
{
if (parameter >= NUM_CONTROLS) {
return ; // Invalid midi parameter
}
m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel;
m_midiConfig[parameter][MIDI_CONTROL] = midiCC;
}
}

@ -0,0 +1,471 @@
/*
* BASpiMemory.cpp
*
* Created on: May 22, 2017
* Author: slascos
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Arduino.h"
#include "BASpiMemory.h"
namespace BAGuitar {
// MEM0 Settings
constexpr int SPI_CS_MEM0 = 15;
constexpr int SPI_MOSI_MEM0 = 7;
constexpr int SPI_MISO_MEM0 = 8;
constexpr int SPI_SCK_MEM0 = 14;
// MEM1 Settings
constexpr int SPI_CS_MEM1 = 31;
constexpr int SPI_MOSI_MEM1 = 21;
constexpr int SPI_MISO_MEM1 = 5;
constexpr int SPI_SCK_MEM1 = 20;
// SPI Constants
constexpr int SPI_WRITE_MODE_REG = 0x1;
constexpr int SPI_WRITE_CMD = 0x2;
constexpr int SPI_READ_CMD = 0x3;
constexpr int SPI_ADDR_2_MASK = 0xFF0000;
constexpr int SPI_ADDR_2_SHIFT = 16;
constexpr int SPI_ADDR_1_MASK = 0x00FF00;
constexpr int SPI_ADDR_1_SHIFT = 8;
constexpr int SPI_ADDR_0_MASK = 0x0000FF;
constexpr int CMD_ADDRESS_SIZE = 4;
constexpr int MAX_DMA_XFER_SIZE = 0x4000;
BASpiMemory::BASpiMemory(SpiDeviceId memDeviceId)
{
m_memDeviceId = memDeviceId;
m_settings = {20000000, MSBFIRST, SPI_MODE0};
}
BASpiMemory::BASpiMemory(SpiDeviceId memDeviceId, uint32_t speedHz)
{
m_memDeviceId = memDeviceId;
m_settings = {speedHz, MSBFIRST, SPI_MODE0};
}
// Intitialize the correct Arduino SPI interface
void BASpiMemory::begin()
{
switch (m_memDeviceId) {
case SpiDeviceId::SPI_DEVICE0 :
m_csPin = SPI_CS_MEM0;
m_spi = &SPI;
m_spi->setMOSI(SPI_MOSI_MEM0);
m_spi->setMISO(SPI_MISO_MEM0);
m_spi->setSCK(SPI_SCK_MEM0);
m_spi->begin();
break;
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
case SpiDeviceId::SPI_DEVICE1 :
m_csPin = SPI_CS_MEM1;
m_spi = &SPI1;
m_spi->setMOSI(SPI_MOSI_MEM1);
m_spi->setMISO(SPI_MISO_MEM1);
m_spi->setSCK(SPI_SCK_MEM1);
m_spi->begin();
break;
#endif
default :
// unreachable since memDeviceId is an enumerated class
return;
}
pinMode(m_csPin, OUTPUT);
digitalWrite(m_csPin, HIGH);
m_started = true;
}
BASpiMemory::~BASpiMemory() {
}
// Single address write
void BASpiMemory::write(size_t address, uint8_t data)
{
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer(SPI_WRITE_CMD);
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
m_spi->transfer((address & SPI_ADDR_0_MASK));
m_spi->transfer(data);
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
// Single address write
void BASpiMemory::write(size_t address, uint8_t *src, size_t numBytes)
{
uint8_t *dataPtr = src;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer(SPI_WRITE_CMD);
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
m_spi->transfer((address & SPI_ADDR_0_MASK));
for (size_t i=0; i < numBytes; i++) {
m_spi->transfer(*dataPtr++);
}
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
void BASpiMemory::zero(size_t address, size_t numBytes)
{
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer(SPI_WRITE_CMD);
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
m_spi->transfer((address & SPI_ADDR_0_MASK));
for (size_t i=0; i < numBytes; i++) {
m_spi->transfer(0);
}
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
void BASpiMemory::write16(size_t address, uint16_t data)
{
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer16((SPI_WRITE_CMD << 8) | (address >> 16) );
m_spi->transfer16(address & 0xFFFF);
m_spi->transfer16(data);
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
void BASpiMemory::write16(size_t address, uint16_t *src, size_t numWords)
{
uint16_t *dataPtr = src;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer16((SPI_WRITE_CMD << 8) | (address >> 16) );
m_spi->transfer16(address & 0xFFFF);
for (size_t i=0; i<numWords; i++) {
m_spi->transfer16(*dataPtr++);
}
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
void BASpiMemory::zero16(size_t address, size_t numWords)
{
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer16((SPI_WRITE_CMD << 8) | (address >> 16) );
m_spi->transfer16(address & 0xFFFF);
for (size_t i=0; i<numWords; i++) {
m_spi->transfer16(0);
}
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
Serial.println("DONE!");
}
// single address read
uint8_t BASpiMemory::read(size_t address)
{
int data;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer(SPI_READ_CMD);
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
m_spi->transfer((address & SPI_ADDR_0_MASK));
data = m_spi->transfer(0);
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
return data;
}
void BASpiMemory::read(size_t address, uint8_t *dest, size_t numBytes)
{
uint8_t *dataPtr = dest;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer(SPI_READ_CMD);
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
m_spi->transfer((address & SPI_ADDR_0_MASK));
for (size_t i=0; i<numBytes; i++) {
*dataPtr++ = m_spi->transfer(0);
}
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
uint16_t BASpiMemory::read16(size_t address)
{
uint16_t data;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer16((SPI_READ_CMD << 8) | (address >> 16) );
m_spi->transfer16(address & 0xFFFF);
data = m_spi->transfer16(0);
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
return data;
}
void BASpiMemory::read16(size_t address, uint16_t *dest, size_t numWords)
{
uint16_t *dataPtr = dest;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer16((SPI_READ_CMD << 8) | (address >> 16) );
m_spi->transfer16(address & 0xFFFF);
for (size_t i=0; i<numWords; i++) {
*dataPtr++ = m_spi->transfer16(0);
}
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
/////////////////////////////////////////////////////////////////////////////
// BASpiMemoryDMA
/////////////////////////////////////////////////////////////////////////////
BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId)
: BASpiMemory(memDeviceId)
{
int cs;
switch (memDeviceId) {
case SpiDeviceId::SPI_DEVICE0 :
cs = SPI_CS_MEM0;
m_cs = new ActiveLowChipSelect(cs, m_settings);
break;
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
case SpiDeviceId::SPI_DEVICE1 :
cs = SPI_CS_MEM1;
m_cs = new ActiveLowChipSelect1(cs, m_settings);
break;
#endif
default :
cs = SPI_CS_MEM0;
}
// add 4 bytes to buffer for SPI CMD and 3 bytes of address
m_txCommandBuffer = new uint8_t[CMD_ADDRESS_SIZE];
m_rxCommandBuffer = new uint8_t[CMD_ADDRESS_SIZE];
m_txTransfer = new DmaSpi::Transfer[2];
m_rxTransfer = new DmaSpi::Transfer[2];
}
BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz)
: BASpiMemory(memDeviceId, speedHz)
{
int cs;
switch (memDeviceId) {
case SpiDeviceId::SPI_DEVICE0 :
cs = SPI_CS_MEM0;
m_cs = new ActiveLowChipSelect(cs, m_settings);
break;
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
case SpiDeviceId::SPI_DEVICE1 :
cs = SPI_CS_MEM1;
m_cs = new ActiveLowChipSelect1(cs, m_settings);
break;
#endif
default :
cs = SPI_CS_MEM0;
}
m_txCommandBuffer = new uint8_t[CMD_ADDRESS_SIZE];
m_rxCommandBuffer = new uint8_t[CMD_ADDRESS_SIZE];
m_txTransfer = new DmaSpi::Transfer[2];
m_rxTransfer = new DmaSpi::Transfer[2];
}
BASpiMemoryDMA::~BASpiMemoryDMA()
{
delete m_cs;
if (m_txTransfer) delete [] m_txTransfer;
if (m_rxTransfer) delete [] m_rxTransfer;
if (m_txCommandBuffer) delete [] m_txCommandBuffer;
if (m_rxCommandBuffer) delete [] m_txCommandBuffer;
}
void BASpiMemoryDMA::m_setSpiCmdAddr(int command, size_t address, 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 BASpiMemoryDMA::begin(void)
{
switch (m_memDeviceId) {
case SpiDeviceId::SPI_DEVICE0 :
m_csPin = SPI_CS_MEM0;
m_spi = &SPI;
m_spi->setMOSI(SPI_MOSI_MEM0);
m_spi->setMISO(SPI_MISO_MEM0);
m_spi->setSCK(SPI_SCK_MEM0);
m_spi->begin();
//m_spiDma = &DMASPI0;
m_spiDma = new DmaSpiGeneric();
break;
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
case SpiDeviceId::SPI_DEVICE1 :
m_csPin = SPI_CS_MEM1;
m_spi = &SPI1;
m_spi->setMOSI(SPI_MOSI_MEM1);
m_spi->setMISO(SPI_MISO_MEM1);
m_spi->setSCK(SPI_SCK_MEM1);
m_spi->begin();
m_spiDma = new DmaSpiGeneric(1);
//m_spiDma = &DMASPI1;
break;
#endif
default :
// unreachable since memDeviceId is an enumerated class
return;
}
m_spiDma->begin();
m_spiDma->start();
m_started = true;
}
// SPI must build up a payload that starts the teh CMD/Address first. It will cycle
// through the payloads in a circular buffer and use the transfer objects to check if they
// are done before continuing.
void BASpiMemoryDMA::write(size_t address, uint8_t *src, size_t numBytes)
{
size_t bytesRemaining = numBytes;
uint8_t *srcPtr = src;
size_t nextAddress = address;
while (bytesRemaining > 0) {
m_txXferCount = min(bytesRemaining, MAX_DMA_XFER_SIZE);
while ( m_txTransfer[1].busy()) {} // wait until not busy
m_setSpiCmdAddr(SPI_WRITE_CMD, nextAddress, m_txCommandBuffer);
m_txTransfer[1] = DmaSpi::Transfer(m_txCommandBuffer, CMD_ADDRESS_SIZE, nullptr, 0, m_cs, TransferType::NO_END_CS);
m_spiDma->registerTransfer(m_txTransfer[1]);
while ( m_txTransfer[0].busy()) {} // wait until not busy
m_txTransfer[0] = DmaSpi::Transfer(srcPtr, m_txXferCount, nullptr, 0, m_cs, TransferType::NO_START_CS);
m_spiDma->registerTransfer(m_txTransfer[0]);
bytesRemaining -= m_txXferCount;
srcPtr += m_txXferCount;
nextAddress += m_txXferCount;
}
}
void BASpiMemoryDMA::zero(size_t address, size_t numBytes)
{
size_t bytesRemaining = numBytes;
size_t nextAddress = address;
while (bytesRemaining > 0) {
m_txXferCount = min(bytesRemaining, MAX_DMA_XFER_SIZE);
while ( m_txTransfer[1].busy()) {} // wait until not busy
m_setSpiCmdAddr(SPI_WRITE_CMD, nextAddress, m_txCommandBuffer);
m_txTransfer[1] = DmaSpi::Transfer(m_txCommandBuffer, CMD_ADDRESS_SIZE, nullptr, 0, m_cs, TransferType::NO_END_CS);
m_spiDma->registerTransfer(m_txTransfer[1]);
while ( m_txTransfer[0].busy()) {} // wait until not busy
m_txTransfer[0] = DmaSpi::Transfer(nullptr, m_txXferCount, nullptr, 0, m_cs, TransferType::NO_START_CS);
m_spiDma->registerTransfer(m_txTransfer[0]);
bytesRemaining -= m_txXferCount;
nextAddress += m_txXferCount;
}
}
void BASpiMemoryDMA::write16(size_t address, uint16_t *src, size_t numWords)
{
write(address, reinterpret_cast<uint8_t*>(src), sizeof(uint16_t)*numWords);
}
void BASpiMemoryDMA::zero16(size_t address, size_t numWords)
{
zero(address, sizeof(uint16_t)*numWords);
}
void BASpiMemoryDMA::read(size_t address, uint8_t *dest, size_t numBytes)
{
size_t bytesRemaining = numBytes;
uint8_t *destPtr = dest;
size_t nextAddress = address;
while (bytesRemaining > 0) {
m_setSpiCmdAddr(SPI_READ_CMD, nextAddress, m_rxCommandBuffer);
while ( m_rxTransfer[1].busy()) {}
m_rxTransfer[1] = DmaSpi::Transfer(m_rxCommandBuffer, CMD_ADDRESS_SIZE, nullptr, 0, m_cs, TransferType::NO_END_CS);
m_spiDma->registerTransfer(m_rxTransfer[1]);
m_rxXferCount = min(bytesRemaining, MAX_DMA_XFER_SIZE);
while ( m_rxTransfer[0].busy()) {}
m_rxTransfer[0] = DmaSpi::Transfer(nullptr, m_rxXferCount, destPtr, 0, m_cs, TransferType::NO_START_CS);
m_spiDma->registerTransfer(m_rxTransfer[0]);
bytesRemaining -= m_rxXferCount;
destPtr += m_rxXferCount;
nextAddress += m_rxXferCount;
}
}
void BASpiMemoryDMA::read16(size_t address, uint16_t *dest, size_t numWords)
{
read(address, reinterpret_cast<uint8_t*>(dest), sizeof(uint16_t)*numWords);
}
bool BASpiMemoryDMA::isWriteBusy(void) const
{
return (m_txTransfer[0].busy() or m_txTransfer[1].busy());
}
bool BASpiMemoryDMA::isReadBusy(void) const
{
return (m_rxTransfer[0].busy() or m_rxTransfer[1].busy());
}
} /* namespace BAGuitar */
Loading…
Cancel
Save