Effect switcher and relays - EEPROM question

Working demo... I already know the breadboard looks insane, LOL...

I just did a pretty full-on demo. Filthy practice area aside, I now need to troubleshoot that high-pitch noise... pretty sure it's digital bleed into the audio signal - I thought I had them separated enough, but I also have no audio filtering whatsoever, so that's my next step.

It's coming together... soon!

Isolated it to grounding issues - still a very faint high pitch, but it's far more manageable... This thing is pretty cool. Getting excited! Thank you all so much for your help - I know I jump around a bit, but I gotta keep moving, so appreciate the patience and help as it comes!

"I'm a little too proud of those..."

LOL, there is no place for modesty.

It surprises me that your noise issues are so minimal. I wouldn't worry anymore until the ting is built good with proper lead dress and whatever final connection method(s) you employ.

It's your trip and the velocity is yours to control. I do see that it was almost four weeks ago when this was getting "close", I think it is closer now. :-|

Even #118 "Mark 7" just ten days ago was optimistic if you think of what just those days between have wrought.

a7

That’s about what I’m surmising… Your notion earlier about sprinkling caps around may be the ticket here - a couple filtering caps along the power rail to the relays might do some good. Will test further today at some point.

I also need to get the Arduino off USB power - my power bricks are all center negative, and I think the Arduino is center positive? I have just the end of a plug I can use that fits the arduino, but just never figured out which wire was center for it. (This could be a culprit for some remaining noise, actually). It would also allow me to power the LCD on its own finally.

This is true… I was willing to settle, and y’all just kept coming back with better fixes and this is now far beyond my initial expectations. If I can get the noise even lower, I’ll be able to focus on the PCBs.

I think for real this time… It’s pretty freakin’ close!

I added a 220u and .1u cap along the power rail for the relays, as well as hooked the Arduino into the same power as everything else.

I then just redid the demo video in its entirety. https://youtu.be/S_nsFbjlikU?si=gU5EaoL_g21BBZqA

The guitar was live the entire time, so - not too bad! I had to modify the muting a bit, and I may or may not be sold on keeping it. I think it's better with it, but I gotta tinker with the resistor value and timing maybe - but overall, this is fully functional.

I fully plan to keep this thread going with updates - and if you got mods, hit me with em!

Hi everyone! I've been using the breadboarded switch now for a minute and it's pretty solid. Added a mains circuit for the relays that has eliminated all the noise issues I was having, as well as adjusting the photoFET muting a bit further. I already laid out the PCB for the relays, now just have to figure out the Mega, Power, LCD and LEDs - but that's pretty simple.

Given that it's working and I'm at the PCB ordering phase officially, I'll mark this solved. I'm including the final code here, as well as the wokwi .json code. I also mocked up a very quick page with all the power diagrams and a single relay circuit.

I'll post more as I go along, and am always open to more advice, but for now - maybe this can help someone else. :slight_smile:

The Arduino code:

// This is a heavily modified version of this project: https://www.instructables.com/Arduino-based-8-loops-pedal-switcher/
// Information about photoFET muting can be found here: https://stompville.co.uk/?p=423
// You can follow the journey from the instructable to this final solution via the Arduino thread: https://forum.arduino.cc/t/effect-switcher-and-relays-eeprom-question/1209660/165
// There is a working simulation (wokwi) here: https://wokwi.com/projects/389032204915078145 - the red/blue LEDs represent optocouplers that power the relays
// Any diagrams or relevant coding are in the Arduino thread, including power management and .json file for creating your own Wokwi simulation
// Special thanks to Alto777 and "she who will not be named" on the Arduino forum for the teaching and coding and solving and all of the things


# 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

void okDelay(unsigned long theDelay) {    // use this instead of delay() - mostly for setup or short pulses.
  delay(theDelay);
}

enum escreen {  // list of different LCD display screens
  DUMBBITCH = 101, READPRESET, WRITEOUT, RUN, SAVED, PROGRAMMODE, SAVEMODE, SPLASH, NOSCREEN
};

const 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);

// setup of neopixels
# define ELEDS 16 // how many
# define EPIN  12 // which pin
# define PLEDS 8
# define PPIN  13
# define SLEDS 1
# define SPIN  A15

Adafruit_NeoPixel eLED(ELEDS, EPIN, NEO_RGB + NEO_KHZ800); // effect LEDs
Adafruit_NeoPixel pLED(PLEDS, PPIN, NEO_RGB + NEO_KHZ800); // preset LEDs
Adafruit_NeoPixel sLED(SLEDS, SPIN, NEO_RGB + NEO_KHZ800); // program/save LED

# define rHot HIGH
# define rGnd LOW

const byte rows = 1;
const byte cols = 8; // number of presets - up to 16

char keys[rows][cols] = {
  { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' },  // max 16 columns and max 10 rows. Ensure to match the numbers in const byte rows/cols above
};

const int numberOfPedals = 16;  // your total number of effects/pedals
const int bankSize = numberOfPedals * numberOfPedals;   // bank variable to skip ahead by a full bank
const int numberOfBanks = 9;  // up to 14 banks @ 16 pedals; 27 banks @ 12 pedals, 62 banks @ 8 pedals - be sure to adjust LCD displays to accommodate double digit bank readout
const int numberOfEfBks = 2;  // no more than 2 effect banks - for use ONLY when you have HALF the number of footswitches compared with the number of effects
const int numberOfPhotos = 2;  // photoFET variable to mute signal during switching
const int effectVariable = 8;  // effect variable for the effect banks to pull proper keys
const int numberOfProgNeos = 1;  // program/save LED variable

byte colPins[cols] = { 4, 5, 6, 7, 8, 9, 10, 11 };   // buttons or momentary switches
byte rowPins[rows] = { 2 };                          // just 1 row is needed

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 photoPin[numberOfPhotos] = { 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"};

const byte NOKEY = 99;

// These reset things. Named obvioulsy.
void progLampsOff() {
  for (int ii = 0; ii < numberOfPedals; ii++) {
    setPresetLED(ii, HIGH);
  }
}

void allLampsOff() {
  for (int ii = 0; ii < numberOfPedals; ii++) {
    setPresetLED(ii, HIGH);
    setEffectLED(ii, HIGH);
    sLED.clear();
  }
}

// save your effect/loop selections to a preset
void memory(int theAddr) {    
  for (int ii = 0; ii < numberOfPedals; ii++) {
    EEPROM.write((theAddr) + ii, getEffectLED(ii));
    setPresetLED(ii, HIGH);
  }
  for (int ii = 0; ii < numberOfPedals; ii++) {
    setEffectLED(ii, HIGH);
  }
}

// select your effects/loops for saving
void writeOut(int relay) {
  progLampsOff();
  setEffectLED(relay, getEffectLED(relay) ? LOW : HIGH);
}

// default mode - USE THE SWITCH!
void eepromToRelays(int address) {
  for (int ii = 0; ii < numberOfPhotos; ii++) {
    digitalWrite(photoPin[ii], rHot); //turn ON all photofets
  }
  okDelay(4); // FRONT mute to 
  for (int ii = 0; ii < numberOfPedals; ii++) {
    bool onOff = EEPROM.read((address) + ii) ? HIGH : LOW;
    if (onOff) {
      digitalWrite(OneRelayPin[ii], rGnd);
      digitalWrite(TenRelayPin[ii], rHot);
    } else {
      digitalWrite(TenRelayPin[ii], rGnd);
      digitalWrite(OneRelayPin[ii], rHot);
    }
    int kPreset = EEPROM.read((address) + ii);
    setEffectLED(ii, kPreset);
    setPresetLED(ii, HIGH);
  }
  okDelay(5);  // + BACK, if
  for (int ii = 0; ii < numberOfPedals; ii++) {
    digitalWrite(OneRelayPin[ii], rGnd);
    digitalWrite(TenRelayPin[ii], rGnd);
  }
  okDelay(1);
  for (int ii = 0; ii < numberOfPhotos; ii++) {
    digitalWrite(photoPin[ii], rGnd); //turn OFF all photofets last step
  }
}

void readPreset(int address) {
  eepromToRelays(address);
}

void loop() {
  static byte realMode = READ;
  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) {    // effectBank 1 is effects 1 through 8, effectBank 2 is 9 through 16
      key += effectVariable;
    }
  }

  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

  runAckBlinker();

  switch (realMode) {
    case WRITE:
      progPresetLED();  
      if (doBank) {
        effectBank = 2;
        Serial.print("effect bank became "); Serial.println(effectBank);
        paintLCD(WRITEOUT, 0, 103);
      }
      if (doDown) {
        effectBank = 1;
        Serial.print("effect bank became "); Serial.println(effectBank);
        paintLCD(WRITEOUT, 0, 103);
      }
      if (doProg) {
        realMode = READ;
        allLampsOff();
        paintLCD(RUN, 0, 103);
      }
      if (doSave) {
        realMode = SAVE;
        alreadyStored();
        paintLCD(SAVEMODE);
      }
      if (key != NOKEY) {
        writeOut(key); // relay
        paintLCD(WRITEOUT, key);
        alreadyStored();
      }
      break;

    case SAVE:
      savePresetLED();  
      effectBank = 1;
      if (doProg) {
        realMode = READ;
        alreadyStored();
        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);  // theAddr
        paintLCD(SAVED, key);
        blinkAnAck(key);
        realMode = READ;
        paintLCD(RUN, 0, 101);
      }
      break;

    case READ:
      clearPresetLED();  
      if (doBank) {
        currentBank++;
        if (currentBank > numberOfBanks) currentBank = 1;
        Serial.print("preset bank became "); Serial.println(currentBank);
        allLampsOff();
        paintLCD(RUN, 0, 102);
      }
      if (doDown) {
        currentBank--;
        if (currentBank < 1) currentBank = numberOfBanks;
        Serial.print("preset bank became "); Serial.println(currentBank);
        allLampsOff();
        paintLCD(RUN, 0, 102);
      }
      if (doProg) {
        realMode = WRITE;
        allLampsOff();
        alreadyStored();
        paintLCD(PROGRAMMODE);
      }
      if (doSave) {
        paintLCD(DUMBBITCH);
        paintLCD(RUN, 0, 104);
      }
      if (key != NOKEY) {
        readPreset(currentBank * bankSize + numberOfPedals * key);
        setPresetLED(key, LOW); // all done and only now do we say
        paintLCD(READPRESET, key);
      }
      break;

    default:
      Serial.print(realMode);
      Serial.println(" error");
  }
}

