parent
129e3a3e3b
commit
701a7cc1a5
@ -0,0 +1,9 @@ |
||||
#ifndef DEBUG_PRINTF_H_ |
||||
#define DEBUG_PRINTF_H_ |
||||
|
||||
// Make a safe call to Serial where it checks if the object is valid first. This allows your
|
||||
// program to work even when no USB serial is connected. Printing to the Serial object when
|
||||
// no valid connection is present will crash the CPU.
|
||||
#define DEBUG_PRINT(x) {if (Serial) {x;}} |
||||
|
||||
#endif |
@ -0,0 +1,276 @@ |
||||
/***********************************************************************************
|
||||
* MULTIVERSE DEMO |
||||
*
|
||||
* This demo program shows how to use BALibrary to access the hardware |
||||
* features of the Aviate Audio Multiverse effects processor. |
||||
*
|
||||
* The following are demonstrated in this programming using BALibrary: |
||||
* - WM8731 stereo audio codec in master mode (NOTE: not slave mode like TGA Pro |
||||
* - Interact with all physical controls |
||||
* - Control the 128x64 pixel OLED display (connected to SPI0) |
||||
* - Use the 8MB external SRAM (simple memory test) |
||||
*
|
||||
* USAGE INSTRUCTIONS |
||||
* - Use the 'Gain' knob to control the input gain on the codec. See checkPot(). |
||||
* - Use the 'Level' knob to control output volume with an AudioMixer4 object. |
||||
* - Stomp switches S1 and S2 will write status to display, and turn on LED |
||||
* - Encoder push-button switches will write status to display when pressed/released |
||||
* - Encoder rotary control will adjust a positive/negative count and update display |
||||
*/ |
||||
|
||||
#include <Audio.h> |
||||
#include <SPI.h> |
||||
#include "BALibrary.h" |
||||
|
||||
#include "DebugPrintf.h" |
||||
#include "PhysicalControls.h" |
||||
|
||||
using namespace BALibrary; |
||||
|
||||
// OLED display stuff
|
||||
#include "Adafruit_SH1106.h" |
||||
#include "Adafruit_GFX.h" |
||||
#include "Fonts/FreeSansBold9pt7b.h" |
||||
constexpr unsigned SCREEN_WIDTH = 128; // OLED display width, in pixels
|
||||
constexpr unsigned SCREEN_HEIGHT = 64; // OLED display height, in pixels
|
||||
Adafruit_SH1106 display(37, 35, 10); |
||||
|
||||
// External SPI RAM
|
||||
ExternalSramManager sramManager; |
||||
ExtMemSlot memSlot; |
||||
BASpiMemory spiMem1(SpiDeviceId::SPI_DEVICE1); |
||||
unsigned spiAddress = 0; |
||||
unsigned spiAddressMax; |
||||
unsigned sramStage = 0; // stage 0 is zero, 1 is write, 2 is read
|
||||
volatile float sramCompletion = 0.0f; |
||||
volatile unsigned errorCount = 0; |
||||
|
||||
AudioInputI2Sslave i2sIn; |
||||
AudioOutputI2Sslave i2sOut; |
||||
AudioMixer4 volumeOut; |
||||
|
||||
// i2sIn --> volumeOut(Mixer) --> i2sOut
|
||||
AudioConnection patchIn0(i2sIn, 0, volumeOut, 0); |
||||
AudioConnection patchIn1(i2sIn, 1, volumeOut, 1); |
||||
AudioConnection patchOut0(volumeOut,0, i2sOut, 0); |
||||
AudioConnection patchOut1(volumeOut,0, i2sOut, 1); |
||||
|
||||
BAAudioControlWM8731master codec; |
||||
elapsedMillis timer; |
||||
|
||||
// Create a control object using the number of switches, pots, encoders and outputs on the
|
||||
// Multiverse pedal
|
||||
BAPhysicalControls controls(6, 4, 4, 2); // (SW, POT, ENC, LED)
|
||||
|
||||
unsigned loopCounter = 0; |
||||
|
||||
void drawProgressBar(float completion); // declaration
|
||||
|
||||
void drawBlackaddrAudio() { |
||||
display.setCursor(0, 24); // (x,y)
|
||||
display.printf(" Blackaddr"); |
||||
display.setCursor(0, 40); // (x,y)
|
||||
display.printf(" Audio"); |
||||
} |
||||
|
||||
void setup() { |
||||
|
||||
codec.disable(); // this will reset the codec
|
||||
|
||||
// wait up for the serial to appear for up to 1 second
|
||||
Serial.begin(57600); |
||||
unsigned serialLoopCount = 10; |
||||
while (!Serial && (serialLoopCount > 0)) { |
||||
delay(100); |
||||
serialLoopCount--; |
||||
} |
||||
|
||||
MULTIVERSE(); // constants defined in BALibrary become valid only after this call
|
||||
SPI_MEM1_64M(); // Declare the correct memory size
|
||||
|
||||
// Init the display
|
||||
display.begin(SH1106_SWITCHCAPVCC, SH1106_I2C_ADDRESS, true); |
||||
display.clearDisplay(); |
||||
display.display(); |
||||
display.setTextColor(WHITE); // Draw white text
|
||||
display.setFont(&FreeSansBold9pt7b); |
||||
drawBlackaddrAudio(); |
||||
display.display(); |
||||
|
||||
configPhysicalControls(&controls, &codec);
|
||||
|
||||
// Request a memory slot from the external RAM
|
||||
size_t numBytes = BAHardwareConfig.getSpiMemSizeBytes(MemSelect::MEM1); |
||||
spiAddressMax = BAHardwareConfig.getSpiMemMaxAddr(1)/4; // test the first 25% of memory
|
||||
bool success = sramManager.requestMemory(&memSlot, numBytes, MemSelect::MEM1, /* no DMA */ false); |
||||
if (!success && Serial) { printf("Request for memory slot failed\n\r"); }
|
||||
|
||||
// Allocated audio buffers and enable codec
|
||||
AudioMemory(64); |
||||
codec.enable(); |
||||
delay(100); |
||||
|
||||
// Mixer at full volume
|
||||
volumeOut.gain(0,1.0f); |
||||
volumeOut.gain(1,1.0f); |
||||
|
||||
// flush the pot filters. The analog measurement of the analog pots is averaged (filtered)
|
||||
// over time, so at startup you will see a bunch of false changes detected as the filter
|
||||
// settles. We can force this with a few dozen repeated calls.
|
||||
for (unsigned i=0; i < 50; i++) { |
||||
float potValue; |
||||
for (unsigned j=0; j < BA_EXPAND_NUM_POT; j++) { |
||||
controls.checkPotValue(j, potValue);
|
||||
} |
||||
delay(10); |
||||
} |
||||
} |
||||
|
||||
void loop() { |
||||
|
||||
// Check all the physical controls for updates
|
||||
checkPot(0); |
||||
checkPot(1); |
||||
checkPot(2); |
||||
checkPot(3); |
||||
|
||||
checkSwitch(0); |
||||
checkSwitch(1); |
||||
checkSwitch(2); |
||||
checkSwitch(3); |
||||
checkSwitch(4); |
||||
checkSwitch(5); |
||||
|
||||
checkEncoder(0); |
||||
checkEncoder(1); |
||||
checkEncoder(2); |
||||
checkEncoder(3);
|
||||
|
||||
// If the SRAM test is not complete, run the next block
|
||||
if (sramCompletion < 1.0f) { |
||||
nextSpiMemTestBlock();
|
||||
} |
||||
|
||||
// Adjusting one of the knobs/switches will result in its value being display for
|
||||
// 2 seconds in the check*() functions.
|
||||
if (timer > 2000) { |
||||
loopCounter++;
|
||||
display.clearDisplay(); |
||||
drawBlackaddrAudio(); |
||||
drawSramProgress(sramCompletion);
|
||||
display.display();
|
||||
} |
||||
} |
||||
|
||||
// This function will draw on the display which stage the memory test is in, and
|
||||
// the percentage complete for that stage.
|
||||
void drawSramProgress(float completion) |
||||
{ |
||||
if (errorCount > 0) { // If errors, print the error count
|
||||
display.setCursor(0, SCREEN_HEIGHT-1); |
||||
display.printf("Errors: %d", errorCount);
|
||||
return; |
||||
} |
||||
|
||||
// Draw the SRAM test progress at the bottom of the screen
|
||||
display.setCursor(0, SCREEN_HEIGHT-1); |
||||
switch(sramStage) {
|
||||
case 0 : display.printf("0 mem:"); break; |
||||
case 1 : display.printf("0 chk:"); break; |
||||
case 2 : display.printf("wr mem:"); break; |
||||
case 3 : display.printf("rd mem:"); break; |
||||
case 4 : // same as default
|
||||
default: display.printf("Done"); break; |
||||
} |
||||
display.setCursor(SCREEN_WIDTH*0.63f, SCREEN_HEIGHT-1); // position to lower right corner
|
||||
display.printf("%0.f%%", 100.0f * completion); |
||||
} |
||||
|
||||
// Create a predictable data pattern based on address.
|
||||
constexpr int mask0 = 0x5555; |
||||
constexpr int mask1 = 0xaaaa; |
||||
int calcNextData(int spiAddress, int loopPhase, int maskPhase) |
||||
{ |
||||
int data; |
||||
|
||||
int phase = ((loopPhase << 1) + maskPhase) & 0x3; |
||||
switch(phase) |
||||
{ |
||||
case 0 : |
||||
data = spiAddress ^ mask0; |
||||
break; |
||||
case 1: |
||||
data = spiAddress ^ mask1; |
||||
break; |
||||
case 2: |
||||
data = ~spiAddress ^ mask0; |
||||
break; |
||||
case 3: |
||||
data = ~spiAddress ^ mask1; |
||||
|
||||
} |
||||
return (data & 0xffff); |
||||
} |
||||
|
||||
// Process the next block of data in the memory test
|
||||
void nextSpiMemTestBlock() |
||||
{ |
||||
constexpr unsigned BLOCK_SIZE_BYTES = 256; // transfer 256 bytes (arbitrary) per transaction
|
||||
constexpr unsigned NUM_BLOCK_WORDS = BLOCK_SIZE_BYTES; |
||||
static uint8_t buffer[BLOCK_SIZE_BYTES]; |
||||
static int16_t buffer16a[NUM_BLOCK_WORDS]; |
||||
static int16_t buffer16b[NUM_BLOCK_WORDS]; |
||||
static int maskPhase = 0; |
||||
|
||||
if (sramStage == 0) { // Zero write
|
||||
// zero the memory
|
||||
while (spiMem1.isWriteBusy()) {} // wait for DMA write to complete
|
||||
memSlot.zero(spiAddress, BLOCK_SIZE_BYTES); |
||||
spiAddress += BLOCK_SIZE_BYTES;
|
||||
|
||||
} else if (sramStage == 1) { // Zero check
|
||||
memSlot.read(spiAddress, buffer, BLOCK_SIZE_BYTES); |
||||
while (spiMem1.isReadBusy()) {} // wait for DMA read results
|
||||
for (unsigned i=0; i < BLOCK_SIZE_BYTES; i++) { |
||||
if (buffer[i] != 0) { errorCount++; } |
||||
} |
||||
spiAddress += BLOCK_SIZE_BYTES; |
||||
|
||||
} |
||||
else if (sramStage == 2) { // write test
|
||||
// Calculate the data for a block
|
||||
for (unsigned i=0; i<NUM_BLOCK_WORDS; i++) { |
||||
buffer16a[i] = calcNextData(spiAddress+i, 0, 0); |
||||
maskPhase = (maskPhase+1) % 2; |
||||
} |
||||
memSlot.write16(spiAddress, buffer16a, NUM_BLOCK_WORDS); |
||||
while (memSlot.isWriteBusy()) {} // wait for DMA write to complete
|
||||
spiAddress += BLOCK_SIZE_BYTES; |
||||
|
||||
} |
||||
else if (sramStage == 3) { // read test
|
||||
// Calculate the data for a block
|
||||
for (unsigned i=0; i<NUM_BLOCK_WORDS; i++) { |
||||
buffer16a[i] = calcNextData(spiAddress+i, 0, 0); |
||||
maskPhase = (maskPhase+1) % 2; |
||||
} |
||||
memSlot.read16(spiAddress, buffer16b, NUM_BLOCK_WORDS); |
||||
while (memSlot.isReadBusy()) {} // wait for DMA read results
|
||||
for (unsigned i=0; i < NUM_BLOCK_WORDS; i++) { |
||||
if (buffer16a[i] != buffer16b[i]) { errorCount++; } |
||||
}
|
||||
spiAddress += BLOCK_SIZE_BYTES; |
||||
} |
||||
|
||||
else if (sramStage == 4) { |
||||
sramCompletion = 1.0f; |
||||
return; |
||||
} |
||||
|
||||
if (spiAddress > spiAddressMax && sramStage < 4) {
|
||||
spiAddress = 0; sramStage++; sramCompletion = 0.0f; |
||||
return; |
||||
} |
||||
|
||||
sramCompletion = (float)spiAddress / (float)spiAddressMax ; |
||||
} |
@ -0,0 +1,222 @@ |
||||
#include <cmath> |
||||
#include "Adafruit_SH1106.h" |
||||
#include "BALibrary.h" |
||||
#include "DebugPrintf.h" |
||||
|
||||
using namespace BALibrary; |
||||
|
||||
// Declare the externally shared variables from the main .ino
|
||||
extern Adafruit_SH1106 display; |
||||
extern BAAudioControlWM8731master codec; |
||||
extern AudioMixer4 volumeOut; |
||||
extern elapsedMillis timer; |
||||
|
||||
constexpr int displayRow = 36; // Row to start OLED display updates on
|
||||
constexpr int potCalibMin = 8; |
||||
constexpr int potCalibMax = 1016; |
||||
constexpr bool potSwapDirection = true; |
||||
constexpr bool encSwapDirection = true; |
||||
int pot1Handle= -1, pot2Handle = -1, pot3Handle = -1, pot4Handle = -1; |
||||
int sw1Handle = -1, sw2Handle = -1, sw3Handle = -1, sw4Handle = -1, sw5Handle = -1, sw6Handle = -1; |
||||
int enc1Handle = -1, enc2Handle = -1, enc3Handle = -1, enc4Handle = -1; |
||||
int led1Handle = -1, led2Handle = -1; |
||||
|
||||
BAAudioControlWM8731master *codecPtr = nullptr; |
||||
BAPhysicalControls *controlPtr = nullptr; |
||||
|
||||
// Configure and setup the physical controls
|
||||
void configPhysicalControls(BAPhysicalControls* controls, BAAudioControlWM8731master* codec) |
||||
{ |
||||
// Setup the controls. The return value is the handle to use when checking for control changes, etc.
|
||||
controlPtr = controls; |
||||
codecPtr = codec; |
||||
|
||||
if (!controlPtr) { DEBUG_PRINT(Serial.printf("ERROR: controlPtr is invalid\n\r")); return; } |
||||
if (!codecPtr) { DEBUG_PRINT(Serial.printf("ERROR: codecPtr is invalid\n\r")); return; } |
||||
|
||||
// pushbuttons
|
||||
sw1Handle = controlPtr->addSwitch(BA_EXPAND_SW1_PIN); |
||||
sw2Handle = controlPtr->addSwitch(BA_EXPAND_SW2_PIN); |
||||
sw3Handle = controlPtr->addSwitch(BA_EXPAND_SW3_PIN); |
||||
sw4Handle = controlPtr->addSwitch(BA_EXPAND_SW4_PIN); |
||||
sw5Handle = controlPtr->addSwitch(BA_EXPAND_SW5_PIN); |
||||
sw6Handle = controlPtr->addSwitch(BA_EXPAND_SW6_PIN); |
||||
// pots
|
||||
pot1Handle = controlPtr->addPot(BA_EXPAND_POT1_PIN, potCalibMin, potCalibMax, potSwapDirection); |
||||
pot2Handle = controlPtr->addPot(BA_EXPAND_POT2_PIN, potCalibMin, potCalibMax, potSwapDirection);
|
||||
pot3Handle = controlPtr->addPot(BA_EXPAND_POT3_PIN, potCalibMin, potCalibMax, potSwapDirection); |
||||
pot4Handle = controlPtr->addPot(BA_EXPAND_POT4_PIN, potCalibMin, potCalibMax, potSwapDirection); |
||||
|
||||
// encoders
|
||||
enc1Handle = controlPtr->addRotary(BA_EXPAND_ENC1_A_PIN, BA_EXPAND_ENC1_B_PIN, encSwapDirection); |
||||
enc2Handle = controlPtr->addRotary(BA_EXPAND_ENC2_A_PIN, BA_EXPAND_ENC2_B_PIN, encSwapDirection);
|
||||
enc3Handle = controlPtr->addRotary(BA_EXPAND_ENC3_A_PIN, BA_EXPAND_ENC3_B_PIN, encSwapDirection); |
||||
enc4Handle = controlPtr->addRotary(BA_EXPAND_ENC4_A_PIN, BA_EXPAND_ENC4_B_PIN, encSwapDirection); |
||||
|
||||
// leds
|
||||
led1Handle = controlPtr->addOutput(BA_EXPAND_LED1_PIN); |
||||
led2Handle = controlPtr->addOutput(BA_EXPAND_LED2_PIN); // will illuminate when pressing SW2
|
||||
|
||||
} |
||||
|
||||
void checkPot(unsigned id) |
||||
{ |
||||
float potValue; |
||||
unsigned handle; |
||||
switch(id) { |
||||
case 0 : |
||||
handle = pot1Handle; |
||||
break; |
||||
case 1 : |
||||
handle = pot2Handle; |
||||
break; |
||||
case 2 : |
||||
handle = pot3Handle; |
||||
break; |
||||
case 3 : |
||||
handle = pot4Handle; |
||||
break; |
||||
default : |
||||
handle = pot1Handle; |
||||
} |
||||
|
||||
if ((handle < 0) || (handle >= controlPtr->getNumPots())) { |
||||
DEBUG_PRINT(Serial.printf("ILLEGAL POT HANDLE: %d for id %d\n\r", handle, id)); |
||||
return; |
||||
} |
||||
|
||||
if (controlPtr->checkPotValue(handle, potValue)) { |
||||
// Pot has changed
|
||||
DEBUG_PRINT(Serial.println(String("POT") + id + String(" value: ") + potValue)); |
||||
|
||||
timer = 0; |
||||
display.clearDisplay(); |
||||
display.setCursor(0,displayRow); |
||||
switch(id) { |
||||
case 0 : |
||||
{ |
||||
display.printf("Gain: %0.f\n", potValue * 100.0f); |
||||
int gain = static_cast<int>(std::roundf(31.0f * potValue)); |
||||
codecPtr->setLeftInputGain(gain); |
||||
codecPtr->setRightInputGain(gain); |
||||
yield(); // give time for i2C transfers to complete
|
||||
break; |
||||
} |
||||
case 1 :
|
||||
{ |
||||
display.printf("Level: %0.f\n", potValue * 100.0f); |
||||
volumeOut.gain(0, potValue); |
||||
volumeOut.gain(1, potValue); |
||||
break; |
||||
} |
||||
case 2 : display.printf("Exp T: %0.f\n", potValue * 100.0f); break; |
||||
case 3 : display.printf("Exp R: %0.f\n", potValue * 100.0f); break; |
||||
} |
||||
display.display(); |
||||
} |
||||
|
||||
} |
||||
|
||||
int checkSwitch(unsigned id, bool getValueOnly=false) |
||||
{ |
||||
unsigned swHandle = -1; |
||||
unsigned ledHandle = -1; |
||||
switch(id) { |
||||
case 0 : |
||||
swHandle = sw1Handle; |
||||
ledHandle = led1Handle; |
||||
break; |
||||
case 1 : |
||||
swHandle = sw2Handle; |
||||
ledHandle = led2Handle; |
||||
break; |
||||
case 2 : |
||||
swHandle = sw3Handle; |
||||
break; |
||||
case 3 : |
||||
swHandle = sw4Handle; |
||||
break; |
||||
case 4 : |
||||
swHandle = sw5Handle; |
||||
break; |
||||
case 5 : |
||||
swHandle = sw6Handle; |
||||
break; |
||||
default : |
||||
swHandle = sw1Handle; |
||||
ledHandle = led1Handle; |
||||
} |
||||
|
||||
if ((swHandle < 0) || (swHandle >= controlPtr->getNumSwitches())) { |
||||
DEBUG_PRINT(Serial.printf("ILLEGAL SWITCH HANDLE: %d for id %d\n\r", swHandle, id); Serial.flush()); |
||||
return -1; |
||||
} |
||||
|
||||
bool switchValue; |
||||
bool changed = controlPtr->hasSwitchChanged(swHandle, switchValue); |
||||
if (getValueOnly) { return controlPtr->getSwitchValue(swHandle); } |
||||
|
||||
if (changed) { |
||||
DEBUG_PRINT(Serial.println(String("Button ") + id + String(" pressed"))); |
||||
timer = 0; |
||||
display.clearDisplay(); |
||||
display.setCursor(0, displayRow); |
||||
switch(id) { |
||||
case 0 : display.printf("S1: %d\n", switchValue); break; |
||||
case 1 : display.printf("S2: %d\n", switchValue); break; |
||||
case 2 : display.printf("EncSw A: %d\n", switchValue); break; |
||||
case 3 : display.printf("EncSw B: %d\n", switchValue); break; |
||||
case 4 : display.printf("EncSw C: %d\n", switchValue); break; |
||||
case 5 : display.printf("EncSw D: %d\n", switchValue); break; |
||||
} |
||||
display.display(); |
||||
} |
||||
|
||||
if (swHandle < 2) { // these SWs map to LEDs
|
||||
bool pressed = controlPtr->isSwitchHeld(swHandle); |
||||
controlPtr->setOutput(ledHandle, pressed); |
||||
} |
||||
return controlPtr->getSwitchValue(swHandle); |
||||
} |
||||
|
||||
void checkEncoder(unsigned id) |
||||
{ |
||||
unsigned encHandle; |
||||
static int enc1 = 0, enc2 = 0, enc3 = 0, enc4 = 0; |
||||
switch(id) { |
||||
case 0 : |
||||
encHandle = enc1Handle; |
||||
break; |
||||
case 1 : |
||||
encHandle = enc2Handle; |
||||
break; |
||||
case 2 : |
||||
encHandle = enc3Handle; |
||||
break; |
||||
case 3 : |
||||
encHandle = enc4Handle; |
||||
break; |
||||
default : |
||||
encHandle = enc1Handle; |
||||
} |
||||
|
||||
if ((encHandle < 0) || (encHandle >= controlPtr->getNumRotary())) { |
||||
DEBUG_PRINT(Serial.printf("ILLEGAL ENCODER HANDLE: %d for id %d\n\r", encHandle, id); Serial.flush()); |
||||
return; |
||||
} |
||||
|
||||
int adj= controlPtr->getRotaryAdjustUnit(encHandle); |
||||
if (adj != 0) {
|
||||
DEBUG_PRINT(Serial.printf("Enc %d: %d\n\r", id, adj); Serial.flush());
|
||||
display.clearDisplay(); |
||||
display.setCursor(0, displayRow); |
||||
switch(id) { |
||||
case 0 : enc1 += adj; display.printf("Enc A: %d", enc1); break; |
||||
case 1 : enc2 += adj; display.printf("Enc B: %d", enc2); break; |
||||
case 2 : enc3 += adj; display.printf("Enc C: %d", enc3); break; |
||||
case 3 : enc4 += adj; display.printf("Enc D: %d", enc4); break; |
||||
} |
||||
display.display(); |
||||
timer = 0; |
||||
} |
||||
} |
@ -0,0 +1,11 @@ |
||||
#ifndef PHYSICAL_CONTROLS_H_ |
||||
#define PHYSICAL_CONTROLS_H_ |
||||
|
||||
#include "BALibrary.h" |
||||
|
||||
void configPhysicalControls(BALibrary::BAPhysicalControls* controls, BALibrary::BAAudioControlWM8731master* codec); |
||||
void checkPot(unsigned id); |
||||
int checkSwitch(unsigned id, bool getValueOnly=false); |
||||
void checkEncoder(unsigned id); |
||||
|
||||
#endif |
Loading…
Reference in new issue