General library cleanup, added DMA SPI MEM support

pull/1/head
Steve Lascos 7 years ago
parent e894318451
commit b649550cb3
  1. 114
      examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino
  2. 19
      examples/Delay/AnalogDelayDemo/name.c
  3. 316
      examples/Tests/DMA_MEM0_test/DMA_MEM0_test.ino
  4. 316
      examples/Tests/DMA_MEM1_test/DMA_MEM1_test.ino
  5. 78
      src/AudioEffectAnalogDelay.h
  6. 6
      src/BAAudioControlWM8731.h
  7. 7
      src/BAGpio.h
  8. 10
      src/BAGuitar.h
  9. 13
      src/BAHardware.h
  10. 17
      src/BASpiMemory.h
  11. 30
      src/BATypes.h
  12. 356
      src/LibBasicFunctions.cpp
  13. 6
      src/LibBasicFunctions.h
  14. 4
      src/LibMemoryManagement.h
  15. 171
      src/common/AudioDelay.cpp
  16. 73
      src/common/AudioHelpers.cpp
  17. 88
      src/common/ExtMemSlot.cpp
  18. 113
      src/common/ExternalSramManager.cpp
  19. 145
      src/common/IirBiquadFilter.cpp
  20. 6
      src/effects/AudioEffectAnalogDelay.cpp
  21. 0
      src/effects/BAAudioEffectDelayExternal.cpp
  22. 0
      src/peripherals/BAAudioControlWM8731.cpp
  23. 0
      src/peripherals/BAGpio.cpp
  24. 467
      src/peripherals/BASpiMemory.cpp

@ -0,0 +1,114 @@
//#include <MIDI.h>
#include "BAGuitar.h"
using namespace BAGuitar;
AudioInputI2S i2sIn;
AudioOutputI2S i2sOut;
BAAudioControlWM8731 codec;
#define USE_EXT
#ifdef USE_EXT
ExternalSramManager externalSram(1); // Manage both SRAMs
ExtMemSlot delaySlot; // For the external memory
AudioEffectAnalogDelay myDelay(&delaySlot);
#else
AudioEffectAnalogDelay myDelay(200.0f);
#endif
AudioMixer4 mixer; // Used to mix the original dry with the wet (effects) path.
AudioConnection patch0(i2sIn,0, myDelay,0);
AudioConnection mixerDry(i2sIn,0, mixer,0);
AudioConnection mixerWet(myDelay,0, mixer,1);
AudioConnection leftOut(mixer,0, i2sOut, 0);
AudioConnection rightOut(mixer,0, i2sOut, 1);
unsigned loopCount = 0;
void setup() {
delay(100);
Serial.begin(57600);
codec.disable();
delay(100);
AudioMemory(128);
delay(5);
// Setup MIDI
//usbMIDI.setHandleControlChange(OnControlChange);
Serial.println("Enabling codec...\n");
codec.enable();
delay(100);
#ifdef USE_EXT
Serial.println("Using EXTERNAL memory");
//externalSram.requestMemory(&delaySlot, 1400.0f);
//externalSram.requestMemory(&delaySlot, 1400.0f, MemSelect::MEM0, true);
externalSram.requestMemory(&delaySlot, 1400.0f, MemSelect::MEM1, true);
#else
Serial.println("Using INTERNAL memory");
#endif
myDelay.delay(200.0f);
//myDelay.delay( 128.0f/44100.0f*1000.0f);
//myDelay.delay(0, 0.0f);
//myDelay.delay((size_t)8192);
myDelay.mapMidiBypass(16);
myDelay.mapMidiDelay(20);
myDelay.mapMidiFeedback(21);
myDelay.mapMidiMix(22);
myDelay.enable();
myDelay.bypass(false);
myDelay.mix(1.0f);
myDelay.feedback(0.0f);
mixer.gain(0, 0.0f); // unity gain on the dry
mixer.gain(1, 1.0f); // unity gain on the wet
}
void OnControlChange(byte channel, byte control, byte value) {
myDelay.processMidi(channel, control, value);
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();
}
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.
//Serial.println(".");
if (loopCount % 262144 == 0) {
Serial.print("Processor Usage: "); Serial.print(AudioProcessorUsage());
Serial.print(" "); Serial.print(AudioProcessorUsageMax());
Serial.print(" Delay: "); Serial.print(myDelay.processorUsage());
Serial.print(" "); Serial.println(myDelay.processorUsageMax());
}
loopCount++;
if (usbMIDI.read()) {
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
//cable = usbMIDI.getCable(); // which virtual cable with MIDIx8, 0-7
if (type == 3) {
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) {}
}

