forked from wirtz/BALibrary
parent
e894318451
commit
b649550cb3
@ -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,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; |
||||
} |
||||
|
||||
} |
||||
|
@ -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); |
||||
} |
||||
|
||||
} |
||||
|
@ -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; |
||||
} |
||||
|
||||
} |
||||
|
@ -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…
Reference in new issue