Is My Code Bypassing If Conditions?

Hello everyone,

I hope I'm pasting my code properly; I saw a thread stating the experts on here like full code dumps, not snippets. If I need to retry please let me know.

A quick bit of context: I'm making a project for a class that involves some UI to manipulate lights and pathing of an automated marble run. I cannot for the life of me get my servo to act properly. This is frustrating for many reasons, not the least of which is my LEDs were built using ideas from the delay without delay and time blocking mega threads on here and work fine; I then tried building a second time with modifications to make my servo be manipulated and one of the if loops seems to be executing regardless of if the conditions are met.

I am very sure that the issue is the code and not an electrical problem; I will elaborate more if needed, but the strongest argument is that using Serial.println I can watch a boolean flip back and forth even when I (at least in terms of intent) lock it behind an If statement. it is the else branch in the changePath function that begins with println "path mode manual" to the end of the code.

I've tried rewriting it thinking I had a silly bug I couldn't find and the issue persisted. I have tried putting the if statement to two checks (pulled up input == LOW and a time block) as well as nesting them. I also once tried just making it input == low as the condition and using the serial monitor as well as confirming voltage with my DMM I can see that the button is HIGH. Yet, the pathA boolean will still swap back and forth.

I then wondered if I had inadvertently used that boolean earlier in my code but didn't see any, and on top of that, even if I had, that still doesn't explain why it runs through anything other than printing "pathMode manual" and "pathToggle Value"; at that point, there are nested If statements which check for time to debounce (an idea given by the aforementioned thread) as well as reading my pin for the pathToggle UI once the build is finished.

I can't think of what else it can be. Again, because of what I've already done to try and debug, I'm very sure it is code. In the event someone disagrees, let me know what you want to know on the hardware side and I'll provide that information.

Thanks in advance, everyone.

#include <Servo.h>  //Importing the servo library

Servo diverter;  //Making an instance for my servo used to divert the marbles to one path or the other



//defining inputs

const int lightMode = 4;
const int lightToggle = 6;

const int pathMode = 2;
const int pathToggle = 3;




//defining outputs

const int servoPosition = 5;

const int servoPowerRelay = 7;
const int motorPowerRelay = 8;

const int red = 10;
const int yellow = 11;
const int blue = 12;
const int green = 13;




// defining variables and constants

bool pathAuto = false;   //this will be reference to tell if pathing is in automatic or manual
bool lightAuto = false;  //this will be reference to tell if lighting is in automatic or manual

const int pathRate = 4000;  //interval for switching paths when in automatic
const int lightRate = 500;  //interval for switching LEDs when in automatic

bool pathA = true;  //one servo value found by trial and error to effectively path the marble
//const int pathB = false;  //the other servo value found by trial and error to effectively path the marble
//int path = pathA;      //holder for current path state

const int buttonPress = 300;  //this will be referenced as part of a software debounce

unsigned long currentMillis = 0;           //this will be used as an ever progressing timeline
unsigned long lightModeButtonMillis = 0;   //this will be referenced as part of a software debounce
unsigned long lightStateButtonMillis = 0;  //this will be referenced as part of a software debounce
unsigned long servoModeButtonMillis = 0;   //this will be referenced as part of a software debounce
unsigned long servoStateButtonMillis = 0;  //this will be referenced as part of a software debounce
unsigned long pathSwitched = 0;            // this will hold the time that the path last changed; used in automatic mode
unsigned long lightSwitched = 0;           // this will hold the time that the LED tower last changed; used in automatic mode


void setup() {


  Serial.begin(9600);

  pinMode(lightMode, INPUT_PULLUP);    //this will look for UI to switch between manual and automatic lighting
  pinMode(lightToggle, INPUT_PULLUP);  //this will look for UI to switch current light state when in manual

  pinMode(pathMode, INPUT_PULLUP);    //this will look for UI to switch between manual and automatic pathing
  pinMode(pathToggle, INPUT_PULLUP);  //this will look for UI to switch current path state when in manual

  diverter.attach(5);  //this will send one of two values to the servo for pathing control

  //these will set two pins to output and constant high as they're energizing the optoisolator on power relays
  pinMode(servoPowerRelay, OUTPUT);
  digitalWrite(servoPowerRelay, HIGH);
  pinMode(motorPowerRelay, OUTPUT);
  digitalWrite(motorPowerRelay, HIGH);

  //these will be the wires activating a battery of relays feeding the LED arrays
  pinMode(red, OUTPUT);
  pinMode(yellow, OUTPUT);
  pinMode(blue, OUTPUT);
  pinMode(green, OUTPUT);

  //this sets up the two UI controlled systems in a default starting state so that an LED is lit and the servo is positioned properly in case the system was previously shut off
  //while the servo was mid move
  //delay(2000);
  diverter.write(0);
  digitalWrite(random(10, 14), HIGH);  //this selects a random LED output pin to activate
  //digitalWrite(blue, HIGH);
  //Serial.println("end of setup");
  //delay(5000);
}