void spillMode() {
  Serial.println("spillMode broken just now, write it");
}

void setupRelays() {  // engages relays on and off at start-up - triggering pin 10 across the board puts them in default bypass mode. 
  for (int ii = 0; ii < numberOfPedals; ii++) {
    pinMode(OneRelayPin[ii], OUTPUT);
    pinMode(TenRelayPin[ii], OUTPUT);
    digitalWrite(TenRelayPin[ii], rHot); //rHot
    okDelay(50);
    digitalWrite(TenRelayPin[ii], rGnd); //rGnd
  }
// The below is needed only when you have single coil relays
/*  for (int ii = 0; ii < numberOfPedals; ii++) {
    digitalWrite(OneRelayPin[ii], rHot); //rHot
    okDelay(20);
    digitalWrite(TenRelayPin[ii], rHot); //rHot
    digitalWrite(OneRelayPin[ii], rGnd); //rGnd
    okDelay(20);
    digitalWrite(TenRelayPin[ii], rGnd); //rGnd
    okDelay(20);
  }*/
}

void alreadyStored() {   // this tells us which preset has something stored during program and save mode
  int theBank = currentBank;
  for (int thePreset = 0; thePreset < numberOfPedals; thePreset++) {
    bool anyEffect = false;
    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() {   // shows the rainbow and sets the neopixels on startup
  eLED.begin();
  pLED.begin();
  sLED.begin();
  eLED.show();
  pLED.show();
  sLED.show();
  eLED.setBrightness(40); // up to 255... if you want to go blind, that is. And use a LOT of power. 
  pLED.setBrightness(40); // IOW, put these below 50. You'll thank yourself. 
  sLED.setBrightness(40);
  rainbow(0); // start the rainbow - has to be zero, otherwise it will never end
  eLED.clear();
  pLED.clear();
  sLED.clear();
  eLED.show();
  pLED.show();
  sLED.show();
}

void rainbow(int wait) {    // the rainbow function. duh.
  for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; firstPixelHue += 256) {
    for (long ii = 0; ii < eLED.numPixels(); ii++) {
      long pixelHue = firstPixelHue + (ii * 65536L / eLED.numPixels());
      eLED.setPixelColor(ii, eLED.gamma32(eLED.ColorHSV(pixelHue)));
    }
    for (long ii = 0; ii < pLED.numPixels(); ii++) {
      long pixelHue = firstPixelHue + (ii * 65536L / pLED.numPixels());
      pLED.setPixelColor(ii, pLED.gamma32(pLED.ColorHSV(pixelHue)));
    }
    for (long ii = 0; ii < sLED.numPixels(); ii++) {
      long pixelHue = firstPixelHue + (ii * 65536L / sLED.numPixels());
      sLED.setPixelColor(ii, sLED.gamma32(sLED.ColorHSV(pixelHue)));
    }
    pLED.show();
    eLED.show();
    sLED.show();
    okDelay(wait);
  }
}

char kStoreEffectLED[numberOfPedals];

// set colors for neopixels here
const unsigned long neoClear = 0x000000;   // Clear
const unsigned long neoMagenta = 0x800080;   // Magenta
const unsigned long neoGreen = 0x00ff00;   // Green
const unsigned long neoYellow = 0xffff00;   // Yellow
const unsigned long neoOrange = 0xff9900;   // Orange Sherbert

void setEffectLED(int theLED, int theValue) {    // these are the effect LEDs in read mode
  eLED.setPixelColor(theLED, theValue ? 1 : neoYellow);
  eLED.show();
  kStoreEffectLED[theLED] = theValue;
}

int getEffectLED(int theLED) {   // this allows the neopixels to work in program and save modes
  return kStoreEffectLED[theLED] ? 1 : 0;
}

void setPresetLED(int theLED, int theValue) {    // these are the preset LEDs in read mode
  pLED.setPixelColor(theLED, theValue ? 1 : neoGreen);  
  pLED.show();
}

void progPresetLED() {    // these is the program/save LED color in program mode
  for (int ii = 0; ii < numberOfProgNeos; ii++) {
    sLED.setPixelColor(ii, neoMagenta);  
    sLED.show();
  }
}

void savePresetLED() {    // these is the program/save LED color in save mode
  for (int ii = 0; ii < numberOfProgNeos; ii++) {
    sLED.setPixelColor(ii, neoOrange);  
    sLED.show();
  }
}

void clearPresetLED() {    // these is the program/save LED color in read mode
  for (int ii = 0; ii < numberOfProgNeos; ii++) {
    sLED.setPixelColor(ii, HIGH);  
    sLED.show();
  }
}

// Blink and it's all over... 
static bool blinkingAnAck = false;
static int blinkCounter;
static int blinkingLED;

const byte blinkPeriod = 100;   // how long each flash stays on
const byte blinkNTimes = 19;   // how many times it blinks - has to be odd to leave the theLED off

void blinkAnAck(int theLED) {
  blinkingAnAck = true;
  blinkCounter = blinkNTimes;
  blinkingLED = theLED;
}

void runAckBlinker() {
  if (!blinkingAnAck) return;
  static unsigned long lastBlink;
  unsigned long now = millis();     
  if (now - lastBlink > blinkPeriod) {
    pLED.setPixelColor(blinkingLED, blinkCounter & 0x1 ? 1 : neoOrange);  
    pLED.show();
    blinkCounter--;
    lastBlink = now;
  }
  if (!blinkCounter) killAckBlinker();
}

void runAckBlinker(bool killMe) {
  if (killMe) killAckBlinker();
  else runAckBlinker();
}

void killAckBlinker() {
  if (blinkingAnAck) {
    eLED.clear();
    blinkingAnAck = false;
  }
}

void beginLCD() {
  lcd.begin(16, 2);

  // custom LCD characters - used only during write/program mode
  byte effChosenLCD[8] = { 0b11111, 0b10101, 0b11111, 0b10101, 0b11111, 0b11111, 0b11011, 0b11111 }; // pedal icon - also dominos
  byte effBankLCD[8] = { 0b00000, 0b00000, 0b11111, 0b01110, 0b00100, 0b00000, 0b00000, 0b00000 }; // down arrow
  lcd.createChar(0, effChosenLCD);
  lcd.createChar(1, effBankLCD);
}

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) {
    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
  }
}

void paintLCD(byte screenName) {   
  paintLCD(screenName, 97, 98);
}

void paintLCD(byte screenName, byte argument) {   
  paintLCD(screenName, argument, 99);
}

void paintLCD(byte screenName, byte argument, byte extra) {   
  if (lcdDwellTime) {
    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;
      savedLCD(argument);
      break;

    case PROGRAMMODE:
      programModeLCD();
      break;

    case SAVEMODE:
      saveModeLCD();
      break;

    case SPLASH:
      lcdDwellTime = now;
      splashLCD();
      break;

    default:
      Serial.print(screenName);
      Serial.println(" error unknown screen name/number");
  }
}

// LCD readouts for 16x2 LCD display - instead of using lcd.clear each time, just put in spaces until you get to 16 spaces per line.
void dumbBitchLCD() {
  lcd.setCursor(0, 0);
  lcd.print("Nope...         ");
  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");
}

// writeOutLCD displays the effects from right to left, which is how they would be chained together in the real world
void writeOutLCD(byte) {     
  lcd.setCursor(0, 0);
  if (effectBank == 1) {    // byte 1 is down arrows denoting active effectBank,
    lcd.print("  FX-2  "); 
    lcd.write((byte)1); 
    lcd.write((byte)1); 
    lcd.print("FX-1"); 
    lcd.write((byte)1); 
    lcd.write((byte)1);
  } else if (effectBank == 2) {
    lcd.write((byte)1); 
    lcd.write((byte)1); 
    lcd.print("FX-2"); 
    lcd.write((byte)1); 
    lcd.write((byte)1); 
    lcd.print("  FX-1  "); 
  }
  for (int ii = 0; ii < numberOfPedals; ii++) {    // byte 0 is the pedal/domino shape
    if (getEffectLED(ii) == LOW) {
      lcd.setCursor(((numberOfPedals - 1) - ii), 1); lcd.write((byte)0);
    } else {
      lcd.setCursor(((numberOfPedals - 1) - ii), 1); lcd.print(" ");
    }
  }
}

void savedLCD(byte theLED) {
  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 B-");
  lcd.print(currentBank);
  lcd.setCursor(0, 1);
  lcd.print("Select Pedals   ");
}

void saveModeLCD() {
  lcd.setCursor(0, 0);
  lcd.print("Save Mode       ");
  lcd.setCursor(0, 1);
  lcd.print("Select Preset   ");
}

void splashLCD() { // a lovely affirmation to set off each session right! 
  lcd.setCursor(0, 0);
  lcd.print("You Gorgeous Fat");
  lcd.setCursor(0, 1);
  lcd.print("Sausage Bitch!  ");
}

// Tryna' set me up? Better be ready to finish the job... 
void setup() {
  Serial.begin(1000000); /* not for midi communication - pin 1 TX */
  Serial.println("first things first.\n");

  beginLCD();
  paintLCD(SPLASH);

  for (int ii = 0; ii < numberOfPhotos; ii++) {
    pinMode(photoPin[ii], OUTPUT);
    digitalWrite(photoPin[ii], rHot);
  }

  setupVLEDs(); // see if they're working - wow, rainbow!
  setupRelays();  // relay pins and initial state

  ezBankButton.setDebounceTime(10);
  ezProgButton.setDebounceTime(10);
  ezSaveButton.setDebounceTime(10);
  ezDownButton.setDebounceTime(10);

  for (int ii = 0; ii < numberOfPhotos; ii++) {
    digitalWrite(photoPin[ii], rGnd);
  }

  paintLCD(RUN, 0, 111);
  allLampsOff();
}

The WOKWI .json code:

