Effect switcher and relays - EEPROM question

You'd be amazed at what I can unfixable-y break (just look at how I used and spelled "unfixable-y")!

everything in there looks understandable and makes sense, so either I'm getting cocky or actually learning - or both! Will read through the comments and incorporate later tonight, but really liking how it's coming out - Will also give my buttons issue a think (I need more than I have). More to follow for sure...

Haha, I meant try to make it fail. It is fragile, so tinkering with the code means breaking it almost certainly, we did many times getting the last details correct. If we did...

Just try every thing you can think of. Invoke the dumb bitch. Hit save again when you are being asked for the slot button to be pressed. Hit buttons or keys whilst the transient messages are being displyaed, or the green LED is flashing acknowledgment.

Watch the real parts, and follow along with the verbose running commentary.

a7

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

Looks like I corrected the button issue by decreasing the debounce time from 20 to 5 for each of the buttons.

And somehow the LCD is working properly now, too - that went better once I commented out the midichannel.

I gotta play with some timings, but it’s getting close! I can feel it! Hehe…

That's counterintuitive at best. I don't usually use any library for buttons, too many unknowns.

I do know that in some libraries it is essential not only to call the provided loop() service method, but to call it at a relatively high frequency.

Which our loop() should be doing without any problem. Unless somehow it has become compromised…

I'll see if I can replicate the problem. A back pocket solution would be to use my own button thing, which I don't usually in these shared code situations just to make things ez-er for those trying to run or exploit any sketches that are published.

a7

My original code had a more standardized debounce, which was set to 5, which is why I tried playing with it here.

well, I don't think that's necessary just yet, hehe... Trying to keep a level head, but I think it's looking and feeling pretty good. There's some finer details I'm thinking about, like timing of blinks and LCD displays, or what LCDs should say, or dumpEEPROM (now "alreadyStored" hehe) showing LEDs in another color - things like that.

but debugging for now is needed, and there's an annoying 8-buttons-for-16-effects issue I need to think about down the road, hehe...

I have tested ezButton and find that it can produce multiple isPressed() events as well as another method we don't use, isReleased() but only in circumstances I just do not think obtain with the current code.

IRL you may find 5 millisecond debounce time brings back some issues; I have some crappy switches that def need more than that.