void loop() {

  //this will take the current reading of millis and pass it as a placeholder and then run through the functions which are where the logic lies.

  //Serial.println("loop");

  currentMillis = millis();

  //Serial.println("currentMillis");

  switchLightMode();

  //Serial.println("lightMode");

  switchPathMode();

  //Serial.println("pathMode");

  changeLED();

  //Serial.println("ledChange");

  changePath();

  //Serial.println("pathChange");
}

void switchLightMode() {

  //this function debounces by referencing variables and when the button is pressed, changes the state of the lightAuto bool

  if (millis() - lightModeButtonMillis >= buttonPress) {
    if (digitalRead(lightMode) == LOW) {
      lightAuto = !lightAuto;
      lightModeButtonMillis = millis();
      Serial.println("lightModeButton");
      //delay(500);
    }
  }
}

void switchPathMode() {

  //this function debounces by referencing variables and when the button is pressed, changes the state of the lightAuto bool

  if (millis() - servoModeButtonMillis >= buttonPress) {
    if (digitalRead(pathMode) == LOW) {
      pathAuto = !pathAuto;
      servoModeButtonMillis = millis();
      Serial.println("pathModeButton");
      //delay(500);
    }
  }
}

void changeLED() {
  if (lightAuto == true) {
    //Serial.println("lightAuto == true");
    if (currentMillis - lightSwitched >= lightRate) {
      digitalWrite(red, LOW);
      digitalWrite(yellow, LOW);
      digitalWrite(blue, LOW);
      digitalWrite(green, LOW);
      digitalWrite(random(10, 14), HIGH);  //this selects a random LED output pin to activate
      lightSwitched = currentMillis;
    }
  } else {
    if (millis() - lightStateButtonMillis >= buttonPress) {
      if (digitalRead(lightToggle) == LOW) {
        digitalWrite(red, LOW);
        digitalWrite(yellow, LOW);
        digitalWrite(blue, LOW);
        digitalWrite(green, LOW);
        digitalWrite(random(10, 14), HIGH);  //this selects a random LED output pin to activate
        lightStateButtonMillis = millis();
        Serial.println("lightSwitchButton");
        //delay(500);
      }
    }
  }
}