{
  "version": 1,
  "author": "Anonymous maker",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-arduino-mega", "id": "mega", "top": 0.6, "left": -13.2, "attrs": {} },
    { "type": "wokwi-led", "id": "led1", "top": 34.8, "left": 656.6, "attrs": { "color": "red" } },
    { "type": "wokwi-lcd1602", "id": "lcd1", "top": 233.83, "left": -60.8, "attrs": {} },
    {
      "type": "wokwi-resistor",
      "id": "r1",
      "top": 397.85,
      "left": -78.2,
      "rotate": 180,
      "attrs": { "value": "8000" }
    },
    { "type": "wokwi-led", "id": "led2", "top": 34.8, "left": 695, "attrs": { "color": "red" } },
    { "type": "wokwi-led", "id": "led3", "top": 34.8, "left": 733.4, "attrs": { "color": "red" } },
    { "type": "wokwi-led", "id": "led7", "top": 82.8, "left": 695, "attrs": { "color": "blue" } },
    {
      "type": "wokwi-led",
      "id": "led8",
      "top": 82.8,
      "left": 733.4,
      "attrs": { "color": "blue" }
    },
    {
      "type": "wokwi-led",
      "id": "led9",
      "top": 82.8,
      "left": 656.6,
      "attrs": { "color": "blue" }
    },
    {
      "type": "wokwi-resistor",
      "id": "r2",
      "top": 378.65,
      "left": -78.2,
      "rotate": 180,
      "attrs": { "value": "220" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn4",
      "top": 307.6,
      "left": 532,
      "rotate": 90,
      "attrs": { "color": "blue", "bounce": "0" }
    },
    {
      "type": "wokwi-resistor",
      "id": "r3",
      "top": 320.2,
      "left": 565.55,
      "rotate": 270,
      "attrs": { "value": "1000" }
    },
    {
      "type": "wokwi-resistor",
      "id": "r4",
      "top": 320.2,
      "left": 767.15,
      "rotate": 270,
      "attrs": { "value": "1000" }
    },
    {
      "type": "wokwi-resistor",
      "id": "r5",
      "top": 320.2,
      "left": 699.95,
      "rotate": 270,
      "attrs": { "value": "1000" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn5",
      "top": 307.6,
      "left": 666.4,
      "rotate": 90,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn6",
      "top": 307.6,
      "left": 733.6,
      "rotate": 90,
      "attrs": { "color": "yellow" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn7",
      "top": 307.6,
      "left": 599.2,
      "rotate": 90,
      "attrs": { "color": "red", "bounce": "0" }
    },
    {
      "type": "wokwi-resistor",
      "id": "r6",
      "top": 320.2,
      "left": 632.75,
      "rotate": 270,
      "attrs": { "value": "1000" }
    },
    {
      "type": "wokwi-rgb-led",
      "id": "rgb1",
      "top": -303.2,
      "left": 778.7,
      "attrs": { "common": "cathode" }
    },
    { "type": "wokwi-vcc", "id": "vcc1", "top": 327.16, "left": -96, "attrs": {} },
    { "type": "wokwi-gnd", "id": "gnd1", "top": 441.6, "left": -96.6, "attrs": {} },
    {
      "type": "wokwi-resistor",
      "id": "r7",
      "top": -236.05,
      "left": 710.4,
      "attrs": { "value": "5000" }
    },
    {
      "type": "wokwi-resistor",
      "id": "r8",
      "top": -216.85,
      "left": 710.4,
      "attrs": { "value": "1000" }
    },
    {
      "type": "wokwi-led",
      "id": "led13",
      "top": 34.8,
      "left": 426.2,
      "attrs": { "color": "red" }
    },
    {
      "type": "wokwi-led",
      "id": "led14",
      "top": 34.8,
      "left": 464.6,
      "attrs": { "color": "red" }
    },
    { "type": "wokwi-led", "id": "led15", "top": 34.8, "left": 503, "attrs": { "color": "red" } },
    {
      "type": "wokwi-led",
      "id": "led16",
      "top": 34.8,
      "left": 541.4,
      "attrs": { "color": "red" }
    },
    {
      "type": "wokwi-led",
      "id": "led17",
      "top": 34.8,
      "left": 579.8,
      "attrs": { "color": "red" }
    },
    {
      "type": "wokwi-led",
      "id": "led18",
      "top": 34.8,
      "left": 618.2,
      "attrs": { "color": "red" }
    },
    {
      "type": "wokwi-led",
      "id": "led19",
      "top": 82.8,
      "left": 426.2,
      "attrs": { "color": "blue" }
    },
    {
      "type": "wokwi-led",
      "id": "led20",
      "top": 82.8,
      "left": 464.6,
      "attrs": { "color": "blue" }
    },
    { "type": "wokwi-led", "id": "led21", "top": 82.8, "left": 503, "attrs": { "color": "blue" } },
    {
      "type": "wokwi-led",
      "id": "led22",
      "top": 82.8,
      "left": 541.4,
      "attrs": { "color": "blue" }
    },
    {
      "type": "wokwi-led",
      "id": "led23",
      "top": 82.8,
      "left": 579.8,
      "attrs": { "color": "blue" }
    },
    {
      "type": "wokwi-led",
      "id": "led24",
      "top": 82.8,
      "left": 618.2,
      "attrs": { "color": "blue" }
    },
    { "type": "wokwi-text", "id": "txt1", "top": 393.6, "left": 556.8, "attrs": { "text": "UP" } },
    {
      "type": "wokwi-text",
      "id": "txt7",
      "top": -220.8,
      "left": 249.6,
      "attrs": { "text": "<-----  PRESET 1" }
    },
    {
      "type": "wokwi-text",
      "id": "txt8",
      "top": -220.8,
      "left": 9.6,
      "attrs": { "text": "PRESET 8  <-----" }
    },
    {
      "type": "wokwi-text",
      "id": "txt9",
      "top": -76.8,
      "left": 912,
      "attrs": { "text": "<-----     EFFECT 1" }
    },
    {
      "type": "wokwi-text",
      "id": "txt10",
      "top": -76.8,
      "left": 432,
      "attrs": { "text": "EFFECT 16    <-----" }
    },
    {
      "type": "wokwi-text",
      "id": "txt11",
      "top": -211.2,
      "left": 1065.6,
      "attrs": { "text": "TUNER" }
    },
    {
      "type": "wokwi-text",
      "id": "txt12",
      "top": -249.6,
      "left": 912,
      "attrs": { "text": "TUNER RELAY" }
    },
    {
      "type": "wokwi-text",
      "id": "txt6",
      "top": 336,
      "left": 422.4,
      "attrs": { "text": "PROG/SAVE" }
    },
    {
      "type": "wokwi-text",
      "id": "txt5",
      "top": 163.2,
      "left": 432,
      "attrs": { "text": "PHOTO" }
    },
    {
      "type": "wokwi-text",
      "id": "txt4",
      "top": 393.6,
      "left": 604.8,
      "attrs": { "text": "DOWN" }
    },
    {
      "type": "wokwi-text",
      "id": "txt2",
      "top": 393.6,
      "left": 681.6,
      "attrs": { "text": "PROG" }
    },
    {
      "type": "wokwi-text",
      "id": "txt3",
      "top": 393.6,
      "left": 748.8,
      "attrs": { "text": "SAVE" }
    },
    { "type": "wokwi-neopixel", "id": "rgb2", "top": -176.3, "left": 8.6, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb3", "top": -176.3, "left": 56.6, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb4", "top": -176.3, "left": 104.6, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb5", "top": -176.3, "left": 152.6, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb6", "top": -176.3, "left": 200.6, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb7", "top": -176.3, "left": 248.6, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb8", "top": -176.3, "left": 296.6, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb9", "top": -176.3, "left": 344.6, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb10", "top": -22.7, "left": 891.8, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb11", "top": -22.7, "left": 431, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb12", "top": -22.7, "left": 469.4, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb13", "top": -22.7, "left": 507.8, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb14", "top": -22.7, "left": 546.2, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb15", "top": -22.7, "left": 584.6, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb16", "top": -22.7, "left": 623, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb17", "top": -22.7, "left": 661.4, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb18", "top": -22.7, "left": 699.8, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb19", "top": -22.7, "left": 738.2, "attrs": {} },
    { "type": "wokwi-vcc", "id": "vcc2", "top": -66.44, "left": 1046.4, "attrs": {} },
    { "type": "wokwi-gnd", "id": "gnd3", "top": -153.6, "left": 412.2, "attrs": {} },
    { "type": "wokwi-led", "id": "led4", "top": 34.8, "left": 810.2, "attrs": { "color": "red" } },
    {
      "type": "wokwi-led",
      "id": "led5",
      "top": 82.8,
      "left": 810.2,
      "attrs": { "color": "blue" }
    },
    { "type": "wokwi-led", "id": "led6", "top": 34.8, "left": 771.8, "attrs": { "color": "red" } },
    {
      "type": "wokwi-led",
      "id": "led10",
      "top": 82.8,
      "left": 771.8,
      "attrs": { "color": "blue" }
    },
    { "type": "wokwi-vcc", "id": "vcc3", "top": -210.44, "left": -38.4, "attrs": {} },
    { "type": "wokwi-gnd", "id": "gnd2", "top": 0, "left": 1045.8, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb20", "top": -22.7, "left": 776.6, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb21", "top": -22.7, "left": 815, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb22", "top": -22.7, "left": 930.2, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb23", "top": -22.7, "left": 968.6, "attrs": {} },
    {
      "type": "wokwi-led",
      "id": "led11",
      "top": 34.8,
      "left": 848.6,
      "attrs": { "color": "red" }
    },
    {
      "type": "wokwi-led",
      "id": "led12",
      "top": 82.8,
      "left": 848.6,
      "attrs": { "color": "blue" }
    },
    { "type": "wokwi-neopixel", "id": "rgb24", "top": -22.7, "left": 853.4, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "rgb25", "top": -22.7, "left": 1007, "attrs": {} },
    {
      "type": "wokwi-led",
      "id": "led25",
      "top": 178.8,
      "left": 426.2,
      "attrs": { "color": "cyan" }
    },
    {
      "type": "wokwi-led",
      "id": "led26",
      "top": 178.8,
      "left": 455,
      "attrs": { "color": "magenta" }
    },
    { "type": "wokwi-led", "id": "led27", "top": 34.8, "left": 887, "attrs": { "color": "red" } },
    { "type": "wokwi-led", "id": "led28", "top": 82.8, "left": 887, "attrs": { "color": "blue" } },
    {
      "type": "wokwi-led",
      "id": "led29",
      "top": 34.8,
      "left": 963.8,
      "attrs": { "color": "red" }
    },
    {
      "type": "wokwi-led",
      "id": "led30",
      "top": 82.8,
      "left": 963.8,
      "attrs": { "color": "blue" }
    },
    {
      "type": "wokwi-led",
      "id": "led31",
      "top": 34.8,
      "left": 925.4,
      "attrs": { "color": "red" }
    },
    {
      "type": "wokwi-led",
      "id": "led32",
      "top": 82.8,
      "left": 925.4,
      "attrs": { "color": "blue" }
    },
    {
      "type": "wokwi-led",
      "id": "led33",
      "top": 34.8,
      "left": 1002.2,
      "attrs": { "color": "red" }
    },
    {
      "type": "wokwi-led",
      "id": "led34",
      "top": 82.8,
      "left": 1002.2,
      "attrs": { "color": "blue" }
    },
    { "type": "wokwi-vcc", "id": "vcc4", "top": 336.76, "left": 844.8, "attrs": {} },
    { "type": "wokwi-gnd", "id": "gnd4", "top": 384, "left": 863.4, "attrs": {} },
    {
      "type": "wokwi-pushbutton",
      "id": "btn1",
      "top": -114.8,
      "left": -15.2,
      "rotate": 90,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn2",
      "top": -114.8,
      "left": 32.8,
      "rotate": 90,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn3",
      "top": -114.8,
      "left": 80.8,
      "rotate": 90,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn8",
      "top": -114.8,
      "left": 128.8,
      "rotate": 90,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn9",
      "top": -114.8,
      "left": 176.8,
      "rotate": 90,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn10",
      "top": -114.8,
      "left": 224.8,
      "rotate": 90,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn11",
      "top": -114.8,
      "left": 272.8,
      "rotate": 90,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn12",
      "top": -114.8,
      "left": 320.8,
      "rotate": 90,
      "attrs": { "color": "green" }
    },
    {
      "type": "wokwi-led",
      "id": "led35",
      "top": -291.6,
      "left": 867.8,
      "attrs": { "color": "purple" }
    },
    {
      "type": "wokwi-led",
      "id": "led36",
      "top": -243.6,
      "left": 867.8,
      "attrs": { "color": "blue" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn13",
      "top": -297.2,
      "left": 1060,
      "rotate": 90,
      "attrs": { "color": "black" }
    },
    {
      "type": "wokwi-resistor",
      "id": "r9",
      "top": -284.6,
      "left": 1093.55,
      "rotate": 270,
      "attrs": { "value": "1000" }
    },
    { "type": "wokwi-neopixel", "id": "rgb26", "top": 342.1, "left": 392.6, "attrs": {} }
  ],
  "connections": [
    [ "lcd1:V0", "r1:1", "red", [ "v0" ] ],
    [ "lcd1:VSS", "lcd1:RW", "black", [ "v9.6", "h38.5" ] ],
    [ "r2:1", "lcd1:A", "red", [ "h124.8", "v-19.2" ] ],
    [ "btn5:1.l", "r5:2", "#8f4814", [ "v0" ] ],
    [ "btn6:1.l", "r4:2", "#8f4814", [ "v0" ] ],
    [ "btn7:1.l", "r6:2", "#8f4814", [ "v0" ] ],
    [ "lcd1:K", "gnd1:GND", "black", [ "v57.6", "h422.4" ] ],
    [ "lcd1:VSS", "gnd1:GND", "black", [ "v67.2", "h566.4" ] ],
    [ "r1:2", "vcc1:VCC", "red", [ "h-8.4", "v-48" ] ],
    [ "lcd1:VDD", "vcc1:VCC", "red", [ "v48", "h-67.1", "v-57.6" ] ],
    [ "r7:2", "rgb1:R", "red", [ "v0", "h46.8" ] ],
    [ "r8:2", "rgb1:G", "green", [ "h37.2", "v-48" ] ],
    [ "btn4:1.l", "r3:2", "#8f4814", [ "v0" ] ],
    [ "r2:2", "vcc1:VCC", "red", [ "h0" ] ],
    [ "lcd1:D7", "mega:A0", "green", [ "v28.8", "h153.9", "v-163.2", "h-57.6" ] ],
    [ "lcd1:D5", "mega:A2", "green", [ "v38.4", "h0.1", "v9.6", "h172.8", "v-182.4", "h-35.3" ] ],
    [ "mega:A3", "lcd1:D4", "green", [ "v45.3", "h25.55", "v201.6", "h-182.4" ] ],
    [ "mega:A4", "lcd1:E", "green", [ "v45.3", "h16.05", "v211.2", "h-230.4" ] ],
    [ "mega:A5", "lcd1:RS", "green", [ "v45.3", "h6.55", "v220.8", "h-249.9" ] ],
    [ "mega:A1", "lcd1:D6", "green", [ "v45.3", "h44.8", "v172.8", "h-163.4" ] ],
    [ "mega:A7", "btn5:1.l", "green", [ "v83.7", "h304.1" ] ],
    [ "mega:A6", "btn4:1.l", "blue", [ "v93.3", "h246.4" ] ],
    [ "mega:A9", "btn6:1.l", "yellow", [ "v74.1", "h1.2" ] ],
    [ "rgb9:VDD", "rgb8:VDD", "#990000", [ "h0", "v-9.6", "h-48" ] ],
    [ "rgb8:VDD", "rgb7:VDD", "#990000", [ "h0", "v-9.6", "h-48" ] ],
    [ "rgb7:VDD", "rgb6:VDD", "#990000", [ "h0", "v-9.6", "h-48" ] ],
    [ "rgb6:VDD", "rgb5:VDD", "#990000", [ "h0", "v-9.6", "h-48" ] ],
    [ "rgb5:VDD", "rgb4:VDD", "#990000", [ "h0", "v-9.6", "h-48" ] ],
    [ "rgb4:VDD", "rgb3:VDD", "#990000", [ "h0", "v-9.6", "h-48" ] ],
    [ "rgb3:VDD", "rgb2:VDD", "#990000", [ "h0", "v-9.6", "h-48" ] ],
    [ "rgb2:VSS", "rgb3:VSS", "black", [ "v8.7", "h48" ] ],
    [ "rgb3:VSS", "rgb4:VSS", "black", [ "v8.7", "h48" ] ],
    [ "rgb4:VSS", "rgb5:VSS", "black", [ "v8.7", "h48" ] ],
    [ "rgb5:VSS", "rgb6:VSS", "black", [ "v8.7", "h48" ] ],
    [ "rgb6:VSS", "rgb7:VSS", "black", [ "v8.7", "h48" ] ],
    [ "rgb7:VSS", "rgb8:VSS", "black", [ "v8.7", "h48" ] ],
    [ "rgb8:VSS", "rgb9:VSS", "black", [ "v8.7", "h48" ] ],
    [ "rgb11:VDD", "rgb12:VDD", "#990000", [ "h0", "v-9.6", "h48" ] ],
    [ "rgb12:VDD", "rgb13:VDD", "#990000", [ "h0", "v-9.6", "h48" ] ],
    [ "rgb13:VDD", "rgb14:VDD", "#990000", [ "h0", "v-9.6", "h48" ] ],
    [ "rgb14:VDD", "rgb15:VDD", "#990000", [ "h0", "v-9.6", "h48" ] ],
    [ "rgb15:VDD", "rgb16:VDD", "#990000", [ "h0", "v-9.6", "h48" ] ],
    [ "rgb16:VDD", "rgb17:VDD", "#990000", [ "h0", "v-9.6", "h48" ] ],
    [ "rgb17:VDD", "rgb18:VDD", "#990000", [ "h0", "v-9.6", "h48" ] ],
    [ "rgb18:VDD", "rgb19:VDD", "#990000", [ "h0", "v-9.6", "h48" ] ],
    [ "rgb11:VSS", "rgb12:VSS", "black", [ "v8.7", "h38.4" ] ],
    [ "rgb12:VSS", "rgb13:VSS", "black", [ "v8.7", "h38.4" ] ],
    [ "rgb13:VSS", "rgb14:VSS", "black", [ "v8.7", "h37.6", "v-8.7" ] ],
    [ "rgb14:VSS", "rgb15:VSS", "black", [ "v8.7", "h38.4" ] ],
    [ "rgb15:VSS", "rgb16:VSS", "black", [ "v8.7", "h38.4" ] ],
    [ "rgb16:VSS", "rgb17:VSS", "black", [ "v8.7", "h38.4" ] ],
    [ "rgb17:VSS", "rgb18:VSS", "black", [ "v8.7", "h38.4" ] ],
    [ "rgb18:VSS", "rgb19:VSS", "black", [ "v8.7", "h38.4" ] ],
    [ "rgb2:VDD", "vcc3:VCC", "#990000", [ "h0" ] ],
    [ "rgb20:VDD", "rgb19:VDD", "#990000", [ "h0", "v-9.6", "h-48" ] ],
    [ "rgb21:VDD", "rgb20:VDD", "#990000", [ "h0", "v-9.6", "h-48" ] ],
    [ "rgb19:VSS", "rgb20:VSS", "black", [ "v8.7", "h38.4" ] ],
    [ "rgb20:VSS", "rgb21:VSS", "black", [ "v8.7", "h38.4" ] ],
    [ "rgb10:VSS", "rgb22:VSS", "black", [ "v8.7", "h38.4" ] ],
    [ "rgb22:VSS", "rgb23:VSS", "black", [ "v8.7", "h38.4" ] ],
    [ "rgb21:VDD", "rgb24:VDD", "#990000", [ "h0", "v-9.6", "h48" ] ],
    [ "rgb21:VSS", "rgb24:VSS", "black", [ "v8.7", "h38.4" ] ],
    [ "rgb23:VSS", "rgb25:VSS", "black", [ "v8.7", "h38.4" ] ],
    [ "btn7:1.l", "mega:A11", "#FF3333", [ "v-9.6", "h-201.6" ] ],
    [ "led25:A", "mega:A12", "cyan", [ "v28.8", "h-125.65" ] ],
    [ "led26:A", "mega:A13", "magenta", [ "v19.2", "h-144.7" ] ],
    [ "rgb25:VSS", "gnd2:GND", "black", [ "v-0.9", "h28", "v57.6" ] ],
    [ "rgb24:VDD", "rgb10:VDD", "#990000", [ "h0", "v-9.6", "h57.6" ] ],
    [ "rgb10:VDD", "rgb22:VDD", "#990000", [ "h0", "v-9.6", "h48" ] ],
    [ "rgb22:VDD", "rgb23:VDD", "#990000", [ "h0", "v-9.6", "h48" ] ],
    [ "rgb23:VDD", "rgb25:VDD", "#990000", [ "h0", "v-9.6", "h48" ] ],
    [ "rgb9:VSS", "gnd3:GND", "black", [ "v-0.9", "h229.6" ] ],
    [ "rgb24:VSS", "rgb10:VSS", "", [ "v8.7", "h38.4" ] ],
    [ "rgb25:VDD", "vcc2:VCC", "green", [ "h0", "v-9.6", "h38.4" ] ],
    [ "led27:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-518.4", "v-86.4", "h-45.8" ] ],
    [ "led31:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-556.8", "v-86.4", "h-45.8" ] ],
    [ "led29:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-595.2", "v-86.4", "h-45.8" ] ],
    [ "led33:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-633.6", "v-86.4", "h-45.8" ] ],
    [ "led11:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-480", "v-86.4", "h-45.8" ] ],
    [ "led4:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-441.6", "v-86.4", "h-45.8" ] ],
    [ "led6:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-403.2", "v-86.4", "h-45.8" ] ],
    [ "led3:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-364.8", "v-86.4", "h-45.8" ] ],
    [ "led2:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-326.4", "v-86.4", "h-45.8" ] ],
    [ "led1:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-288", "v-86.4", "h-45.8" ] ],
    [ "led18:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-249.6", "v-86.4", "h-45.8" ] ],
    [ "led17:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-211.2", "v-86.4", "h-45.8" ] ],
    [ "led16:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-172.8", "v-86.4", "h-45.8" ] ],
    [ "led15:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-134.4", "v-86.4", "h-45.8" ] ],
    [ "led14:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-96", "v-86.4", "h-45.8" ] ],
    [ "led13:C", "mega:GND.5", "#8f4814", [ "v9.6", "h-76.4", "v86.4", "h-7" ] ],
    [ "led19:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-67.2", "v-125.8" ] ],
    [ "led20:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-105.6", "v-125.8" ] ],
    [ "led21:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-144", "v-125.8" ] ],
    [ "led22:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-182.4", "v-125.8" ] ],
    [ "led23:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-220.8", "v-125.8" ] ],
    [ "led24:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-259.2", "v-125.8" ] ],
    [ "led9:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-297.6", "v-125.8" ] ],
    [ "led7:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-336", "v-124.8", "h-26.2" ] ],
    [ "led8:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-374.4", "v-125.8" ] ],
    [ "led10:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-412.8", "v-124.8", "h-26.2" ] ],
    [ "led5:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-451.2", "v-125.8" ] ],
    [ "led12:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-489.6", "v-125.8" ] ],
    [ "led28:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-528", "v-125.8" ] ],
    [ "led32:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-566.4", "v-125.8" ] ],
    [ "led30:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-604.8", "v-125.8" ] ],
    [ "led34:C", "mega:GND.4", "#8f4814", [ "v9.6", "h-643.2", "v-125.8" ] ],
    [ "btn4:2.r", "gnd4:GND", "BLACK", [ "v19.4", "h307", "v-28.8" ] ],
    [ "btn7:2.r", "gnd4:GND", "BLACK", [ "v19.4", "h239.8", "v-28.8" ] ],
    [ "btn5:2.r", "gnd4:GND", "BLACK", [ "v19.4", "h172.6", "v-28.8" ] ],
    [ "btn6:2.r", "gnd4:GND", "BLACK", [ "v19.4", "h105.4", "v-28.8" ] ],
    [ "r3:1", "vcc4:VCC", "#990000", [ "h0", "v19.2", "h259.2", "v-48" ] ],
    [ "r6:1", "vcc4:VCC", "#990000", [ "h0", "v19.2", "h192", "v-57.6" ] ],
    [ "r5:1", "vcc4:VCC", "#990000", [ "h0", "v19.2", "h124.8", "v-57.6" ] ],
    [ "r4:1", "vcc4:VCC", "#990000", [ "h0", "v19.2", "h57.6", "v-57.6" ] ],
    [ "led25:C", "mega:GND.5", "#990000", [ "v9.6", "h-67.2", "v-220.8", "h-26.2" ] ],
    [ "led26:C", "mega:GND.5", "#990000", [ "v9.6", "h-96", "v-221.8" ] ],
    [ "btn1:1.l", "btn2:1.l", "green", [ "v-9.6", "h48" ] ],
    [ "btn3:1.l", "btn2:1.l", "green", [ "v-9.6", "h-38.4" ] ],
    [ "btn3:1.l", "btn8:1.l", "green", [ "v-9.6", "h9.6" ] ],
    [ "btn8:1.l", "btn9:1.l", "green", [ "v-9.6", "h48" ] ],
    [ "btn9:1.l", "btn10:1.l", "green", [ "v-9.6", "h48" ] ],
    [ "btn10:1.l", "btn11:1.l", "green", [ "v-9.6", "h48" ] ],
    [ "btn11:1.l", "btn12:1.l", "green", [ "v-9.6", "h48" ] ],
    [ "btn12:1.l", "mega:2", "green", [ "v-9.6", "h28.8", "v105.6", "h-168.8" ] ],
    [ "mega:13", "rgb9:DIN", "limegreen", [ "v-28.8", "h287.4", "v-172.8" ] ],
    [ "rgb9:DOUT", "rgb8:DIN", "limegreen", [ "h-9.6", "v-10.5" ] ],
    [ "rgb8:DOUT", "rgb7:DIN", "limegreen", [ "h-9.6", "v-10.5" ] ],
    [ "rgb7:DOUT", "rgb6:DIN", "limegreen", [ "h-9.6", "v-10.5" ] ],
    [ "rgb6:DOUT", "rgb5:DIN", "limegreen", [ "h-9.6", "v-10.5" ] ],
    [ "rgb5:DOUT", "rgb4:DIN", "limegreen", [ "h-9.6", "v-10.5" ] ],
    [ "rgb4:DOUT", "rgb3:DIN", "limegreen", [ "h-9.6", "v-10.5" ] ],
    [ "rgb3:DOUT", "rgb2:DIN", "limegreen", [ "h-9.6", "v-10.5" ] ],
    [ "btn12:2.r", "mega:4", "green", [ "v9.8", "h-0.2" ] ],
    [ "btn11:2.r", "mega:5", "green", [ "v9.8", "h-105.8" ] ],
    [ "btn10:2.r", "mega:6", "green", [ "v9.8", "h-57.8" ] ],
    [ "btn9:2.r", "mega:7", "green", [ "v9.8", "h-29" ] ],
    [ "btn8:2.r", "mega:8", "green", [ "v9.8", "h9.4" ] ],
    [ "btn3:2.r", "mega:9", "green", [ "v9.8", "h47.8" ] ],
    [ "btn2:2.r", "mega:10", "green", [ "v9.8", "h86.2" ] ],
    [ "btn1:2.r", "mega:11", "green", [ "v9.8", "h115" ] ],
    [ "mega:12", "rgb25:DIN", "yellow", [ "v-48", "h768", "v0", "h144", "v19.2" ] ],
    [ "rgb25:DOUT", "rgb23:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb23:DOUT", "rgb22:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb22:DOUT", "rgb10:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb10:DOUT", "rgb24:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb24:DOUT", "rgb21:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb21:DOUT", "rgb20:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb20:DOUT", "rgb19:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb19:DOUT", "rgb18:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb18:DOUT", "rgb17:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb17:DOUT", "rgb16:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb16:DOUT", "rgb15:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb15:DOUT", "rgb14:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb14:DOUT", "rgb13:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb13:DOUT", "rgb12:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "rgb12:DOUT", "rgb11:DIN", "yellow", [ "h-9.6", "v-10.5" ] ],
    [ "led33:A", "mega:22", "red", [ "h9.6", "v-48", "h-633.6", "v9.6" ] ],
    [ "led29:A", "mega:23", "red", [ "h9.6", "v-48", "h-595.2", "v-9.6" ] ],
    [ "led31:A", "mega:24", "red", [ "h9.6", "v-48" ] ],
    [ "led27:A", "mega:25", "red", [ "h9.6", "v-48" ] ],
    [ "led11:A", "mega:26", "red", [ "h9.6", "v-48", "h-480", "v9.6" ] ],
    [ "led4:A", "mega:27", "red", [ "h9.6",  "v-48", "h-441.6", "v9.6" ] ],
    [ "led6:A", "mega:28", "red", ["h9.6",  "v-48", "h-403.2", "v19.2" ] ],
    [ "led3:A", "mega:29", "red", [ "h9.6", "v-48", "h-364.8", "v19.2" ] ],
    [ "led2:A", "mega:30", "red", [ "h9.6", "v-48", "h-326.4", "v28.8" ] ],
    [ "led1:A", "mega:31", "red", [ "h9.6", "v-48", "h-288", "v28.8" ] ],
    [ "led18:A", "mega:32", "red", [ "h9.6", "v-48", "h-249.6", "v38.4" ] ],
    [ "led17:A", "mega:33", "red", [ "h9.6", "v-48", "h-211.2", "v38.4" ] ],
    [ "led16:A", "mega:34", "red", [ "h9.6", "v-48", "h-172.8", "v48" ] ],
    [ "led15:A", "mega:35", "red", [ "h9.6", "v-48", "h-179.8" ] ],
    [ "led14:A", "mega:36", "red", [ "h9.6", "v-48", "h-105.2", "v56.3" ] ],
    [ "led13:A", "mega:37", "red", [ "h9.6", "v-48", "h-103" ] ],
    [ "led34:A", "mega:38", "blue", [ "v19.2", "h-642.8", "v-48" ] ],
    [ "led30:A", "mega:39", "blue", [ "v19.2", "h-604.4", "v-48" ] ],
    [ "led32:A", "mega:40", "blue", [ "v19.2", "h-566", "v-38.4" ] ],
    [ "led28:A", "mega:41", "blue", [ "v19.2", "h-527.6", "v-38.4" ] ],
    [ "led12:A", "mega:42", "blue", [ "v19.2", "h-489.2", "v-28.8" ] ],
    [ "led5:A", "mega:43", "blue", [ "v19.2", "h-450.8", "v-28.8" ] ],
    [ "led10:A", "mega:44", "blue", [ "v19.2", "h-412.4", "v-19.2" ] ],
    [ "led8:A", "mega:45", "blue", [ "v19.2", "h-374", "v-19.2" ] ],
    [ "led7:A", "mega:46", "blue", [ "v19.2", "h-335.6", "v-9.6" ] ],
    [ "led9:A", "mega:47", "blue", [ "v19.2", "h-297.2", "v-9.6" ] ],
    [ "led24:A", "mega:48", "blue", [ "v19.2", "h0.4" ] ],
    [ "led23:A", "mega:49", "blue", [ "v19.2", "h-9.2" ] ],
    [ "led22:A", "mega:50", "blue", [ "v19.2", "h-182", "v9.6" ] ],
    [ "led21:A", "mega:51", "blue", [ "v19.2", "h-143.6", "v9.6" ] ],
    [ "led20:A", "mega:52", "blue", [ "v19.2", "h-105.2", "v19.2" ] ],
    [ "led19:A", "mega:53", "blue", [ "v19.2", "h-66.8", "v19.2" ] ],
    [ "btn13:1.l", "r9:2", "green", [ "v0" ] ],
    [ "rgb26:DIN", "mega:A15", "orange", [ "h-0.8", "v-160.5" ] ],
    [ "rgb26:VDD", "vcc1:VCC", "#990000", [ "h-19.2", "v144", "h-489.6", "v-134.4" ] ],
    [ "rgb26:VSS", "gnd1:GND", "black", [ "v104.7", "h-442.4", "v-19.2" ] ]
  ],
  "dependencies": {}
}

