I have gone through and incorporated a lot of what you've done - obviously I had to finagle some things into my current setup, but it's working* (hehe). The wokwi:
https://wokwi.com/projects/387935845357494273
I've brought the title inline with 16 loops - I've put in all the code that was relevant per the comments written. I then went and cleaned up the code and renamed things more consistently (this may not be helpful for y'all to troubleshoot issues, but once your code is mixed with my code things start to get crowded and not make sense - apologies in advance, and arrears, for that!)
I have found a few quirks - namely, 1) the buttons are skipping worse than ever. I have a feeling there's something hitting them twice but I can't find it, and 2) when I hit save and then select a preset, the LCD goes blank. (oddly, when I tried this in bank 9, and it worked as expected, but in banks 1 and 2 it just went blank after a microsecond - very odd).
When accounting for the proper LED to light up for readPreset, it's making the LCD reset every time I hit a new preset. This doesn't bother me much, but it's just something I'm noticing.
There might be other quirks, but I haven't found em yet... If you see anything off the bat that could be causing those issues, I'm all ears. Here's my full code:
// 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 12
# define EPIN 32
# define PLEDS 12
# 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 = 1;
const byte cols = 12;
char keys[rows][cols] = {
{ 'a','b','c','d','e','f','g','h','i','j','k','l' },
};
const int numberOfPedals = 12; //+ 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 colPins[cols] = { 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45 }; /* buttons or momentary switches */
const byte rowPins[rows] = { 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, 22, 23, 26 }; /* 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, 24, 25, 27 }; /* 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)
byte midiChannel = 0;
int currentBank = 1; //Starting 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 < numberOfPedals; ii++) {
int kPreset = EEPROM.read((theAddr) + ii);
switchRelay(ii, kPreset ? HIGH : LOW);
}
okDelay(7);
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';
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
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;
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
}
break;
case SAVE:
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) {
dumbBitchLCD();
paintLCD(DUMBBITCH);
// delayForTEST(777); // switched up to timed/mortal for delay-less DWELL
paintLCD(RUN, 0, 104);
}
if (key != NOKEY) {
readPreset(currentBank * bankSize + numberOfPedals * key, key); // theAddr, relay
}
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);
digitalWrite(OneRelayPin[ii], LOW);
okDelay(20);
digitalWrite(TenRelayPin[ii], LOW);
digitalWrite(OneRelayPin[ii], HIGH);
okDelay(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 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
// 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 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 loop_testLCD() {
now = millis();
ezBankButton.loop();
ezProgButton.loop();
ezSaveButton.loop();
ezDownButton.loop();
bool doBitch = ezBankButton.isPressed();
bool doUser = ezProgButton.isPressed();
bool doPended = ezSaveButton.isPressed();
bool doDown = ezDownButton.isPressed(); // unused still
serviceLCD(doUser); // servide routine sees user input or timer expiry to <short circuit> transient messages
if (doSave) paintLCD(DUMBBITCH);
if (doPended) paintLCD(SAVED, 6);
}
*/
void beginLCD()
{
lcd.begin(16, 2); // Sorry, Mom!
}
unsigned long lcdDwellTime; // timer for transient screen messages
const unsigned long DWELL = 5000; // 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
}
// the real lcd routines
void dumbBitchLCD()
{
lcd.clear(); // displays user error before defaulting back to read mode
lcd.print("User Error:");
lcd.setCursor(0, 1);
lcd.print("Dumb Bitch!");
}
void readPresetLCD(byte theLED)
{
lcd.clear();
lcd.print(" B-");
lcd.print(currentBank);
lcd.setCursor(0, 1);
lcd.print("Preset ");
lcd.print(theLED + 1);
}
void runLCD(byte wtf)
{
Serial.print(wtf); Serial.println(" paint run mode LCD");
lcd.clear(); // resets the LCD to default if we exit program mode
lcd.print(" B-");
lcd.print(currentBank);
lcd.setCursor(0, 1);
lcd.print("Press Any Preset");
}
void writeOutLCD(byte theRelay)
{
lcd.clear();
lcd.print("Current Bank: ");
lcd.print(currentBank);
lcd.setCursor(0, 1);
lcd.print("Select Loops: ");
lcd.print(theRelay + 1);
}
//... 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.clear(); // sweep one day for needless clearing to solve flashing when the same message is repainted
lcd.print("Program saved to");
lcd.setCursor(0, 1);
lcd.print("Bank ");
lcd.print(currentBank);
lcd.print(" Preset ");
lcd.print(theLED + 1);
}
void programModeLCD()
{
lcd.clear();
lcd.print("Program Mode:");
lcd.setCursor(0, 1);
lcd.print("Select Loops");
}
void saveModeLCD()
{
lcd.clear(); // actual save mode
lcd.print("Save Mode:");
lcd.setCursor(0, 1);
lcd.print("Select Preset");
}
void splashLCD()
{
lcd.clear();
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);
}