Also, my friend was working her version and pointed out this

    if (doSave) {
      dumbBitchLCD();
      paintLCD(DUMBBITCH);

which shouldn't be a problem, but lose the direct call to dumbBitchLCD() as it is entirely unnecessary. At the higher level only paintLCD() needs be used. I'll look to see if there are any others like that, at worst it is a waste of time and might cause some flicker.

She has also isolated all the LCD stuff off to separate tabs, but I don't think that's worth "opening it up" for you, it meant somewhat pervasive changes all over the high level code, so.

Meanwhile we are both somewhat amazed that you were able to take and run with the hole "pended message" transient message handler... nice work.

It seems to function here, even with 20 ms debounce time. I don't like hearing about the flaws as there is no good reason, please next time if you can get one into captivity and describe exactly how to make it happen let me know.

Stuff that happens occasionally or inconsistently is certainly possible, so we mightn't have observed anything like you report. That would indicate a kind of defect that will be very hard to find, so let us hope when/if the time comes the cause can be located. Things like this may mean arrays being addressed out of bounds.

We spent some time tearing out hair over the inadvertent omission of the LCD begin() method, you didn't want to be there for longer than I will admit to its haven taken to find.

Also, this comment just scrolled by

  lcd.clear();  // sweep one day for needless clearing to solve flashing when the same message is repainted

I think we (she) did all the transient screen stuff on a version with lcd.clear() all over the place, or maybe added it (back) rather than count characters.

Lastly, now that the loop runs free, and you seem to be managing the neopixels correctly, there is no need to call the show() method on the strips except once per loop.

Don't change it yet, but we've just commented out all of them except those in any setup functions, and wrote as the first two lines of the loop()

void loop() {

  eLED.show();
  pLED.show();

and it seems to work fine to the degree we've tested it.

This is the beauty of a free running loop written if not strictly then at least in the spirit of the IPO model. I haven't measured it, but you coming around the loop many many times a second; any timed effects on the neopixels is by virtue of the various machines that move things along one step at a time at the proper time, so showing only needs be done once.

It is sad that the simulated neopixel parts are not as punchy as the simulated regularLEDs, it's harder to see and confirm their correct behaviour.

Real neopixels are hella bright, look forward to tuning the colours you've chosen. I usually use pretty hot colours and mask them with a neutral density filter gel. I have a lifetime supply in a few densitie, they are optically very good and can be stacked for a variety of attenuation effects.

a7

We went over both versions with the finest of tooth combs w/o seeing anything jump up.

We reread your problems descriptions: When you say the LCD goes blank, is it just a flicker of blankness or do you mean there is nothing on the LCD, if so, until when?

We see nothing but the typical very brief flicker as caused by the clear() method.

a7

Updated wokwi: https://wokwi.com/projects/387935845357494273

I changed back to 20 on the debounce, and on my faster machine it's fine - I think my older Mac just doesn't like the wokwi sometimes; the speed meter in the upper right is always slower on that one than this newer one (60-75% vs. ~100% all the time). Everything works on the old Mac, but I think there's some lag in the interface at those speeds that doesn't play nice with buttons.

as for the footswitches, I should wire the ones I have to the breadboard and see how they work. I've been using tiny pushbuttons for testing, but that's not what I'm using in realtime, so this is a good reality check.

removed. good call. (also, LOL'd that my mean messaging became its own function - nice work!)

Like I said - either I'm getting cocky, or actually learning; or both! Maybe I'm just good at copy/paste and tweak until it works - but I do think I'm getting a handle on what everything is doing, which is good! Definitely a good learning exercise...

lcd.begin is in the beginLCD function, which is oddly buried in a weird spot... It just took me a minute to find it myself, lol. Maybe I'll move that closer to the void setup().

Good call on the lcd.clear; stuff - got rid of it and everything should be good now... amazing that you can just change them all at once so easily when they're all in the same spot! (this is where you take a victory lap :wink: )

consider it not changed! But noted!

I agree - probably not worth wokwi's time to create 5mm versions of neopixels, as I'm sure they're niche for what people typically use them for. I am a little worried about brightness, but the 5mm versions only come in diffused (they discontinued the clear ones) so I'm hoping that helps a bit. They are RGB only (I guess the others are RGBW, which I assume means RGB+white) - there's a bunch of threads online about changing brightness of them, notably this thread has several ways of varying control, but until I actually have them, I'm not gonna worry too much about specific colors or brightness just yet.

I think I'm at the point where I need to order my extra materials (gonna need to order some 817 and s8050 optos/transistors to handle all the relays, a new 5v converter, some LED bezels, and it's recommended to have .1u caps on the power supply for the LEDs - I'm almost out, so gotta order new ones anyway).

This is where I think the 8-buttons-for-16-effects thing comes in, as I may need parts for it, but I'm dreading it. I don't have space for more buttons. The only time I would need 16 buttons is during programming, anyway - in general use, I'm only going to have 8 presets (well, I guess 16 is technically possible, but I only want 8). Here's my current thinking, and would love some thoughts here...

  • Long press or double press of a button. This effectively makes 2 "effects banks" (as opposed to the current "preset banks"). Feels complicated to code, and I know you're no fan of "hidden" functionality. BUT, it's only for programming, which I won't do often... BUT, that also means I could easily forget how to do it, lol.

  • use a manual footswitch/toggle to switch between button inputs. Inelegant, but easier to code and less prone to "forgetting how". I would have to give up either my bank down switch or my master bypass switch (master bypass allows me to get around the switch completely should a relay break or something go awry. It's a typical feature on things like this).

  • Small pushbuttons on the back. This is particularly hack-y and wildly inelegant. It creates a one-to-one button to effect situation from a code perspective, but also means that I'd be bending down to the floor every time I go to program, and who know what could hit those little buggers at any point in time should a pedal shift or something.

I lean toward long-press, but open to other solutions. If I can't make it work, then it's back to 9 effects, but I REALLY wanna make it work, lol.

more to follow, but it's looking good. REAL good...

I'm having better luck on my better computer... I just did a long winded reply with all kindsa thoughts, but generally I'm unable to get it to hiccup anywhere just yet. Will test throughout the day.

Inchestin'.

Theoretically all that shoukd happen when the real time performance nose dives is that everything s l o w s d o w n but all processes should perform the same.

Like if you took the right combination of drugs you wouldn't even notice.

I have seen, however, that there is some unfortunate sensitivity to the combination of browser choice and device type.

I have all but given up on using the wokwi on any tablet; on my big rig Safari loses to Chrome for fidelity and operability.

So… the nice thing is that these gremlins can be made to go away.

I was wondering and you have answered whether these observations were from the simulation or any real circuitry you are working. It is nice to go to the real world with solid confidence in the software.

That's why we like to leave all the chatting spewing out. Until you don't need it, those messages can be a very good first guide to confirming that the code is doing what it does because that is what you wanted it to do, not just on accident.

Yes. If I had to pick one basic idea this one would be a contender. Never do in N places something that cloud be done in only one, either by rearranging things logically or by use of a function or a data structure or whatever.

This is getting to be a bigger than small sketch, all such steps taken will make it more compact and way easier to perfect, modify or enhance.

Rainy. Very warm. The UA will meet at the beach and do the other things you can do there all day long…

CU. Pound on it, find the little cracks be they logic problems or aesthetics.

a7

We were reading the code looking for more things to mess you up with, and I noticed that in the function setupVLEDs(), you copy/pasted the initialisation dance.

The logic behind it is to confirm that the strip is wired correctly, the correct strip is being named in the object (NEO_GRB) so you get red and green and blue like you think and to light up the last LED on the strip (or chain in your case) so we know which end is which and the entire chain is wired.

It isn't worth doing but if you ever are at a loss for one more pin, there is nothing to keep a bit of logic from letting you have just one longer chain.

Also we have found the loop is moving quite fast, so when if you are ever done with this, we can make a pass and pound a little more IPO into it using just two calls to the show() methods.

We've got it running on a real Mega, you might like to use this one

Shop around, buy from some source you trust. These little guys are very well built, so cute. And inexpensive.

a7

I wondered about this, but figured it would be easier to keep them separate - knowing the logic now, I imagine I'd just change this:

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

to this

# define ELEDS 12
# define PLEDS 12
# define NEOPIN  32

Adafruit_NeoPixel eLED(ELEDS, NEOPIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel pLED(PLEDS, NEOPIN, NEO_GRB + NEO_KHZ800);

First 12 are effects, latter 12 are presets. I could test it... (I just tested and it didn't work, hehe... I think I have to define some kind of order, so I'll look deeper into that).

Let's add it! I gotta order parts anyway, so there's some time still...

OOOH - a tiny one! I have 2 regular size mega's right now: one for testing (I was convinced I was gonna blow it up, hehe), and then one that I'd use as either a backup or who knows. Space is at a premium, and I've been wondering about soldering, shields, etc., so I think this is great.

I have been looking at longpress options for the buttons, but I had another idea... maybe change the bank button functions during program/save, so in read mode, it's 9 banks of 8 presets, but in program/save modes, it's 2 banks of 8 effects. This, of course, means that you couldn't choose your preset bank while you're IN program mode, but I think that's okay - my logic was always to go to the bank you wanted, and then start programming there.

If that's not feasible, I'd have to use a longpress on the bank up button. Found this:

Not quite sure how to interpret it for my use case, but it's a starting point... Again, if I can get to my ideal "16 individual control over each relay," this headache will be worth it! At least that's what I tell myself, lol.

More to follow...

There is a place for creativity, even improvisation, in coding, but no more than for anywhere else would such be useful until you have the basics firmly in hand.

As you have seen, one can imagine all day long and then the cold reality of what happens when you get out over your skis a bit hits...

I'll refrain from offering glimpses of the future. If that extra pin should actually become critical, something very interesting will have developed, and looking for one more pin on a Mega will likely be the least of your problems.

Somehow I threw a the version where there were no real LEDs. Otherwise I would have pointed it out again, but it really is a detour that is not worth the trouble.

Haha, have you tried to switch banks while in programming mode?

// switching bank in any mode but READ places us back in READ mode
  if (doBank) {
    realMode = READ; // rude! this could be handled differently

The most you lose is one pass of setting up the selected loops, which while rude isn't like the old days when things didn't warn you before a misstep meant goodbye the term paper.

Keeping up with a not-small toy like this is a frustrating matter of keeping both the large picture and all the pesky details that make things happen, forest and trees, in mind at all times. Which is why I've said at least once this is geting to the point where it can be used as a functional specification for a total re-write. Which is too very rarely how things like this play out, even in professional circumstances. At the center of Windows (still? I haven't kept track) is it beating heart which is good old MS-DOS...

a7

ha! I had not, apparently - my testing isn't so thorough after all. Tomorrow is my day without distraction to really dig in. I know I was able to at some point, but it's not necessary...

Alas, you may be right (and haven't steered me wrong thus far) - a good ol' college try isn't worth nothing, but this has already come MILES farther than I thought it could, so just thinking through ideas.

this won't be a problem - I just tried to put a insane scenario with extra relays for tuners and everything in there, and still come up with an extra 8 pins.

As this heads to its logical finish line, I want to be sure proper due and credit is given...

Being new (well, new again) to all this, I'm sure there's a protocol for such things that I just don't know about (e.g. a line in the code, or a simple thanks in the thread, etc.)

Most ppl who contribute here know it can be a thankless undertaking. Too often the OP disappears with a solution or code and we never hear it worked out or get any acknowledgment for having helped.

This slog has been a case study in stepwise refinement, an almost ideal real-world device powered by just enough Arduino all in pursuit of implementing what can be described in a paragraph and mastered by a user in minutes on an intuitive level.

As for thanks, be assured no need. Also, use the search tool up at the top of the window and search for occurrences of thank "in this thread", you'll see you've offered plenty of positive feedback.

But srsly, this has been fun. There are some hack-ish things going on, but we are not too unhappy with the extent to which goals repeatedly expressed by us throughout have been met, nor with exactly how those goals were accomplished.

The last thing, the pended message mechanism, I do wish I could take credit for. My solution never made it off the back of the envelope I was scribbling on, and although it might have been more by-the-book was turning into a little nightmare. She who noticed that it was just needing to stash one new static message before letting it go on to the LCD after some time passed or event made it led to a simple if kludgy bit of machinery.

a7

I've learned in life that "thank you" can go a long way - I make sure to show appreciation whenever possible, and will certainly post my final code and solution once I get there.

Still making some tweaks and had to add a photoFET for muting the relays during switching. I also started a version that has only 8 buttons for 16 effects:

https://wokwi.com/projects/388081117082022913

Currently there's a manual switch - this is simply to get functionality into the Wokwi and I fully plan to remove it.

As you said before, the banks don't work in program mode (in fact, hitting one takes you out of program mode altogether, which I should fix somehow) - but I'm thinking to create a function for the bank buttons within program mode only, creating 2 effects banks that are only active in WRITE mode (or basically bank 1: effects 1 to 8, and bank 2: effects 9-16). Once I hit save, it would default to selecting presets 1 to 16 (but I'll only have 8 buttons, so effectively presets 1 to 8).

The problem becomes how do I tell it that effectBank 1 is keys 'a' through 'h' and effectBank 2 is keys 'I' through 'p' - I gotta think through this, but it feels possible, so I'm giving it a think - even if it's a little (or a lot) hack-ish, it's better than a manual switch, right? hehe...

(believe it or not, the manual switch is helping me with the logic - gotta see what it does and functions like before telling code to do your dirty work). I think I'm actually learning something...

So thank you once more - and yet again, more to follow! hehe...

Alas, I’ve been working away…

I was able to separate the preset bank from the effect bank by assigning the buttons within either READ (preset) or WRITE (effect).

So now it’s about how to write keys a-h in effect bank one and keys I through p in effect bank 2. (Such that the LEDs light as needed, and then get stored).

I moved things around, too, but in doing so the 8 buttons weren’t working quite right - I had to assign them to some crazy order of pins to get them working. Not sure what that’s about…

I’m close - and then it’s all about tweaks to timings and other miscellany.