My diagrams for relay wiring and power management:

Looks good.

The only thing that jumped was the 100 ohm resistor inline to the 7805 regulator.

Above my pay grade, but something you could check…

If that 5 volts only for the relay switching power, the large caps may be masking or enabling a marginal supply. By my crude calculations, it would not be able to deliver 40 mA at 5 volts on a continuous basis.

So you are getting away with it due to the transient and low current nature of the demand you place on it.

Measure the 5 volts where it is supplied to a relay, and switch on one of its coils by code or more easily by jumper wire 5 volts like it was coming from an output and see if the voltage stays good.

Maybe do that for two or three relays one coil each.

Using the device as designed, make a preset that turns all the loops off, and one that turns them all on. Switching between them rapidly woukd be the torture test you want it to very much pass.

I'm mostly curious and like I always say I do not argue with success. But one day if you do draw more current, just revisit that circuit if there is a problem

Thanks for the shout out in the code. She who will not be named is modest, kinda, but is OK with being mentioned… I'm not known to be, so it is nice to see my name there.

We never did do much pounding, I think any serious defects woukd have surfaced by now. As for the muting, I haven't looked and can't remember mentioning or commenting in the code that the short okDelay() calls can be tuned and sprinkled all over in there.

Please when it is about to get a road test take a picture of it as a device and mebbe some of how the insides ended up looking. No doubt a giant leap from the wash bucket bread board proto. :expressionless:

a7

makes sense, and I'll test, but after breadboarding this up and checking the voltages, I'm not getting any drop from the 9v, and after the 5v regulator I'm getting between 4.9 and 5v to each relay pin. When triggered, I get 4.25v at one side of the coil and 0v at the other. You've got me intrigued to do another, though, so will give it a go.

This mains filtering was based on this video (I trust these guys - have even made one of their EQ pedals): https://www.youtube.com/watch?v=julbbj4R3Ko

At about 3:30 he explains that he used an inductor but it didn't do much, so he recommended a 100r resistor. Any noise is almost completely eliminated using this method, for what it's worth, so I don't know...

All is to say: Noted, will test more, but so far am finding it working out okay.

I'm still not convinced about the muting, honestly - it's kind of like a "which is worse?" question - the sort of faint audible switch noise or a nearly inaudible but still kinda noticeable quick mute blip? I don't know - but I need to experiment a little more on that front.

hehe, I have never had to breadboard something so complex - I feel like I should somehow enshrine that jumble into its own artwork.

I'm working through how to get the final boxed version looking clean, while keeping audio wires as short as possible and having any boards bottom mounted (e.g. away from where I'm stomping on the enclosure). It's tricky, but doable. The relay boards are designed, and I'm working on the NeoPixel and LCD ones now before ordering from JLC. Will certainly keep the photos flowing... I may have marked this solved, but as always: more to follow...