void changePath() {
  if (pathAuto == true) {
    Serial.println("pathMode auto");
    //delay(500);
    if (currentMillis - pathSwitched >= pathRate) {
      if (pathA == true) {
        diverter.write(0);
        pathA = false;
        pathSwitched = currentMillis;
        Serial.println("position 0");
      } else {
        diverter.write(20);
        pathA = true;
        pathSwitched = currentMillis;
        Serial.println("position 20");
      }
    }
  } else {
    Serial.println("pathMode manual");
    //delay(500);
    Serial.print("pathToggleValue:  ");
    Serial.println(digitalRead(pathToggle));
    if (currentMillis - servoStateButtonMillis >= buttonPress) {
      if (digitalRead(pathToggle == LOW)) {
        if (pathA != true) {
          diverter.write(0);
          Serial.println("pathA is not true");
          pathA = true;
          servoStateButtonMillis = currentMillis;
          //delay(500);
        }
        else {
          diverter.write(20);
          Serial.println("pathA is true");
          pathA = false;
          servoStateButtonMillis = currentMillis;
          //delay(500);
        }
      }
    }
  }
}






        /*
        //if (millis() - servoStateButtonMillis >= buttonPress && digitalRead(pathToggle == LOW)){
        Serial.println("pathSwitchButton");
        delay(500);
        Serial.println(pathA);
        if (pathA != true) {
          Serial.println("if block");
          delay(500);
          diverter.write(0);
          pathA = true;
          servoStateButtonMillis = currentMillis;
          Serial.println(pathA);
        } else {
          Serial.println("else block");
          delay(500);
          diverter.write(20);
          pathA = false;
          servoStateButtonMillis = currentMillis;
          Serial.println(pathA);
        }
      }
    }
  }
} */ 
if (digitalRead(pathToggle == LOW)) {

should be

if (digitalRead(pathToggle) == LOW) {

:wink:

2 Likes

Well spotted!

This is a very common source of bugs in C and derivative languages.

It's a fundamental flaw in C that booleans aren't their own data type. There's no reason why the compiler couldn't catch this error.

Hello. I see you have no delay calls that aren't commented out, and have a variable call curretnMillis that you set at the top of the loop.

So do you need more than one call to millis()?

My quick automatic test of a sketch to see what kindsa issues might come up includes looking for delays, which outside of setup() I don't like to see, and calls to millis() and micros() hoping there will be but one.

No large deal and prolly makes no difference, but you did say

currentMillis = 0; //this will be used as an ever progressing timeline

and use it some places, but not all. So would you need to use millis() again? Just abject curiosity.

When you get it working come back and share details. Sounds like fun.

a7

@alto777

Regarding curretnMillis, I don't see that, nor does my search function. I'm certainly not above typos, but I must admit I'm not sure where that claim comes from...

I found this idea of a currentMillis variable on the huge threads I keep mentioning; I'm not sure why one would regularly set a variable equal to a function result in this context, but following the ideas mentioned in the thread (seen here they use the millis() function for button presses and a variable reference for other things. I'd love some elaboration on why you only use one. Personally, I would just use millis() and forego the variable altogether, but I'm at best a reasonably intelligent hobbyist so there is a good chance I'm ignorant about something that would make that a bad idea.

Am I to assume that micros() is mills() but at the 10^-6 level? I'm surprised that these little inexpensive chips are able to do that reliably; honestly, even the number of things I have taking up about 1/6 of what a sketch can be is incredible to me because it checks so many things so quickly. What home level project would even use a microsecond timer?

Delays weren't in my script at all until today; the one in setup was put there because relays are going to be energized as well as the Arduino from a main power switch and the idea was to let everything become energized before the output pins send a signal, primarily so that the servo will be live to get the 'setup' position instruction sent to it. All of the others were sprinkled in while I was trying to figure out why the heck my code wouldn't do as I expected it to, which brings me to.....

@MaximoEsfuerzo, that worked! Thank you so much! I now absolutely must know why haha. I'll end up scraping the interwebs for an answer in the coming days if not, but I'd love it if you are willing to help me understand the why behind the what here.

Since I seem to have attracted a knowledgeable group, I have another code related question that isn't project breaking: every time that I set up my sketch, the green is the LED from the digitalWrite(random(10, 14), HIGH) is selected- I've tested this 1 in 4 option upwards of a dozen times so I don't believe it is a statistical anomaly- if it is, I should've quit school today to play the lotto. I also observed that sometimes (maybe always?) soon after I power the Arduino, the four LEDs will rapidly cycle at maybe twice the speed and then seem to coast into the 1/2 second interval after a second or so. I know that the green lights up on power because I have it on pin 13, but I would think after that boot blip that sometimes one of the other three would illuminate, and I also have no idea what that fast cycle is. After these two oddities pass, and they have never failed to work themselves through, the code acts as intended, to include slower changes when the same color gets lit twice in a row or the button seeming to not work for the same reason. So I think random does as I think, but I'm not sure.

Oh, typo. Sry.

The ieda of using one time for all activities that happen in one pass through the loop() is just a habit. It puts all players on the same page; I use it in animation where something is done every, say, 20 milliseconds and the calculations that inform the animation may take 15 of them. So to the extent that those calculations may also depend on time, it's nice that they are all able to have things done off the value of millis() grabbed at the top.

There real is no problem with using millis() everywhere, most of the time. It does, though, make it easier to swap in micros() for millis() or to replace millis() as the basis for timing, as it would only require changing one line of code.

Or, as I have done, to write a custom millis() that itself used some other timing mechanisms and would be one stop shopping for making something go faster than or slower than it would otherwise do, and even to make it c r a w l or STOP.

Grabbing inputs once per loop is another Good Idea. As you are marvelling now, these processors are fast fast, so fast that a re-reading of a pushbutton you checked a few lines of code ago might be different. Same with analogRead() - the idea is to get all those things once, and for the entirety of the loop eveyone uses the values stashed in variables.

Here you've applied the idea

void changeLED() {
  if (lightAuto == true) {
    //Serial.println("lightAuto == true");
    if (currentMillis - lightSwitched >= lightRate) {

and here

  } else {
    if (millis() - lightStateButtonMillis >= buttonPress) {
      if (digitalRead(lightToggle) == LOW) {

We read other ppl's code, and anytime something that is different but seems gratuitously so, it slows us down and causes a little bit of wondering. Hence my question as to whether that was advertent or not.


Read the code! You were reading a pin number, which number is the result of testing pathToggle and LOW for equality. The result of such a test is either 0 (false) or 1 (true), so this reads pin 0 or pin 1:

if (     digitalRead    (  pathToggle == LOW    )      ) {

The correct version and obviously what you were going for

if (  digitalRead(pathToggle)       ==       LOW) {

takes the result of reading pin pathToggle and tests that for equality to LOW. Very different, and similar to

   a = b + (c * d);

// is not

  a = (b + c) * d;

It is impressive when someone spots that kind of mistake; mistakes we may be well out of the time when we made them are hardest to spot.

HTH

a7

Yeah, I definitely overcomplicated the curiosity about why it worked (or, didn't). Because someone had commented about boolean types not existing in C and related languages and the complier should've caught it, I was in the mindset of 'there is some underlying, complicated com-sci reason that was the solution'. As soon as I saw how you had expanded it I saw that I was always reading I felt like an idiot. I will say, however, as a matter of learning and growth, I had not considered the idea of defining a read/write by an equation before. That is interesting even if any usefulness isn't apparent to me right now.

See

a7

Hey alto777,

I see that and have seen that before; it seems to me that is a way to get a list of random values without hand writing all sorts of integers over and over, but that those values can fall absolutely wherever in number space.

I would need the values to hit from 10-13 so that it lands on one of my four LED pins. I guess the part I don't get is how the random works as expected in the loop but not at the setup phase. Is there a way to constrain the randomSeed function to 10, 11, 12, and 13? I didn't come across one which is why I went with random instead.

Sry, you'll have to make sense of it in the meantime:

https://docs.arduino.cc/language-reference/en/functions/random-numbers/random/

a7

Generate a random number between 0 and 3, and add ten to it.

To generate a random number between 0 and 3, generate a random number between 0 and 1 and multiply it by 4.

A "normal" random number generator is actually a pseudo-random number generator. Think of the digits of pi. They kinda look random from 0 to 9, but there is a sequence.

If you don't manually seed the PRNG, it will start at the beginning -- 3 in the pi analogy -- and produce the same sequence of results. Using randomSeed is like picking which Nth digit to start at; and then produce the same sequence of results from that point forward. This repeatability is actually a feature, and also why PRNGs are not suitable for cryptography.

The docs for random and randomSeed mention using analogRead as a truer "dice roll"

Your entire answer is at best misleading, a distraction.

Have you read the documentation for either random() or randomSeed()?

Both have been linked on this thread. Review them, or see it for the first time.

a7

I built your project and ran it in the wokwi simulator. I won't use the word "fix" but I did change a number of things I thought merited the effort.

Your pushbuttons weren't being handled very well. I added state change to the mode changing buttons, and real debouncing, so now the controlled mode changes once per press of either button. The debounce time is 20 milliseconds.

I left the other buttons as they functioned. The debounce constant was functioning more like timer for having multiple actions whilst holding the button down, so those two switches have a different, longer, constant. The time between repeated actions.

I moved the random selection of the LED to a function, where I was then more inclined to modify the logic so it doesn't randomly choose the LED that is already illuminated.

I added manifest constants for the diverter angles.

Much of what I did allowed eliminating global variables, always a good thing. I used a pin array and some character constants for the LEDs, and generally enhanced the information coming from the serial printing.

The sketch is at that annoying size where taking time to change it for easier future development competes with just continuing to add code... you can see that the two debounced and state-detected pushbuttons use nearly identical code. To add a third would be work that might better be put into coding something general. Any time you see code that is almost identical happening, or you are tempted to cut/paste/edit your way forward, functions should be considered.

The program lines for the two auto-repeat buttons are also nearly identically.

The "select a random LED" code sequence was an example of using one function and calling it wherever you need to.

I completed the change to using currentMillis everywhere.

Undoubtedly I made other small changes of less significance w/o even realizing it.

Try it here. You could also paste your sketch in its entirety to replace the code in *.ino tab to see it work the same as IRL.



// https://wokwi.com/projects/418664622634574849

# include <Servo.h>  //Importing the servo library

Servo diverter;  //Making an instance for my servo used to divert the marbles to one path or the other

//defining inputs

const int lightMode = 4;
const int lightToggle = 6;

const int pathMode = 2;
const int pathToggle = 3;

const byte ledPin[] = {10, 11, 12, 13};
const byte numLEDs = sizeof ledPin / sizeof *ledPin;
const char *ledTags[] = {"red", "yellow", "blue", "green"};

//defining outputs

const int servoPosition = 5;

// angles for the diverter servo.
const int diverterLEFT = 35;
const int diverterRIGHT = 145;

const int servoPowerRelay = 7;
const int motorPowerRelay = 8;

// defining variables and constants

bool pathAuto = false;   //this will be reference to tell if pathing is in automatic or manual
bool lightAuto = false;  //this will be reference to tell if lighting is in automatic or manual

const int pathRate = 777;  // was 4000 interval for switching paths when in automatic
const int lightRate = 333;  //was 500 interval for switching LEDs when in automatic

bool pathA = true;  //one servo value found by trial and error to effectively path the marble
//const int pathB = false;  //the other servo value found by trial and error to effectively path the marble
//int path = pathA;      //holder for current path state

const int debounceTime = 20;  // debounce constant
const int keyRepeat = 300;    // repeat rate for buttons

unsigned long currentMillis = 0;           //this will be used as an ever progressing timeline
unsigned long pathSwitched = 0;            // this will hold the time that the path last changed; used in automatic mode
unsigned long lightSwitched = 0;           // this will hold the time that the LED tower last changed; used in automatic mode

void setup() {

  Serial.begin(9600);
  Serial.println("Jello Whirled!\n");

  pinMode(lightMode, INPUT_PULLUP);    //this will look for UI to switch between manual and automatic lighting
  pinMode(lightToggle, INPUT_PULLUP);  //this will look for UI to switch current light state when in manual

  pinMode(pathMode, INPUT_PULLUP);    //this will look for UI to switch between manual and automatic pathing
  pinMode(pathToggle, INPUT_PULLUP);  //this will look for UI to switch current path state when in manual

  diverter.attach(5);  //this will send one of two values to the servo for pathing control

  //these will set two pins to output and constant high as they're energizing the optoisolator on power relays
  pinMode(servoPowerRelay, OUTPUT);
  digitalWrite(servoPowerRelay, HIGH);
  pinMode(motorPowerRelay, OUTPUT);
  digitalWrite(motorPowerRelay, HIGH);

  //these will be the wires activating a battery of relays feeding the LED arrays

  for (byte ii = 0; ii < numLEDs; ii++) pinMode(ledPin[ii], OUTPUT);

  //this sets up the two UI controlled systems in a default starting state so that an LED is lit and the servo is positioned properly in case the system was previously shut off
  //while the servo was mid move

  diverter.write(diverterLEFT);
  lightSomeLED();

  //Serial.println("end of setup");
  //delay(5000);
}

void loop() {

  //this will take the current reading of millis and pass it as a placeholder and then run through the functions which are where the logic lies.

  //Serial.println("loop");

  currentMillis = millis();

  //Serial.println("currentMillis");

  switchLightMode();

  //Serial.println("lightMode");

  switchPathMode();

  //Serial.println("pathMode");

  changeLED();

  //Serial.println("ledChange");

  changePath();

  //Serial.println("pathChange");
}

# define PRESST LOW

void switchLightMode() {
  static bool lastButton = false;
  static unsigned long lastTime;

  bool thisButton = digitalRead(lightMode) == PRESST;

  if (currentMillis - lastTime > debounceTime) {
    if (lastButton != thisButton) {
      if (thisButton) {
        lightAuto = !lightAuto;

        Serial.print("light Mode ");
        Serial.println(lightAuto ? "automatic" : "manual");
      }

      lastTime = currentMillis;
      lastButton = thisButton;
    }
  }
}

void switchPathMode() {
  static bool lastButton = false;
  static unsigned long lastTime;

  bool thisButton = digitalRead(pathMode) == PRESST;

  if (currentMillis - lastTime > debounceTime) {
    if (lastButton != thisButton) {
      if (thisButton) {
        pathAuto = !pathAuto;

        Serial.print("path Mode ");
        Serial.println(pathAuto ? "automatic" : "manual");
      }

      lastTime = currentMillis;
      lastButton = thisButton;
    }
  }
}

int lightSomeLED()
{
  static int nowShowing = -1;   // not for long
  int theLED;

// just can't use the same LED! Pick a new one.
  do
    theLED = random(0, numLEDs);
  while (theLED == nowShowing);

  nowShowing = theLED;

  for (byte ii = 0; ii < numLEDs; ii++) {
    if (theLED == ii) digitalWrite(ledPin[theLED], HIGH);
    else digitalWrite(ledPin[ii], LOW);
  }

  return theLED;
}

void changeLED() {
  static unsigned long lastTime;

  if (lightAuto == true) {
    //Serial.println("lightAuto == true");
    if (currentMillis - lightSwitched >= lightRate) {
      lightSomeLED();
      lightSwitched = currentMillis;
    }
  } else {
    if (currentMillis - lastTime > keyRepeat) {
      if (digitalRead(lightToggle) == LOW) {
        int theLED = lightSomeLED();
        lastTime = currentMillis;
        Serial.print("light switched on ");
        Serial.println(ledTags[theLED]);
      }
    }
  }
}

void changePath() {
  static unsigned long lastTime;
  static unsigned long lastManualTime;

  if (pathAuto == true) {
    if (currentMillis - lastTime > pathRate) {
      if (pathA == true) {
        diverter.write(diverterLEFT);
        pathA = false;
        pathSwitched = currentMillis;
        Serial.print("LEFT ");
        Serial.println(diverter.read());
      } else {
        diverter.write(diverterRIGHT);
        pathA = true;
        pathSwitched = currentMillis;
        Serial.print("RIGHT ");
        Serial.println(diverter.read());
      }
      lastTime = currentMillis;
    }
  }
  else {
    if (currentMillis - lastManualTime > keyRepeat) {
      if (digitalRead(pathToggle) == LOW) {
        if (pathA) {
          diverter.write(diverterRIGHT);
          Serial.println("pathA is true");
        }
        else {
          diverter.write(diverterLEFT);
          Serial.println("pathA is not true");
        }

        pathA = !pathA;
        lastManualTime = currentMillis;
      }
    }
  }
}

HTH

a7

@thegreatness - you may not like the repeated random LED.

Someone who shall not be named got on my case about the way I avoided selecting the LED that was already. In theory it could take time comparable to forever. Which is something to be avoided.

In the simple case of picking a new LED that is not the one currently illuminated, we can use modular arithmetic and place them in a cycle, then just go a random number of steps through the cycle. If we limit it so it cannot reach the currently selected LED we done.

int lightSomeLED()
{
  static int nowShowing = -1;   // not for long
  int theLED;

  theLED = random(1, numLEDs);  // advance 1 to numLEDs - 1 steps.
  theLED = nowShowing + theLED;

  if (theLED >= numLEDs) theLED -= numLEDs;

  for (byte ii = 0; ii < numLEDs; ii++) {
    if (theLED == ii) digitalWrite(ledPin[theLED], HIGH);
    else digitalWrite(ledPin[ii], LOW);
  }

  nowShowing = theLED;
  return theLED;
}

Now she must get off my case. Her idea anyway, so.

a7

Thanks man, I'll circle back and take a look at it when I have a minute; a few deadlines in the near future at the moment. I think programming is interesting so I always enjoy getting to practice it; I'm at best a novice, but would like to hit that intermediate level someday where I can earn a little side cash doing it. Figure it scratches much of the same itch as the types of video games I enjoy and it is infinitely more productive. Will take notes when I review!

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