@ -1,34 +1,88 @@
/* /**************************************************************************//**
* AudioEffectAnalogDelay.h * @file
* @author Steve Lascos
* @company Blackaddr Audio
* *
* Created on: Jan 7, 2018 * AudioEffectAnalogDelay is a class for simulating a classic BBD based delay
* Author: slascos * 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 SRC_AUDIOEFFECTANALOGDELAY_H_ #ifndef __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H
#define SRC_AUDIOEFFECTANALOGDELAY_H_ #define __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H
#include <Audio.h> #include <Audio.h>
#include "LibBasicFunctions.h" #include "LibBasicFunctions.h"
namespace BAGuitar { 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 { class AudioEffectAnalogDelay : public AudioStream {
public: public:
AudioEffectAnalogDelay() = delete; AudioEffectAnalogDelay() = delete;
AudioEffectAnalogDelay(float maxDelay); /// 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); 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 AudioEffectAnalogDelay(ExtMemSlot *slot); // requires sufficiently sized pre-allocated memory
virtual ~AudioEffectAnalogDelay();
virtual void update(void); virtual ~AudioEffectAnalogDelay(); ///< Destructor
/// Set the delay in milliseconds.
/// @param milliseconds the request delay in milliseconds. Must be less than max delay.
void delay(float milliseconds); 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); 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; } 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; } 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; } void mix(float mix) { m_mix = mix; }
/// Enables audio processing. Note: when not enabled, CPU load is nearly zero.
void enable() { m_enable = true; } void enable() { m_enable = true; }
/// Disables audio process. When disabled, CPU load is nearly zero.
void disable() { m_enable = false; } void disable() { m_enable = false; }
void processMidi(int channel, int control, int value); void processMidi(int channel, int control, int value);
@ -37,6 +91,8 @@ public:
void mapMidiFeedback(int control, int channel = 0); void mapMidiFeedback(int control, int channel = 0);
void mapMidiMix(int control, int channel = 0); void mapMidiMix(int control, int channel = 0);
virtual void update(void); ///< update automatically called by the Teesny Audio Library
private: private:
audio_block_t *m_inputQueueArray[1]; audio_block_t *m_inputQueueArray[1];
bool m_bypass = true; bool m_bypass = true;
@ -62,4 +118,4 @@ private:
} }
#endif /* SRC_AUDIOEFFECTANALOGDELAY_H_ */ #endif /* __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H */

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

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

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

@ -20,8 +20,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/ *****************************************************************************/
#ifndef SRC_BAHARDWARE_H_ #ifndef __BAGUTIAR_BAHARDWARE_H
#define SRC_BAHARDWARE_H_ #define __BAGUTIAR_BAHARDWARE_H
#include <cstdint> #include <cstdint>
@ -64,9 +64,12 @@ enum MemSelect : unsigned {
MEM0 = 0, ///< SPI RAM MEM0 MEM0 = 0, ///< SPI RAM MEM0
MEM1 = 1 ///< SPI RAM MEM1 MEM1 = 1 ///< SPI RAM MEM1
}; };
/**************************************************************************//**
* Set the maximum address (byte-based) in the external SPI memories
*****************************************************************************/
constexpr size_t MEM_MAX_ADDR[NUM_MEM_SLOTS] = { 131071, 131071 }; constexpr size_t MEM_MAX_ADDR[NUM_MEM_SLOTS] = { 131071, 131071 };
//constexpr int MEM0_MAX_ADDR = 131071; ///< Max address size per chip
//constexpr int MEM1_MAX_ADDR = 131071; ///< Max address size per chip
/**************************************************************************//** /**************************************************************************//**
* General Purpose SPI Interfaces * General Purpose SPI Interfaces
@ -87,4 +90,4 @@ constexpr int SPI_MAX_ADDR = 131071; ///< Max address size per chip
} // namespace BAGuitar } // namespace BAGuitar
#endif /* SRC_BAHARDWARE_H_ */ #endif /* __BAGUTIAR_BAHARDWARE_H */