Also, I always try and ensure credit is given where it's due - your help put this way beyond my expectations of it, so I'm forever grateful for that... kinda makes me glad I waited so long to finish it! :wink: (it doesn't actually make me glad, but it's a silver lining, hehe).

There's a bunch of folks up top, as well, that threw their input into the ring, as well - it's a great thing y'all do here, so thank you! :slight_smile:

@greysun dunno if I mentioned these cute inexpensive small Arduino Megas:


What he is not doing is trying to use that filtered 9 volts to run a regulator to get solid 5 volts.

In the comments you can see ppl pushing back variously on the 100 ohm resistor, "do not use this circuit for more than one or two pedals" kinda thing.

Analog effects can probably run on voltage that isn't 9 volts.

The fact that what should be 5 but goes down to 4.25 shows your 7805 is not regulating, it is doing the best that it can given that its drop out voltage (2 volts above the output) is not being presented at the input - the 9 volts is dropping over the 100 ohm resistor as soon as you draw any significant current, as energizing a coil will do here.

For fun, measure the input at the regulator when a few coils are energized. Measure the voltage across the 100 ohm resistor.

I have said above my pay grade, and that I do not argue with success, so. But this is just Ohm's law.

I'd be curious what the guy in the video has for "9 volts" and what he would measure at the point in any pedal that would have, in the old days, been connected to the battery. And what current draw his how-many pedals are creating in total.

a7

