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
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. ***
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

@ -8,9 +8,13 @@
* This demo will provide an audio passthrough, as well as exercise the
* 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 <Audio.h>
#include <MIDI.h>
@ -19,7 +23,6 @@
MIDI_CREATE_DEFAULT_INSTANCE();
using namespace midi;
using namespace BAGuitar;
AudioInputI2S i2sIn;
@ -32,23 +35,25 @@ AudioConnection patch1(i2sIn,1, i2sOut, 1);
BAAudioControlWM8731 codecControl;
BAGpio gpio; // access to User LED
BASpiMemory spiMem0(SpiDeviceId::SPI_DEVICE0);
BASpiMemory spiMem1(SpiDeviceId::SPI_DEVICE1);
unsigned long t=0;
// SPI stuff
int spiAddress0 = 0;
int spiData0 = 0xff;
uint16_t spiData0 = 0xABCD;
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() {
MIDI.begin(MIDI_CHANNEL_OMNI);
Serial.begin(57600);
while (!Serial) {}
delay(5);
// If the codec was already powered up (due to reboot) power itd own first
@ -56,26 +61,53 @@ void setup() {
delay(100);
AudioMemory(24);
Serial.println("Enabling codec...\n");
Serial.println("Sketch: Enabling codec...\n");
codecControl.enable();
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
//////////////////////////////////////////////////////////////////
for (spiAddress0=0; spiAddress0 <= SPI_MAX_ADDR; spiAddress0++) {
maskPhase = 0;
for (spiAddress0=0; spiAddress0 <= SPI_MAX_ADDR; spiAddress0=spiAddress0+2) {
if ((spiAddress0 % 32768) == 0) {
//Serial.print("Writing to ");
//Serial.println(spiAddress0, HEX);
}
//mem0Write(spiAddress0, spiData0);
spiMem0.write(spiAddress0, spiData0);
spiData0 = (spiData0-1) & 0xff;
spiData0 = calcData(spiAddress0, loopPhase, maskPhase);
spiMem0.write16(spiAddress0, spiData0);
maskPhase = (maskPhase+1) % 2;
}
Serial.println("SPI0 writing DONE!");
@ -84,39 +116,41 @@ void loop() {
///////////////////////////////////////////////////////////////////
spiErrorCount0 = 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) {
//Serial.print("Reading ");
//Serial.print(spiAddress0, HEX);
// Serial.print("Reading ");
// Serial.print(spiAddress0, HEX);
}
//int data = mem0Read(spiAddress0);
int data = spiMem0.read(spiAddress0);
spiData0 = calcData(spiAddress0, loopPhase, maskPhase);
int data = spiMem0.read16(spiAddress0);
if (data != spiData0) {
spiErrorCount0++;
Serial.println("");
Serial.print("ERROR MEM0: (expected) (actual):");
Serial.print(spiData0, HEX); Serial.print(":");
Serial.println(data, HEX);
Serial.print(data, HEX);
delay(100);
}
maskPhase = (maskPhase+1) % 2;
if ((spiAddress0 % 32768) == 0) {
//Serial.print(", data = ");
//Serial.println(data, HEX);
// Serial.print(", data = ");
// 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
if (spiErrorCount0 > 10) { break; }
}
if (spiErrorCount0 == 0) { Serial.println("SPI0 TEST PASSED!!!"); }
loopPhase = (loopPhase+1) % 2;
///////////////////////////////////////////////////////////////////////
// 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/>.
*****************************************************************************/
#ifndef __INC_BAAUDIOCONTROLWM8731_H
#define __INC_BAAUDIOCONTROLWM8731_H
#ifndef __BAGUITAR__BAAUDIOCONTROLWM8731_H
#define __BAGUITAR__BAAUDIOCONTROLWM8731_H
namespace BAGuitar {
@ -128,4 +128,4 @@ private:
} /* 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/>.
*****************************************************************************/
#ifndef __SRC_BAGPIO_H
#define __SRC_BAGPIO_H
#ifndef __BAGUITAR_BAGPIO_H
#define __BAGUITAR_BAGPIO_H
#include "BAHardware.h"
@ -35,6 +35,7 @@ namespace BAGuitar {
*****************************************************************************/
class BAGpio {
public:
/// Construct a GPIO object for controlling the various GPIO and user pins
BAGpio();
virtual ~BAGpio();
@ -72,4 +73,4 @@ private:
} /* 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/>.
*/
#ifndef __SRC_BATGUITAR_H
#define __SRC_BATGUITAR_H
#ifndef __BATGUITAR_H
#define __BATGUITAR_H
#include "BAHardware.h" // contains the Blackaddr hardware board definitions
#include "BATypes.h"
#include "BAAudioControlWM8731.h" // Codec Control
#include "BASpiMemory.h"
#include "BAGpio.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/>.
*****************************************************************************/
#ifndef SRC_BAHARDWARE_H_
#define SRC_BAHARDWARE_H_
#ifndef __BAGUTIAR_BAHARDWARE_H
#define __BAGUTIAR_BAHARDWARE_H
#include <cstdint>
/**************************************************************************//**
* BAGuitar is a namespace/Library for Guitar processing from Blackaddr Audio.
@ -57,19 +59,24 @@ enum class GPIO : uint8_t {
/**************************************************************************//**
* Optionally installed SPI RAM
*****************************************************************************/
constexpr unsigned NUM_MEM_SLOTS = 2;
enum MemSelect : unsigned {
MEM0 = 0, ///< SPI RAM MEM0
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
*****************************************************************************/
enum class SpiDeviceId {
SPI_DEVICE0, ///< Arduino SPI device
SPI_DEVICE1 ///< Arduino SPI1 device
enum SpiDeviceId : unsigned {
SPI_DEVICE0 = 0, ///< Arduino SPI device
SPI_DEVICE1 = 1 ///< Arduino SPI1 device
};
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
#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
*
* 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
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __SRC_BASPIMEMORY_H
#define __SRC_BASPIMEMORY_H
#ifndef __BAGUITAR_BASPIMEMORY_H
#define __BAGUITAR_BASPIMEMORY_H
#include <SPI.h>
#include <DmaSpi.h>
#include "BATypes.h"
#include "BAHardware.h"
namespace BAGuitar {
@ -39,43 +42,174 @@ public:
BASpiMemory() = 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 controlw with SpiDeviceId.
/// @param memDeviceId specify which MEM to control with SpiDeviceId.
BASpiMemory(SpiDeviceId memDeviceId);
/// 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.
BASpiMemory(SpiDeviceId memDeviceId, uint32_t speedHz);
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 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
/// @param address the address in the SPI RAM to read from
/// @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
/// @param address the address in the SPI RAM to read from
/// @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;
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
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 */
#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