@ -4,7 +4,8 @@
* @company Blackaddr Audio * @company Blackaddr Audio
* *
* BASpiMemory is convenience class for accessing the optional SPI RAMs on * BASpiMemory is convenience class for accessing the optional SPI RAMs on
* the GTA Series boards. * the GTA Series boards. BASpiMemoryDma works the same but uses DMA to reduce
* load on the processor.
* *
* @copyright This program is free software: you can redistribute it and/or modify * @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -19,8 +20,8 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/ *****************************************************************************/
#ifndef __SRC_BASPIMEMORY_H #ifndef __BAGUITAR_BASPIMEMORY_H
#define __SRC_BASPIMEMORY_H #define __BAGUITAR_BASPIMEMORY_H
#include <SPI.h> #include <SPI.h>
#include <DmaSpi.h> #include <DmaSpi.h>
@ -119,7 +120,6 @@ protected:
}; };
//constexpr int MAX_DMA_XFERS = 4;
class BASpiMemoryDMA : public BASpiMemory { class BASpiMemoryDMA : public BASpiMemory {
public: public:
@ -194,17 +194,12 @@ public:
void readBufferContents(uint16_t *dest, size_t numWords, size_t wordOffset = 0); void readBufferContents(uint16_t *dest, size_t numWords, size_t wordOffset = 0);
private: private:
//AbstractDmaSpi<DmaSpi0, SPIClass, SPI> *m_spiDma = nullptr;
//AbstractDmaSpi<DmaSpi0, SPIClass, SPI1> *m_spiDma = nullptr;
DmaSpiGeneric *m_spiDma = nullptr;
DmaSpiGeneric *m_spiDma = nullptr;
AbstractChipSelect *m_cs = nullptr; AbstractChipSelect *m_cs = nullptr;
//size_t m_bufferSize;
//uint8_t *m_txBuffer = nullptr;
uint8_t *m_txCommandBuffer = nullptr; uint8_t *m_txCommandBuffer = nullptr;
DmaSpi::Transfer *m_txTransfer; DmaSpi::Transfer *m_txTransfer;
//uint8_t *m_rxBuffer = nullptr;
uint8_t *m_rxCommandBuffer = nullptr; uint8_t *m_rxCommandBuffer = nullptr;
DmaSpi::Transfer *m_rxTransfer; DmaSpi::Transfer *m_rxTransfer;
@ -217,4 +212,4 @@ private:
} /* namespace BAGuitar */ } /* namespace BAGuitar */
#endif /* __SRC_BASPIMEMORY_H */ #endif /* __BAGUITAR_BASPIMEMORY_H */

@ -1,12 +1,26 @@
/* /**************************************************************************//**
* BATypes.h * @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.*
* *
* Created on: Feb 7, 2018 * This program is distributed in the hope that it will be useful,
* Author: slascos * 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 SRC_BATYPES_H_ #ifndef __BAGUITAR_BATYPES_H
#define SRC_BATYPES_H_ #define __BAGUITAR_BATYPES_H
namespace BAGuitar { namespace BAGuitar {
@ -141,4 +155,4 @@ private:
} // BAGuitar } // BAGuitar
#endif /* SRC_BATYPES_H_ */ #endif /* __BAGUITAR_BATYPES_H */

@ -1,356 +0,0 @@
/*
* LibBasicFunctions.cpp
*
* Created on: Dec 23, 2017
* Author: slascos
*/
#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);
}
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)
{
//Non-optimized version for illustrative purposes
// for (int i=0; i< AUDIO_BLOCK_SAMPLES; i++) {
// out->data[i] = (dry->data[i] * (1 - mix)) + (wet->data[i] * mix);
// }
// return;
// 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 clearAudioBlock(audio_block_t *block)
{
memset(block->data, 0, sizeof(int16_t)*AUDIO_BLOCK_SAMPLES);
}
////////////////////////////////////////////////////
// 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) {
// Audio is stored in reverse in block so we need to write it backwards to external memory
// to maintain temporal coherency.
// int16_t *srcPtr = block->data + AUDIO_BLOCK_SAMPLES - 1;
// for (int i=0; i<AUDIO_BLOCK_SAMPLES; i++) {
// m_slot->writeAdvance16(*srcPtr);
// srcPtr--;
// }
// this causes pops
m_slot->writeAdvance16(block->data, AUDIO_BLOCK_SAMPLES);
// Code below worked
// int16_t *srcPtr = block->data;
// for (int i=0; i<AUDIO_BLOCK_SAMPLES; i++) {
// m_slot->writeAdvance16(*srcPtr);
// srcPtr++;
// }
}
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);
}
//m_slot->printStatus();
// write the data to the destination block in reverse
// int16_t *destPtr = dest->data + AUDIO_BLOCK_SAMPLES-1;
// for (int i=0; i<AUDIO_BLOCK_SAMPLES; i++) {
// *destPtr = m_slot->readAdvance16();
// destPtr--;
// }
// This causes pops
m_slot->readAdvance16(dest->data, AUDIO_BLOCK_SAMPLES);
// Code below worked
// int16_t *destPtr = dest->data;
// for (int i=0; i<AUDIO_BLOCK_SAMPLES; i++) {
// *destPtr = m_slot->readAdvance16();
// destPtr++;
// }
return true;
} else {
// numSampmles is > than total slot size
Serial.println("getSamples(): ERROR numSamples > total slot size");
return false;
}
}
}
////////////////////////////////////////////////////
// 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;
}
}

