// https://wokwi.com/projects/387121472215461889
// https://forum.arduino.cc/t/effect-switcher-and-relays-eeprom-question/1209660
#include <EEPROM.h>
#include <LiquidCrystal.h>
#include <Wire.h>
#include <Keypad.h>
#include <ezButton.h>
#include <Adafruit_NeoPixel.h>
ezButton ezBankButton(A6), ezProgButton(A7), ezSaveButton(A9), ezDownButton(A12);
LiquidCrystal lcd(A5, A4, A3, A2, A1, A0);
# define ELEDS 9
# define EPIN 32
# define PLEDS 9
# define PPIN 33
Adafruit_NeoPixel eLED(ELEDS, EPIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel pLED(PLEDS, PPIN, NEO_GRB + NEO_KHZ800);
#define rHot LOW
#define rGnd HIGH
void setupRelays();
void switchRelay(int, int);
const byte rows = 9;
const byte cols = 1;
char keys[rows][cols] = {
{ 'a' },
{ 'b' },
{ 'c' },
{ 'd' },
{ 'e' },
{ 'f' },
{ 'g' },
{ 'h' },
{ 'i' },
};
const int numberOfPedals = 9; //+ is also number of effects
const int bankSize = numberOfPedals * numberOfPedals; // bank variable to skip ahead by a full bank
const int numberOfBanks = 9; //+ up to 50
byte rowPins[rows] = { 34, 35, 36, 37, 38, 39, 40, 41, 42 }; /* buttons or momentary switches */
const byte colPins[cols] = { A15 }; // just 1 column a..i
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, rows, cols);
const int OneRelayPin[numberOfPedals] = { 11, 14, 15, 16, 17, 18, 19, 20, 21 }; /* pin 1 on relay - reset/default has pin 1 at 0v - RED LED */
const int TenRelayPin[numberOfPedals] = { 10, 9, 8, 7, 6, 5, 4, 3, 2 }; /* pin 10 on relay - reset/default has pin 10 at 5v - BLUE LED */
const int presetLEDPin[numberOfPedals] = { 44, 45, 46, 47, 48, 49, 50, 51, 52 }; /* shows which preset is engaged - GREEN LED */
const int effectLEDPin[numberOfPedals] = { 22, 23, 24, 25, 26, 27, 28, 29, 30 }; /* shows which effects are engaged - YELLOW LED */
const int progLED = A8; // shows if we're in program mode - GREEN LED
const int saveLED = A10; // shows if we're in save mode - YELLOW LED (dull red, turns yellow when combined with program's GREEN LED)
byte midiChannel = 0;
int currentBank = 0; //Starting Bank Number
boolean saveOn = false;
boolean progOn = false;
/******************************************************/
void setup() {
lcd.begin(16, 2);
lcd.print("I am a ");
lcd.setCursor(0, 1);
lcd.print("real cowboy. ");
setupVLEDs(); // see if they're working
setupRelays(); // pins and initial state
pinMode(progLED, OUTPUT);
pinMode(saveLED, OUTPUT);
for (int ii = 0; ii < numberOfPedals; ii++) /* setup device */
{
pinMode(presetLEDPin[ii], OUTPUT);
pinMode(effectLEDPin[ii], OUTPUT);
digitalWrite(presetLEDPin[ii], LOW);
digitalWrite(effectLEDPin[ii], LOW);
delay(10);
}
delay(10);
for (int ii = 0; ii < numberOfPedals; ii++) {
digitalWrite(presetLEDPin[ii], HIGH);
digitalWrite(effectLEDPin[ii], HIGH);
delay(10);
}
digitalWrite(progLED, LOW);
digitalWrite(saveLED, LOW);
delay(10);
digitalWrite(progLED, HIGH);
digitalWrite(saveLED, HIGH);
delay(10);
Serial.begin(115200); // there is no MIDI
ezBankButton.setDebounceTime(20);
ezProgButton.setDebounceTime(20);
ezSaveButton.setDebounceTime(20);
ezDownButton.setDebounceTime(20);
delay(1500);
}
/*********************************************************/
void midiProg(byte status, int data) {
Serial.write(status);
Serial.write(data);
}
/*********************************************************/
// save your effect/loop selections to a preset
void memory(int addr, int led) {
for (int ii = 0; ii < numberOfPedals; ii++) {
EEPROM.write((addr) + ii, digitalRead(effectLEDPin[ii]));
setPresetLED(ii, HIGH);
}
//lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Program saved to");
lcd.setCursor(0, 1);
lcd.print("Bank ");
lcd.print(currentBank);
lcd.print(" Preset ");
lcd.print(led + 1);
lcd.print(" ");
//for (int ii = 0; ii < 8; ii++) {
// savePresetLED(led, (ii & 1) ? HIGH : LOW);
// delay(100); pLED.show();
//}
// start the ACK blinker
blinkAnAck(led);
/* Preset LED will flash when saving effects loops */
//delay(100);
//digitalWrite(presetLEDPin[led], LOW);
//delay(100);
//digitalWrite(presetLEDPin[led], HIGH);
//delay(100);
//digitalWrite(presetLEDPin[led], LOW);
//delay(100);
//digitalWrite(presetLEDPin[led], HIGH);
//delay(100);
//digitalWrite(presetLEDPin[led], LOW);
//delay(100);
//digitalWrite(presetLEDPin[led], HIGH);
//delay(100);
//digitalWrite(presetLEDPin[led], LOW);
//delay(100);
//digitalWrite(presetLEDPin[led], HIGH);
saveOn = !saveOn; // turn off program/save modes
progOn = !progOn;
for (int ii = 0; ii < numberOfPedals; ii++) {
setEffectLED(ii, HIGH); // low? off
}
lcd.clear();
lcd.print("Program Again Or");
lcd.setCursor(0, 1);
lcd.print("Press Any Preset");
}
/*********************************************************/
void resetAllRelays() {
for (int ii = 0; ii < numberOfPedals; ii++) {
digitalWrite(OneRelayPin[ii], rGnd);
digitalWrite(TenRelayPin[ii], rGnd);
}
}
/*********************************************************/
void resetAllLeds() {
for (int ii = 0; ii < numberOfPedals; ii++) {
setPresetLED(ii, HIGH); // low? off
}
}
/*********************************************************/
// select your effects/loops
void writeOut(int relay) {
resetAllLeds();
dumpEEPROM();
setEffectLED(relay, getEffectLED(relay) ? LOW : HIGH);
//digitalWrite(effectLEDPin[relay], !digitalRead(effectLEDPin[relay]));
// lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Current Bank: ");
lcd.print(currentBank);
lcd.setCursor(0, 1);
lcd.print("Select Loops: ");
if (relay >= 10) {
lcd.print(relay + 1);
lcd.print(" ");
} else {
lcd.print(" ");
lcd.print(relay + 1);
}
}
/*********************************************************/
// default mode - USE THE SWITCH!
void readPreset(int addr, int led) {
for (int ii = 0; ii < numberOfPedals; ii++) {
int kPreset = EEPROM.read((addr) + ii);
switchRelay(ii, kPreset ? HIGH : LOW);
// setEffectLED(ii, kPreset);
// setPresetLED(ii, HIGH);
}
delay(9);
resetAllRelays();
for (int ii = 0; ii < numberOfPedals; ii++) {
int kPreset = EEPROM.read((addr) + ii);
setEffectLED(ii, kPreset);
setPresetLED(ii, HIGH);
}
setPresetLED(led, LOW);
//lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" B-");
lcd.print(currentBank);
lcd.setCursor(0, 1);
lcd.print("Preset ");
lcd.print(led + 1);
lcd.print(" ");
}
/*********************************************************/
void loop() {
ezBankButton.loop();
ezProgButton.loop();
ezSaveButton.loop();
ezDownButton.loop();
bool doBank = ezBankButton.isPressed();
bool doProg = ezProgButton.isPressed();
bool doSave = ezSaveButton.isPressed();
bool doDown = ezDownButton.isPressed(); // down will be easier to get working now ;-)
bool anyInput = doBank || doProg || doSave || doDown;
// make an LED finish blinking an ACK after saving
runAckBlinker();
if (anyInput) killAckBlinker(); // Oops! Let's wrap this up now
static bool firstTime = true;
doBank |= firstTime;
firstTime = false; // makes the bank = 1 on start up
if (doBank) {
currentBank++;
if (currentBank > numberOfBanks) currentBank = 1;
Serial.print("bank became ");
Serial.println(currentBank);
for (int ii = 0; ii < numberOfPedals; ii++) {
setEffectLED(ii, HIGH);
setPresetLED(ii, HIGH);
}
// lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" B-");
lcd.print(currentBank);
lcd.setCursor(0, 1);
lcd.print("Press Any Preset");
}
if (doProg) {
progOn = !progOn;
if (progOn == HIGH && saveOn == HIGH) {
saveOn = !saveOn; // make sure we're not in save mode
// lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Program Mode: ");
lcd.setCursor(0, 1);
lcd.print("Select Loops ");
dumpEEPROM();
} else if (progOn == HIGH) {
for (int ii = 0; ii < numberOfPedals; ii++) {
setEffectLED(ii, HIGH);
setPresetLED(ii, HIGH);
}
lcd.setCursor(0, 0);
lcd.print("Program Mode: ");
lcd.setCursor(0, 1);
lcd.print("Select Loops ");
dumpEEPROM();
} else {
//lcd.clear(); // resets the LCD to default if we exit program mode
lcd.setCursor(0, 0);
lcd.print(" B-");
lcd.print(currentBank);
lcd.setCursor(0, 1);
lcd.print("Press Any Preset");
}
}
digitalWrite(progLED, progOn);
if (doSave) {
saveOn = !saveOn;
if (saveOn == HIGH && progOn == HIGH) {
// lcd.clear(); // actual save mode
lcd.setCursor(0, 0);
lcd.print("Save Mode: ");
lcd.setCursor(0, 1);
lcd.print("Select Preset ");
dumpEEPROM();
} else if (saveOn == HIGH && progOn == LOW) {
//lcd.clear(); // warns to exit save mode before hitting any buttons
//lcd.print("Uh Oh!");
saveOn = !saveOn;
} else {
// lcd.clear(); // resets the LCD to default if we exit save mode
lcd.setCursor(0, 0);
lcd.print(" B-");
lcd.print(currentBank);
lcd.setCursor(0, 1);
lcd.print("Press Any Preset");
}
}
digitalWrite(saveLED, saveOn);
unsigned char kMode = 0;
if (saveOn) kMode += 2;
if (progOn) kMode += 1;
char key = keypad.getKey();
if (!key) return;
char mKey = key - 'a'; // mKey is therefore 0..8
if (0) {
Serial.print(kMode);
Serial.print(" mode ");
Serial.print((int)mKey);
Serial.print(" token ");
Serial.println("");
}
#define READ 0
#define WRITE 1
#define MEMORY 3
switch (kMode) {
case WRITE:
writeOut(mKey); // relay
break;
case MEMORY:
Serial.print("saving to bank ");
Serial.println(currentBank);
memory(currentBank * bankSize + numberOfPedals * mKey, mKey); // addr, led
break;
case READ:
Serial.print("reading bank ");
Serial.print(currentBank);
Serial.print(", offset ");
Serial.println(numberOfPedals * mKey);
readPreset(currentBank * bankSize + numberOfPedals * mKey, mKey); // addr, relay
break;
default:
Serial.print(kMode);
Serial.println(" error");
}
}
void setupRelays() {
for (int ii = 0; ii < numberOfPedals; ii++) {
pinMode(OneRelayPin[ii], OUTPUT);
pinMode(TenRelayPin[ii], OUTPUT);
digitalWrite(OneRelayPin[ii], LOW);
delay(20);
digitalWrite(TenRelayPin[ii], LOW);
digitalWrite(OneRelayPin[ii], HIGH);
delay(20);
digitalWrite(TenRelayPin[ii], HIGH);
// initRelay();
}
}
void switchRelay(int relayNumber, int onOff) {
// engages relays on and off in read mode
if (onOff) {
digitalWrite(OneRelayPin[relayNumber], rGnd);
digitalWrite(TenRelayPin[relayNumber], rHot);
} else {
digitalWrite(TenRelayPin[relayNumber], rGnd);
digitalWrite(OneRelayPin[relayNumber], rHot);
}
}
void dumpEEPROM() {
int theBank = currentBank; // who cares about other banks?
Serial.print("bank ");
Serial.print(theBank);
Serial.println(":+");
for (int thePreset = 0; thePreset < numberOfPedals; thePreset++) {
Serial.print(" preset ");
Serial.print(thePreset);
Serial.print(": ");
bool anyEffect = false; // until proven otherwise
for (int theEffect = 0; theEffect < numberOfPedals; theEffect++) {
unsigned char fxON = EEPROM.read(theBank * bankSize + thePreset * numberOfPedals + theEffect);
if (!fxON) {
anyEffect = true;
break;
} // break; alto777 - why does this break break it?
Serial.print(fxON ? "." : "X"); // should be ON lamps
}
if (anyEffect) {
Serial.print(" SOME");
// so here, when the mode is waiting for a slot to store the builded preset
// we can light up the slots that already have loops selected:
setPresetLED(thePreset, LOW);
} else {
Serial.print(" none");
}
Serial.println(" <");
}
}
void setupVLEDs()
{
eLED.begin();
pLED.begin();
eLED.setPixelColor(0, 0xff0000);
eLED.setPixelColor(1, 0x00ff00);
eLED.setPixelColor(2, 0x0000ff);
pLED.setPixelColor(0, 0xff0000);
pLED.setPixelColor(1, 0x00ff00);
pLED.setPixelColor(2, 0x0000ff);
eLED.setPixelColor(ELEDS - 1, 0xffffff);
pLED.setPixelColor(PLEDS - 1, 0xffffff);
eLED.show();
pLED.show();
delay(200); pLED.clear(); pLED.show(); eLED.clear(); eLED.show();
}
char kStoreEffectLED[numberOfPedals];
const unsigned long kDIM0 = 0x000000; // clear
const unsigned long kDIM1 = 0x008080; // CYAN/2
const unsigned long kDIM2 = 0x800080; // MAGENTA/2
const unsigned long kDIM3 = 0xffff00; // Yellow
void setEffectLED(int theLED, int theValue)
{
// yellow LEDs toggle, vLED turns off other vLEDs ??? //+ remains an defect
//Serial.print(" setEffect "); Serial.print(theLED); Serial.print(" "); Serial.println(theValue);
digitalWrite(effectLEDPin[theLED], theValue ? HIGH : LOW);
eLED.setPixelColor(theLED, theValue ? kDIM1 : kDIM3);
eLED.show();
kStoreEffectLED[theLED] = theValue;
}
int getEffectLED(int theLED)
{
//Serial.print(kStoreEffectLED[theLED] ? 1 : 0); Serial.println(" get effect");
// return digital Read(effectLEDPin[theLED]) == HIGH ? 1 : 0; // true or false
return kStoreEffectLED[theLED] ? 1 : 0;
}
void setPresetLED(int theLED, int theValue)
{
digitalWrite(presetLEDPin[theLED], theValue ? HIGH : LOW);
pLED.setPixelColor(theLED, theValue ? kDIM2 : 0x00ff00); // kDIM, kGREEN
pLED.show();
}
void savePresetLED(int theLED, int theValue)
{
digitalWrite(presetLEDPin[theLED], theValue ? HIGH : LOW);
pLED.setPixelColor(theLED, theValue ? kDIM2 : 0xff9900); // kDIM, Sherbert
pLED.show();
}
// delay-less interruptable blinker
// this could be done better with a struct or class
static bool blinkingAnAck = false;
static int blinkCounter;
static int blinkingLED;
// too many, too slow. adjust to taste:
const byte blinkPeriod = 75;
const byte blinkNTimes = 17; // has to be odd to leave the LED off.
void blinkAnAck(int led) {
Serial.print("blink an ack on "); Serial.println(led);
blinkingAnAck = true;
blinkCounter = blinkNTimes;
blinkingLED = led;
}
void runAckBlinker()
{
if (!blinkingAnAck) return;
// Serial.println("WTF - runAckBlinker");
static unsigned long lastBlink;
unsigned long now = millis(); //use global now when it is available
if (now - lastBlink > blinkPeriod) {
Serial.print(blinkCounter);
Serial.println(" toggle ");
digitalWrite(presetLEDPin[blinkingLED], blinkCounter & 0x1 ? HIGH : LOW);
pLED.setPixelColor(blinkingLED, blinkCounter & 0x1 ? kDIM2 : 0xff9900); // kDIM, Sherbert
pLED.show();
blinkCounter--;
lastBlink = now;
}
if (!blinkCounter) killAckBlinker();
}
void killAckBlinker()
{
if (blinkingAnAck) {
Serial.println(" the cows are home!");
digitalWrite(effectLEDPin[blinkingLED], HIGH);
blinkingAnAck = false;
}
}
BANK
PROG
SAVE