This was happening before I used the mains filter. It happened on both the dual and single coil relays. I don't question results - it only needs 3.75v to flip. My best guess is that there's a voltage drop across the coil due to its resistance (I want to say it's 255 ohm?).

In doing a quick test just now, it worked okay - voltage in front of 100r was 9.1, voltage after was 8.9 - 5v was hitting every relay after the regulator, but I hadn't tested with all of them firing at once. Will need to increase the delay in the code and see what happens. I only have 8 relays to test, so no clue about 16, but will figure out my math...

Looking through the comments, it seems like using an inductor would work better for my needs, and replacing the second 470uf electro with a .01u box/film/ceramic would also work well. It's a night and day difference with noise, so I should figure this out for sure. More to follow.

Did some further testing in many scenarios. Your suspicion (as always) was correct - with 8 relays in play, the voltage on all 4 coil pins was at 5v when resting - dropped to 1v on 3 pins and .01v on the trigger pin when active. When I took out the mains filter, it was 5v all pins resting, then 4.5v on 3 pins and .3v on the trigger pin.

Oddly, the noise didn't change - very quiet and nothing odd. The only thing I changed was the power supply (my RadioShack 9v supply that I've always used for testing was throwing 11.5v, so I swapped to an unused onespot, which I typically avoided because it's cheap and made all my guitar pedals noisy - oh the irony!). So... I retraced everything to ensure there wasn't some ground or something that I wasn't accounting for, but everything checks out. Used it for about an hour of practice tonight with not a single issue.

I'll redo the graphic shortly, but yea... mains is out. direct is in. the solution code still stands unchanged, but I'll update the graphic.

As always - THANK YOU for the red flag - would've been pretty pissed if it didn't work after getting it all boxed up.

Problem is that I have 2 Arduino mega boards now! The issue there is that both have female header pins - but also, should something happen, I kind of like being able to just swap it easily - but that leaves just mechanical connections while using a shield to connect actual wires to... I dunno, there's plenty of space for the boards on the bottom of the enclosure, it's just thinking through the LEDs and LCD where it gets tight.

that being said, I could be swayed to a mini board like this if it had USB-C... I'll do some sleuthing...

Also - these 5mm neopixels have an added complication: how to mount and wire them into standard 5mm bezels - might need to roll my own gaskets for those.

Plugging away over here - designed PCBs and had them delivered, so I finally got to soldering them up.

Long one is a power and LED bus so I don’t have to mess with wires around those. I also put some mini-boards for the switches that I cut out and “creatively” attached to them. This way I can just point to point wire them and keep things tidy. The LEDs mount to the boards and fit snug right into their bezels. I’m not convinced the LEDs footprint was my best move - those pads are REALLY close to each other, but the PCB folks assured me they’d be fine. We’ll see. I keep taking macro photos and using magnifying glass to make sure they don’t touch - we’ll see if I can solder em or not. (Might be time for a new tip before I hit those, hehe). They’ll be the last things I solder once everything is mounted in the enclosure.

I also made a screen for the Arduinos that I have. The little mini board was tempting; but I have 2 mega boards already and soldering pins is nothing. I’ll be getting the little minis for future projects for sure (need some with usb-c!)

There are probably dozens of ways this could have been done better or more effectively, but also dozens worse. I’m at least having fun with it and think it’ll work.

I used Eagle to lay out the relay boards, but moved to Kicad for the LED/switch bus and mega screen. There aren’t size limits with kicad and I was able to make custom footprints much more easily. Not the most stable program, but it got the job done. Eagle will go away soon, so there’s some future thinking as well.

As always, more to follow…


Hi everyone - the physical build portion of this project is still in progress. The amount of detail and organization required to finagle the parts I have into the enclosure that I have has been nothing short of "the most" - but I finally worked it all out over the past few weeks and can start wiring everything this week.

While I'm doing this, I had a thought - I'd love to have an "a la carte" mode - where instead of having to use the programmed presets, I could just turn a pedal on or off using the preset switch (this may have been suggested at one point, but I wasn't quite in that frame of mind, so my apologies if that came up). This would allow me to experiment with combinations of pedals before programming them - sort of a preset bypass mode, if you will.

This would mean each switch doesn't read or write to EEPROM, but sort of becomes a one-to-one thing with the respective effect pedal (with 2 banks, of course, since I have 8 switches for 16 effects).

Before I go down a dark and long coding path, I just wanted to see how feasible such an idea is - and if feasible, I can at least wire that switch for it. Any advice? @alto777 ? hehe...

Code is below, as well as link to my final wokwi:

// This is a heavily modified version of this project: https://www.instructables.com/Arduino-based-8-loops-pedal-switcher/
// Information about photoFET muting can be found here: https://stompville.co.uk/?p=423
// You can follow the journey from the instructable to this final solution via the Arduino thread: https://forum.arduino.cc/t/effect-switcher-and-relays-eeprom-question/1209660/165
// There is a working simulation (wokwi) here: https://wokwi.com/projects/389032204915078145 - the red/blue LEDs represent optocouplers that power the relays
// Any diagrams or relevant coding are in the Arduino thread, including power management and .json file for creating your own Wokwi simulation
// Special thanks to Alto777 and "she who will not be named" on the Arduino forum for the teaching and coding and solving and all of the things


# 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

void okDelay(unsigned long theDelay) {    // use this instead of delay() - mostly for setup or short pulses.
  delay(theDelay);
}

enum escreen {  // list of different LCD display screens
  DUMBBITCH = 101, READPRESET, WRITEOUT, RUN, SAVED, PROGRAMMODE, SAVEMODE, SPLASH, NOSCREEN
};

const 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);

// setup of neopixels
# define ELEDS 16 // how many
# define EPIN  12 // which pin
# define PLEDS 8
# define PPIN  13
# define SLEDS 1
# define SPIN  A15

Adafruit_NeoPixel eLED(ELEDS, EPIN, NEO_RGB + NEO_KHZ800); // effect LEDs
Adafruit_NeoPixel pLED(PLEDS, PPIN, NEO_RGB + NEO_KHZ800); // preset LEDs
Adafruit_NeoPixel sLED(SLEDS, SPIN, NEO_RGB + NEO_KHZ800); // program/save LED

# define rHot HIGH
# define rGnd LOW

const byte rows = 1;
const byte cols = 8; // number of presets - up to 16

char keys[rows][cols] = {
  { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' },  // max 16 columns and max 10 rows. Ensure to match the numbers in const byte rows/cols above
};

const int numberOfPedals = 16;  // your total number of effects/pedals
const int bankSize = numberOfPedals * numberOfPedals;   // bank variable to skip ahead by a full bank
const int numberOfBanks = 9;  // up to 14 banks @ 16 pedals; 27 banks @ 12 pedals, 62 banks @ 8 pedals - be sure to adjust LCD displays to accommodate double digit bank readout
const int numberOfEfBks = 2;  // no more than 2 effect banks - for use ONLY when you have HALF the number of footswitches compared with the number of effects
const int numberOfPhotos = 2;  // photoFET variable to mute signal during switching
const int effectVariable = 8;  // effect variable for the effect banks to pull proper keys
const int numberOfProgNeos = 1;  // program/save LED variable

byte colPins[cols] = { 4, 5, 6, 7, 8, 9, 10, 11 };   // buttons or momentary switches
byte rowPins[rows] = { 2 };                          // just 1 row is needed

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 photoPin[numberOfPhotos] = { 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"};

const byte NOKEY = 99;

// These reset things. Named obvioulsy.
void progLampsOff() {
  for (int ii = 0; ii < numberOfPedals; ii++) {
    setPresetLED(ii, HIGH);
  }
}

void allLampsOff() {
  for (int ii = 0; ii < numberOfPedals; ii++) {
    setPresetLED(ii, HIGH);
    setEffectLED(ii, HIGH);
    sLED.clear();
  }
}

// save your effect/loop selections to a preset
void memory(int theAddr) {    
  for (int ii = 0; ii < numberOfPedals; ii++) {
    EEPROM.write((theAddr) + ii, getEffectLED(ii));
    setPresetLED(ii, HIGH);
  }
  for (int ii = 0; ii < numberOfPedals; ii++) {
    setEffectLED(ii, HIGH);
  }
}

// select your effects/loops for saving
void writeOut(int relay) {
  progLampsOff();
  setEffectLED(relay, getEffectLED(relay) ? LOW : HIGH);
}

// default mode - USE THE SWITCH!
void eepromToRelays(int address) {
  for (int ii = 0; ii < numberOfPhotos; ii++) {
    digitalWrite(photoPin[ii], rHot); //turn ON all photofets
  }
  okDelay(4); // FRONT mute to 
  for (int ii = 0; ii < numberOfPedals; ii++) {
    bool onOff = EEPROM.read((address) + ii) ? HIGH : LOW;
    if (onOff) {
      digitalWrite(OneRelayPin[ii], rGnd);
      digitalWrite(TenRelayPin[ii], rHot);
    } else {
      digitalWrite(TenRelayPin[ii], rGnd);
      digitalWrite(OneRelayPin[ii], rHot);
    }
    int kPreset = EEPROM.read((address) + ii);
    setEffectLED(ii, kPreset);
    setPresetLED(ii, HIGH);
  }
  okDelay(5);  // + BACK, if
  for (int ii = 0; ii < numberOfPedals; ii++) {
    digitalWrite(OneRelayPin[ii], rGnd);
    digitalWrite(TenRelayPin[ii], rGnd);
  }
  okDelay(1);
  for (int ii = 0; ii < numberOfPhotos; ii++) {
    digitalWrite(photoPin[ii], rGnd); //turn OFF all photofets last step
  }
}

void readPreset(int address) {
  eepromToRelays(address);
}

void loop() {
  static byte realMode = READ;
  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) {    // effectBank 1 is effects 1 through 8, effectBank 2 is 9 through 16
      key += effectVariable;
    }
  }

  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

  runAckBlinker();

  switch (realMode) {
    case WRITE:
      progPresetLED();  
      if (doBank) {
        effectBank = 2;
        Serial.print("effect bank became "); Serial.println(effectBank);
        paintLCD(WRITEOUT, 0, 103);
      }
      if (doDown) {
        effectBank = 1;
        Serial.print("effect bank became "); Serial.println(effectBank);
        paintLCD(WRITEOUT, 0, 103);
      }
      if (doProg) {
        realMode = READ;
        allLampsOff();
        paintLCD(RUN, 0, 103);
      }
      if (doSave) {
        realMode = SAVE;
        alreadyStored();
        paintLCD(SAVEMODE);
      }
      if (key != NOKEY) {
        writeOut(key); // relay
        paintLCD(WRITEOUT, key);
        alreadyStored();
      }
      break;

    case SAVE:
      savePresetLED();  
      effectBank = 1;
      if (doProg) {
        realMode = READ;
        alreadyStored();
        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);  // theAddr
        paintLCD(SAVED, key);
        blinkAnAck(key);
        realMode = READ;
        paintLCD(RUN, 0, 101);
      }
      break;

    case READ:
      clearPresetLED();  
      if (doBank) {
        currentBank++;
        if (currentBank > numberOfBanks) currentBank = 1;
        Serial.print("preset bank became "); Serial.println(currentBank);
        allLampsOff();
        paintLCD(RUN, 0, 102);
      }
      if (doDown) {
        currentBank--;
        if (currentBank < 1) currentBank = numberOfBanks;
        Serial.print("preset bank became "); Serial.println(currentBank);
        allLampsOff();
        paintLCD(RUN, 0, 102);
      }
      if (doProg) {
        realMode = WRITE;
        allLampsOff();
        alreadyStored();
        paintLCD(PROGRAMMODE);
      }
      if (doSave) {
        paintLCD(DUMBBITCH);
        paintLCD(RUN, 0, 104);
      }
      if (key != NOKEY) {
        readPreset(currentBank * bankSize + numberOfPedals * key);
        setPresetLED(key, LOW); // all done and only now do we say
        paintLCD(READPRESET, key);
      }
      break;

    default:
      Serial.print(realMode);
      Serial.println(" error");
  }
}

void spillMode() {
  Serial.println("spillMode broken just now, write it");
}

void setupRelays() {  // engages relays on and off at start-up - triggering pin 10 across the board puts them in default bypass mode. 
  for (int ii = 0; ii < numberOfPedals; ii++) {
    pinMode(OneRelayPin[ii], OUTPUT);
    pinMode(TenRelayPin[ii], OUTPUT);
    digitalWrite(TenRelayPin[ii], rHot); //rHot
    okDelay(50);
    digitalWrite(TenRelayPin[ii], rGnd); //rGnd
  }
// The below is needed only when you have single coil relays
/*  for (int ii = 0; ii < numberOfPedals; ii++) {
    digitalWrite(OneRelayPin[ii], rHot); //rHot
    okDelay(20);
    digitalWrite(TenRelayPin[ii], rHot); //rHot
    digitalWrite(OneRelayPin[ii], rGnd); //rGnd
    okDelay(20);
    digitalWrite(TenRelayPin[ii], rGnd); //rGnd
    okDelay(20);
  }*/
}

