// https://wokwi.com/projects/387935845357494273
// 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>
unsigned long now; // global current m i l l i s ( ) value for all processes
// some use of delay is OK, here mostly setup, maybe for relay coil pulses of 0.003 seconds
void okDelay(unsigned long theDelay)
{
delay(theDelay);
}
// will be in LCD h and cpp files one day, along with all lcd.<method> calls
// list of different LCD display screens
enum escreen {
DUMBBITCH = 101, READPRESET, WRITEOUT, RUN, SAVED, PROGRAMMODE, SAVEMODE, SPLASH, NOSCREEN
};
char *screenTag[] = {
"DUMBBITCH", "READPRESET", "WRITEOUT", "RUN", "SAVED", "PROGRAMMODE", "SAVEMODE", "SPLASH", "NO_SCREEN"
};
ezButton ezBankButton(A6), ezProgButton(A7), ezSaveButton(A9), ezDownButton(A11);
LiquidCrystal lcd(A5, A4, A3, A2, A1, A0);
# define ELEDS 16
# define EPIN 12
# define PLEDS 16
# define PPIN 13
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 = 2;
const byte cols = 8;
char keys[rows][cols] = {
{ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' },
{ 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p' },
};
const int numberOfPedals = 16; //+ 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
const int numberOfEfBks = 2;
byte colPins[cols] = { 17, 5, 6, 7, 14, 9, 15, 11 }; /* buttons or momentary switches */
const byte rowPins[rows] = { 2 }; // just 1 column a..i
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, rows, cols);
const int OneRelayPin[numberOfPedals] = { 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37 }; /* pin 1 on relay - reset/default has pin 1 at 0v - RED LED */
const int TenRelayPin[numberOfPedals] = { 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53 }; /* pin 10 on relay - reset/default has pin 10 at 5v - BLUE 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)
int photoPin[2] = { A12, A13 };
byte midiChannel = 0;
int currentBank = 1; //Starting Preset Bank Number
int effectBank = 1; //Starting Effect Bank Number
enum estate {READ = 0, WRITE, ERROR, SAVE};
const char *modeTag[] = {"READ", "WRITE", "ERROR", "SAVE"};
byte realMode = READ;
const byte NOKEY = 99;
/*********************************************************/
void midiProg(byte status, int data) {
Serial.write(status);
Serial.write(data);
}
/*********************************************************/
// These reset things.
void resetAllRelays() {
for (int ii = 0; ii < numberOfPedals; ii++) {
digitalWrite(OneRelayPin[ii], rGnd);
digitalWrite(TenRelayPin[ii], rGnd);
}
}
void progLampsOff() {
for (int ii = 0; ii < numberOfPedals; ii++) {
setPresetLED(ii, HIGH); // low? off
}
}
void allLampsOff() {
for (int ii = 0; ii < numberOfPedals; ii++) {
setPresetLED(ii, HIGH); // low? off
setEffectLED(ii, HIGH); // low? off
}
}
/*********************************************************/
// save your effect/loop selections to a preset
void memory(int theAddr, int theLED) {
for (int ii = 0; ii < numberOfPedals; ii++) {
EEPROM.write((theAddr) + ii, getEffectLED(ii));
setPresetLED(ii, HIGH);
}
paintLCD(SAVED, theLED);
blinkAnAck(theLED);
for (int ii = 0; ii < numberOfPedals; ii++) {
setEffectLED(ii, HIGH); // low? off
}
}
/*********************************************************/
// select your effects/loops
void writeOut(int relay) {
progLampsOff();
setEffectLED(relay, getEffectLED(relay) ? LOW : HIGH);
paintLCD(WRITEOUT, relay);
}
/*********************************************************/
// default mode - USE THE SWITCH!
void readPreset(int theAddr, int theLED) {
Serial.print("WTF readPreset ");
Serial.print(theAddr); Serial.print(" theAddr ");
Serial.print(theLED); Serial.print(" theLED ");
Serial.println("");
for (int ii = 0; ii < 2; ii++) {
digitalWrite(photoPin[ii], LOW); //turn ON all photofets
}
okDelay(1);
for (int ii = 0; ii < numberOfPedals; ii++) {
int kPreset = EEPROM.read((theAddr) + ii);
switchRelay(ii, kPreset ? HIGH : LOW);
}
okDelay(6);
for (int ii = 0; ii < 2; ii++) {
digitalWrite(photoPin[ii], HIGH); //turn ON all photofets
}
resetAllRelays();
for (int ii = 0; ii < numberOfPedals; ii++) {
int kPreset = EEPROM.read((theAddr) + ii);
setEffectLED(ii, kPreset);
setPresetLED(ii, HIGH);
}
setPresetLED(theLED, LOW);
paintLCD(READPRESET, theLED);
}
/*********************************************************/
void loop() {
now = millis(); // with zero delays, now is now is now for eveyone
ezBankButton.loop();
ezProgButton.loop();
ezSaveButton.loop();
ezDownButton.loop();
char key = keypad.getKey();
if (!key) {
key = NOKEY;
} else {
key -= 'a';
if (effectBank == 2)
key += cols;
}
bool doBank = ezBankButton.isPressed();
bool doProg = ezProgButton.isPressed();
bool doSave = ezSaveButton.isPressed();
bool doDown = ezDownButton.isPressed();
bool anyInput = doBank || doProg || doSave || doDown || key != NOKEY;
serviceLCD(anyInput); // manage transient screens. time or user activity moves us along
thoseOtherLEDs(); // light up the red/green/yellow tell-tale LED accordian to mode
runAckBlinker();
if (anyInput) killAckBlinker(); // Oops! Let's wrap this up now
/*
Leaving here for now - will remove once bank buttons find their permanent home in working order
if (realMode == READ) {
if (doBank) {
currentBank++;
if (currentBank > numberOfBanks) currentBank = 1;
Serial.print("bank became "); Serial.println(currentBank);
allLampsOff();
paintLCD(RUN, 0, 102);
}
if (doDown) {
currentBank--;
if (currentBank < 1) currentBank = numberOfBanks;
Serial.print("bank became "); Serial.println(currentBank);
allLampsOff();
paintLCD(RUN, 0, 102);
}
}
if (realMode == WRITE) {
if (doBank) {
effectBank++;
if (effectBank > numberOfEfBks) effectBank = 1;
Serial.print("bank became "); Serial.println(effectBank);
paintLCD(WRITEOUT, 0, 103);
}
if (doDown) {
effectBank--;
if (effectBank < 1) effectBank = numberOfEfBks;
Serial.print("bank became "); Serial.println(effectBank);
paintLCD(WRITEOUT, 0, 103);
}
}
if (doBank) {
realMode = READ;
currentBank++;
if (currentBank > numberOfBanks) currentBank = 1;
Serial.print("bank became "); Serial.println(currentBank);
allLampsOff();
paintLCD(RUN, 0, 102);
}
if (doDown) {
realMode = READ;
currentBank--;
if (currentBank < 1) currentBank = numberOfBanks;
Serial.print("bank became "); Serial.println(currentBank);
allLampsOff();
paintLCD(RUN, 0, 102);
}
*/
switch (realMode) {
case WRITE:
if (doProg) {
realMode = READ;
allLampsOff();
// alreadyStored(); // light up occupied presets. name it better
paintLCD(RUN, 0, 103);
}
if (doSave) {
realMode = SAVE;
alreadyStored(); // light up occupied presets. name it better
paintLCD(SAVEMODE);
}
if (key != NOKEY) {
writeOut(key); // relay
alreadyStored(); // light up occupied presets. name it better
}
if (doBank) {
effectBank++;
if (effectBank > numberOfEfBks) effectBank = 1;
Serial.print("bank became "); Serial.println(effectBank);
paintLCD(WRITEOUT, 0, 103);
}
if (doDown) {
effectBank--;
if (effectBank < 1) effectBank = numberOfEfBks;
Serial.print("bank became "); Serial.println(effectBank);
paintLCD(WRITEOUT, 0, 103);
}
break;
case SAVE:
if (effectBank = 2) {
effectBank--;
}
if (doProg) {
realMode = READ;
alreadyStored(); // light up occupied presets. name it better
paintLCD(RUN, 0, 103);
}
if (doSave) {
Serial.println("I am already in save mode waiting for you to pick a slot get off my back");
}
if (key != NOKEY) {
memory(currentBank * bankSize + numberOfPedals * key, key); // theAddr, theLED
realMode = READ;
paintLCD(RUN, 0, 101);
}
break;
case READ:
if (doProg) {
realMode = WRITE;
allLampsOff();
alreadyStored(); // light up occupied presets. name it better
paintLCD(PROGRAMMODE);
}
if (doSave) {
paintLCD(DUMBBITCH);
paintLCD(RUN, 0, 104);
}
if (key != NOKEY) {
readPreset(currentBank * bankSize + numberOfPedals * key, key); // theAddr, relay
}
if (doBank) {
currentBank++;
if (currentBank > numberOfBanks) currentBank = 1;
Serial.print("bank became "); Serial.println(currentBank);
paintLCD(RUN, 0, 102);
}
if (doDown) {
currentBank--;
if (currentBank < 1) currentBank = numberOfBanks;
Serial.print("bank became "); Serial.println(currentBank);
paintLCD(RUN, 0, 102);
}
break;
default:
Serial.print(realMode);
Serial.println(" error");
}
}
void thoseOtherLEDs()
{
switch (realMode) {
case READ :
digitalWrite(progLED, LOW);
digitalWrite(saveLED, LOW);
break;
case WRITE :
digitalWrite(progLED, HIGH);
digitalWrite(saveLED, LOW);
break;
case SAVE :
digitalWrite(progLED, HIGH);
digitalWrite(saveLED, HIGH);
break;
default :
Serial.println("error 405.");
}
}
void spillMode()
{
Serial.println("spillMode broken just now, write it");
}
void setupRelays() {
for (int ii = 0; ii < numberOfPedals; ii++) {
pinMode(OneRelayPin[ii], OUTPUT);
pinMode(TenRelayPin[ii], OUTPUT);
pinMode(photoPin[ii], OUTPUT);
digitalWrite(photoPin[ii], LOW);
digitalWrite(OneRelayPin[ii], LOW);
okDelay(20);
digitalWrite(TenRelayPin[ii], LOW);
digitalWrite(OneRelayPin[ii], HIGH);
okDelay(20);
digitalWrite(TenRelayPin[ii], HIGH);
okDelay(20);
digitalWrite(photoPin[ii], HIGH);
}
}
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 alreadyStored() {
int theBank = currentBank; // who cares about other banks?
for (int thePreset = 0; thePreset < numberOfPedals; thePreset++) {
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;
}
}
if (anyEffect) {
setPresetLED(thePreset, LOW);
} else {
}
}
}
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();
okDelay(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)
{
eLED.setPixelColor(theLED, theValue ? kDIM1 : kDIM3);
eLED.show();
kStoreEffectLED[theLED] = theValue;
}
int getEffectLED(int theLED)
{
return kStoreEffectLED[theLED] ? 1 : 0;
}
void setPresetLED(int theLED, int theValue)
{
pLED.setPixelColor(theLED, theValue ? kDIM2 : 0x00ff00); // kDIM, kGREEN
pLED.show();
}
void savePresetLED(int theLED, int theValue)
{
pLED.setPixelColor(theLED, theValue ? kDIM2 : 0xff9900); // kDIM, Sherbert
pLED.show();
}
// delay-less interruptable blinker
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 theLED off.
void blinkAnAck(int theLED) {
Serial.print("blink an ack on "); Serial.println(theLED);
blinkingAnAck = true;
blinkCounter = blinkNTimes;
blinkingLED = theLED;
}
void runAckBlinker()
{
if (!blinkingAnAck) return;
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 ");
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!");
eLED.clear();
blinkingAnAck = false;
}
}
void beginLCD()
{
lcd.begin(16, 2); // Sorry, Mom!
}
unsigned long lcdDwellTime; // timer for transient screen messages
const unsigned long DWELL = 2500; // holds off a pending escreen for that long - long so we can see it
// get around to displaying this after the transient message blocked it from being
byte pendedScreenName = NOSCREEN;
byte pendedArgument;
byte pendedExtra;
void serviceLCD(bool userInput)
{
if (lcdDwellTime && now - lcdDwellTime > DWELL || userInput) {
Serial.println("dwell expired or user input");
lcd.clear();
lcdDwellTime = 0; // means LCD is free for all
if (pendedScreenName != NOSCREEN)
paintLCD(pendedScreenName, pendedArgument, pendedExtra);
pendedScreenName = NOSCREEN; // because we out of this world of pain
}
}
int paintLCD(byte screenName) {
paintLCD(screenName, 97, 98);
}
int paintLCD(byte screenName, byte argument) {
paintLCD(screenName, argument, 99);
}
int paintLCD(byte screenName, byte argument, byte extra)
{
Serial.print(" painting ");
Serial.print(screenTag[screenName - DUMBBITCH]);
Serial.print(" argument "); Serial.print(argument);
Serial.print(" extra "); Serial.print(extra);
Serial.println(" ");
if (lcdDwellTime) {
Serial.println("stashing the pended screen call for later");
pendedScreenName = screenName;
pendedArgument = argument;
pendedExtra = extra;
return;
}
switch (screenName) {
case DUMBBITCH :
lcdDwellTime = now; // transient message. time or user activity moves to the pended screen
dumbBitchLCD();
break;
case READPRESET :
readPresetLCD(argument);
break;
case WRITEOUT :
writeOutLCD(argument);
break;
case RUN :
runLCD(extra);
break;
case SAVED :
lcdDwellTime = now; // transient message. time or user activity moves to the pended screen
savedLCD(argument);
break;
case PROGRAMMODE :
programModeLCD();
break;
case SAVEMODE :
saveModeLCD();
break;
case SPLASH :
splashLCD();
break;
default :
Serial.print(screenName);
Serial.println(" error unknown screen name/number");
}
return 0; // normally ignored but available
}
void dumbBitchLCD()
{
lcd.setCursor(0, 0);
lcd.print("User Error: ");
lcd.setCursor(0, 1);
lcd.print("Dumb Bitch! ");
}
void readPresetLCD(byte theLED)
{
lcd.setCursor(0, 0);
lcd.print(" B-");
lcd.print(currentBank);
lcd.setCursor(0, 1);
lcd.print("Preset ");
lcd.print(theLED + 1);
lcd.print(" ");
}
void runLCD(byte wtf)
{
Serial.print(wtf); Serial.println(" paint run mode LCD");
lcd.setCursor(0, 0);
lcd.print(" B-");
lcd.print(currentBank);
lcd.setCursor(0, 1);
lcd.print("Press Any Preset");
}
void writeOutLCD(byte theRelay)
{
lcd.setCursor(0, 0);
lcd.print("Effect B-");
lcd.print(effectBank);
lcd.print(" | B-");
lcd.print(currentBank);
lcd.setCursor(0, 1);
lcd.print("Effect: ");
lcd.print(theRelay + 1);
lcd.print(" ");
}
//... might take bank as an argument and begin removing the global (currentBank in some version already but still global)
void savedLCD(byte theLED)
{
// Serial.println("paint saved transitional message mode LCD");
lcd.setCursor(0, 0);
lcd.print("Program saved to");
lcd.setCursor(0, 1);
lcd.print("Bank ");
lcd.print(currentBank);
lcd.print(" Preset ");
lcd.print(theLED + 1);
lcd.print(" ");
}
void programModeLCD()
{
lcd.setCursor(0, 0);
lcd.print("Program Mode: ");
lcd.setCursor(0, 1);
lcd.print("Select Loops ");
}
void saveModeLCD()
{
lcd.setCursor(0, 0);
lcd.print("Save Mode: ");
lcd.setCursor(0, 1);
lcd.print("Select Preset ");
}
void splashLCD()
{
lcd.setCursor(0, 0);
lcd.print("I am a ");
lcd.setCursor(0, 1);
lcd.print("real cowboy. ");
}
/******************************************************/
void setup() {
Serial.begin(1000000); /* not for midi communication - pin 1 TX */
Serial.println("first things first.\n");
beginLCD();
paintLCD(SPLASH);
setupVLEDs(); // see if they're working
setupRelays(); // pins and initial state
pinMode(progLED, OUTPUT);
pinMode(saveLED, OUTPUT);
digitalWrite(progLED, LOW);
digitalWrite(saveLED, LOW);
okDelay(10);
digitalWrite(progLED, HIGH);
digitalWrite(saveLED, HIGH);
okDelay(10);
ezBankButton.setDebounceTime(20);
ezProgButton.setDebounceTime(20);
ezSaveButton.setDebounceTime(20);
ezDownButton.setDebounceTime(20);
paintLCD(RUN, 0, 111);
}UP
PROG/SAVE
PHOTO
DOWN
PROG
SAVE