@ -30,8 +30,8 @@
#include "BATypes.h" #include "BATypes.h"
#include "LibMemoryManagement.h" #include "LibMemoryManagement.h"
#ifndef __LIBBASICFUNCTIONS_H #ifndef __BAGUITAR_LIBBASICFUNCTIONS_H
#define __LIBBASICFUNCTIONS_H #define __BAGUITAR_LIBBASICFUNCTIONS_H
namespace BAGuitar { namespace BAGuitar {
@ -260,4 +260,4 @@ private:
} }
#endif /* __LIBBASICFUNCTIONS_H */ #endif /* __BAGUITAR_LIBBASICFUNCTIONS_H */

@ -26,8 +26,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/ *****************************************************************************/
#ifndef __LIBMEMORYMANAGEMENT_H #ifndef __BAGUITAR_LIBMEMORYMANAGEMENT_H
#define __LIBMEMORYMANAGEMENT_H #define __BAGUITAR_LIBMEMORYMANAGEMENT_H
#include <cstddef> #include <cstddef>

@ -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,73 @@
/*
* 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);
}
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)
{
//Non-optimized version for illustrative purposes
// for (int i=0; i< AUDIO_BLOCK_SAMPLES; i++) {
// out->data[i] = (dry->data[i] * (1 - mix)) + (wet->data[i] * mix);
// }
// return;
// 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 clearAudioBlock(audio_block_t *block)
{
memset(block->data, 0, sizeof(int16_t)*AUDIO_BLOCK_SAMPLES);
}
}

@ -1,5 +1,5 @@
/* /*
* LibMemoryManagement.cpp * ExtMemSlot.cpp
* *
* Created on: Jan 19, 2018 * Created on: Jan 19, 2018
* Author: slascos * Author: slascos
@ -25,7 +25,6 @@
namespace BAGuitar { namespace BAGuitar {
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// MEM SLOT // MEM SLOT
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
@ -232,90 +231,5 @@ void ExtMemSlot::printStatus(void) const
String(" m_size:") + m_size); String(" m_size:") + m_size);
} }
/////////////////////////////////////////////////////////////////////////////
// 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()
{
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,113 @@
/*
* 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()
{
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;
}
}

@ -29,11 +29,11 @@ constexpr int32_t DEFAULT_COEFFS[5*NUM_IIR_STAGES] = {
}; };
AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelay) AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelayMs)
: AudioStream(1, m_inputQueueArray) : AudioStream(1, m_inputQueueArray)
{ {
m_memory = new AudioDelay(maxDelay); m_memory = new AudioDelay(maxDelayMs);
m_maxDelaySamples = calcAudioSamples(maxDelay); m_maxDelaySamples = calcAudioSamples(maxDelayMs);
m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast<const int32_t *>(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast<const int32_t *>(&DEFAULT_COEFFS), IIR_COEFF_SHIFT);
} }

@ -0,0 +1,467 @@
/*
* 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;
case SpiDeviceId::SPI_DEVICE1 :
cs = SPI_CS_MEM1;
m_cs = new ActiveLowChipSelect1(cs, m_settings);
break;
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;
case SpiDeviceId::SPI_DEVICE1 :
cs = SPI_CS_MEM1;
m_cs = new ActiveLowChipSelect1(cs, m_settings);
break;
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