void alreadyStored() {   // this tells us which preset has something stored during program and save mode
  int theBank = currentBank;
  for (int thePreset = 0; thePreset < numberOfPedals; thePreset++) {
    bool anyEffect = false;
    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() {   // shows the rainbow and sets the neopixels on startup
  eLED.begin();
  pLED.begin();
  sLED.begin();
  eLED.show();
  pLED.show();
  sLED.show();
  eLED.setBrightness(40); // up to 255... if you want to go blind, that is. And use a LOT of power. 
  pLED.setBrightness(40); // IOW, put these below 50. You'll thank yourself. 
  sLED.setBrightness(40);
  rainbow(0); // start the rainbow - has to be zero, otherwise it will never end
  eLED.clear();
  pLED.clear();
  sLED.clear();
  eLED.show();
  pLED.show();
  sLED.show();
}

void rainbow(int wait) {    // the rainbow function. duh.
  for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; firstPixelHue += 256) {
    for (long ii = 0; ii < eLED.numPixels(); ii++) {
      long pixelHue = firstPixelHue + (ii * 65536L / eLED.numPixels());
      eLED.setPixelColor(ii, eLED.gamma32(eLED.ColorHSV(pixelHue)));
    }
    for (long ii = 0; ii < pLED.numPixels(); ii++) {
      long pixelHue = firstPixelHue + (ii * 65536L / pLED.numPixels());
      pLED.setPixelColor(ii, pLED.gamma32(pLED.ColorHSV(pixelHue)));
    }
    for (long ii = 0; ii < sLED.numPixels(); ii++) {
      long pixelHue = firstPixelHue + (ii * 65536L / sLED.numPixels());
      sLED.setPixelColor(ii, sLED.gamma32(sLED.ColorHSV(pixelHue)));
    }
    pLED.show();
    eLED.show();
    sLED.show();
    okDelay(wait);
  }
}

char kStoreEffectLED[numberOfPedals];

// set colors for neopixels here
const unsigned long neoClear = 0x000000;   // Clear
const unsigned long neoMagenta = 0x800080;   // Magenta
const unsigned long neoGreen = 0x00ff00;   // Green
const unsigned long neoYellow = 0xffff00;   // Yellow
const unsigned long neoOrange = 0xff9900;   // Orange Sherbert

void setEffectLED(int theLED, int theValue) {    // these are the effect LEDs in read mode
  eLED.setPixelColor(theLED, theValue ? 1 : neoYellow);
  eLED.show();
  kStoreEffectLED[theLED] = theValue;
}

int getEffectLED(int theLED) {   // this allows the neopixels to work in program and save modes
  return kStoreEffectLED[theLED] ? 1 : 0;
}

void setPresetLED(int theLED, int theValue) {    // these are the preset LEDs in read mode
  pLED.setPixelColor(theLED, theValue ? 1 : neoGreen);  
  pLED.show();
}

void progPresetLED() {    // these is the program/save LED color in program mode
  for (int ii = 0; ii < numberOfProgNeos; ii++) {
    sLED.setPixelColor(ii, neoMagenta);  
    sLED.show();
  }
}

void savePresetLED() {    // these is the program/save LED color in save mode
  for (int ii = 0; ii < numberOfProgNeos; ii++) {
    sLED.setPixelColor(ii, neoOrange);  
    sLED.show();
  }
}

void clearPresetLED() {    // these is the program/save LED color in read mode
  for (int ii = 0; ii < numberOfProgNeos; ii++) {
    sLED.setPixelColor(ii, HIGH);  
    sLED.show();
  }
}

// Blink and it's all over... 
static bool blinkingAnAck = false;
static int blinkCounter;
static int blinkingLED;

const byte blinkPeriod = 100;   // how long each flash stays on
const byte blinkNTimes = 19;   // how many times it blinks - has to be odd to leave the theLED off

void blinkAnAck(int theLED) {
  blinkingAnAck = true;
  blinkCounter = blinkNTimes;
  blinkingLED = theLED;
}

void runAckBlinker() {
  if (!blinkingAnAck) return;
  static unsigned long lastBlink;
  unsigned long now = millis();     
  if (now - lastBlink > blinkPeriod) {
    pLED.setPixelColor(blinkingLED, blinkCounter & 0x1 ? 1 : neoOrange);  
    pLED.show();
    blinkCounter--;
    lastBlink = now;
  }
  if (!blinkCounter) killAckBlinker();
}

void runAckBlinker(bool killMe) {
  if (killMe) killAckBlinker();
  else runAckBlinker();
}

void killAckBlinker() {
  if (blinkingAnAck) {
    eLED.clear();
    blinkingAnAck = false;
  }
}

void beginLCD() {
  lcd.begin(16, 2);

  // custom LCD characters - used only during write/program mode
  byte effChosenLCD[8] = { 0b11111, 0b10101, 0b11111, 0b10101, 0b11111, 0b11111, 0b11011, 0b11111 }; // pedal icon - also dominos
  byte effBankLCD[8] = { 0b00000, 0b00000, 0b11111, 0b01110, 0b00100, 0b00000, 0b00000, 0b00000 }; // down arrow
  lcd.createChar(0, effChosenLCD);
  lcd.createChar(1, effBankLCD);
}

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) {
    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
  }
}

void paintLCD(byte screenName) {   
  paintLCD(screenName, 97, 98);
}

void paintLCD(byte screenName, byte argument) {   
  paintLCD(screenName, argument, 99);
}

void paintLCD(byte screenName, byte argument, byte extra) {   
  if (lcdDwellTime) {
    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;
      savedLCD(argument);
      break;

    case PROGRAMMODE:
      programModeLCD();
      break;

    case SAVEMODE:
      saveModeLCD();
      break;

    case SPLASH:
      lcdDwellTime = now;
      splashLCD();
      break;

    default:
      Serial.print(screenName);
      Serial.println(" error unknown screen name/number");
  }
}

// LCD readouts for 16x2 LCD display - instead of using lcd.clear each time, just put in spaces until you get to 16 spaces per line.
void dumbBitchLCD() {
  lcd.setCursor(0, 0);
  lcd.print("Nope...         ");
  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");
}

// writeOutLCD displays the effects from right to left, which is how they would be chained together in the real world
void writeOutLCD(byte) {     
  lcd.setCursor(0, 0);
  if (effectBank == 1) {    // byte 1 is down arrows denoting active effectBank,
    lcd.print("  FX-2  "); 
    lcd.write((byte)1); 
    lcd.write((byte)1); 
    lcd.print("FX-1"); 
    lcd.write((byte)1); 
    lcd.write((byte)1);
  } else if (effectBank == 2) {
    lcd.write((byte)1); 
    lcd.write((byte)1); 
    lcd.print("FX-2"); 
    lcd.write((byte)1); 
    lcd.write((byte)1); 
    lcd.print("  FX-1  "); 
  }
  for (int ii = 0; ii < numberOfPedals; ii++) {    // byte 0 is the pedal/domino shape
    if (getEffectLED(ii) == LOW) {
      lcd.setCursor(((numberOfPedals - 1) - ii), 1); lcd.write((byte)0);
    } else {
      lcd.setCursor(((numberOfPedals - 1) - ii), 1); lcd.print(" ");
    }
  }
}

void savedLCD(byte theLED) {
  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 B-");
  lcd.print(currentBank);
  lcd.setCursor(0, 1);
  lcd.print("Select Pedals   ");
}

void saveModeLCD() {
  lcd.setCursor(0, 0);
  lcd.print("Save Mode       ");
  lcd.setCursor(0, 1);
  lcd.print("Select Preset   ");
}

void splashLCD() { // a lovely affirmation to set off each session right! 
  lcd.setCursor(0, 0);
  lcd.print("You Gorgeous Fat");
  lcd.setCursor(0, 1);
  lcd.print("Sausage Bitch!  ");
}

// Tryna' set me up? Better be ready to finish the job... 
void setup() {
  Serial.begin(1000000); /* not for midi communication - pin 1 TX */
  Serial.println("first things first.\n");

  beginLCD();
  paintLCD(SPLASH);

  for (int ii = 0; ii < numberOfPhotos; ii++) {
    pinMode(photoPin[ii], OUTPUT);
    digitalWrite(photoPin[ii], rHot);
  }

  setupVLEDs(); // see if they're working - wow, rainbow!
  setupRelays();  // relay pins and initial state

  ezBankButton.setDebounceTime(10);
  ezProgButton.setDebounceTime(10);
  ezSaveButton.setDebounceTime(10);
  ezDownButton.setDebounceTime(10);

  for (int ii = 0; ii < numberOfPhotos; ii++) {
    digitalWrite(photoPin[ii], rGnd);
  }

  paintLCD(RUN, 0, 111);
  allLampsOff();
}

It did. No worries. As I recall, it would have been a simple matter at the time.

I am literally out the door just now, three guesses as to where I am off. But I think it would not be hard yet to do something like that. Ideally it would not mean more switches or modes, just could fall out of how it already almost works.

The organic growth and changes to the sketch may have painted you into a corner. It's hard to leave every door open as a sketch nears "final" status. And when last minute fixes are done "knowing" it won't matter if they be hacks or kluges, it gets worse.

a7

My god, I should listen more. I try, but I definitely don't always see the larger picture that others see until I'm way too far "in it" and then need to revert a bit. I believe my original thinking was that I just didn't have space for another footswitch, and my focus was so intensely on the programming part of this switcher as a whole.

I originally had a master bypass footswitch, which is useless in hindsight - it just bypasses everything, and would only be used if something broke in the switch. I could just as easily disconnect and plug my guitar direct to the amp, so using that footswitch for an a la carte mode makes a LOT more sense since I'm always tweaking how I mix my pedals and whatnot.

Y'all know the possibilities with this platform more than I, so I'm 100% open to ideas and thinking - you've yet to steer me wrong! If it's helpful, here's my current switch enclosure layout:

My "simple" and "first-thought" solution was to replace master bypass with a la carte (as shown in the above image). Doesn't have to be that, but there's a spot for it and I can wire it just like the bank/prog/save switches. lemme know if you've got some better ideas, tho!

Truth, but I knew I'd never truly be done with this one - I'm mounting the Arduino such that the USB port is accessible from the side without having to open it up so I can make updates as I use it. I just didn't think I'd be making updates before I even had the wiring finished, LOL.

Y'all know how to live life and I'm jealous! I've been elbow deep in this enclosure if I'm not workin... Need some vacation soon...

More to follow - for now: more wiring. more soldering.

On January 17, someone said

I can't look at the code just now, but it seems like this was an easy way to get a direct manipulation going.

It could be a mode you invoke somehow, or just how it works: now the changes you put in making a patch are not immediately kept up to date on the relays.

Maybe just one preset on one bank could have the live changes take immediate effect behaviour.

a7

It's just so hard to remember who said what and when... :wink: hehe.

Hindsight being what it is, I feel like I had some issue with always engaging the relays during programming, but I'm not going to be programming so often that the relays will wear out or something ridiculous like that which my brain cooks up while overthinking everything.

But it makes sense, hearing it out - Effects can be chosen in program mode, tested out, and then a simple tap of the save button and a preset, and we've got it set. Perhaps a rename/rebrand of the program mode to simply "effect mode" - so preset mode is default, effect mode is engaged with the former "program" footswitch, save mode engaged with the "save" footswitch. I'd need to figure out what happens if I leave program/effect mode without saving, but that's easy enough to test. I should probably take a look at the naming for all modes doing this.

This may help another thing I was going to address post-build, which is that each relay gets a jolt whether it needs it or not when switching presets - if one effect is disengaged, it'll still get a jolt to disengage, which isn't very efficient. I could use the mechanism for effect selection to possibly get me to where I only jolt whatever effects need it.

This. This right here is why I keep coming back. :slight_smile:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.