Creating and using functions without parameters vs. variables with instructions

This is an example of a program I wrote that detects when a push-button is pressed, and lights up an LED while it is pressed. I created the 'buttonState' function in the universal scope to use just as a shortcut to allow me to not have to write 'digitalRead(BUTTON_PIN)' in the 'void loop'. This worked perfectly well in this specific program, but not in one I will show afterward. Here is the first program...

# define BUTTON_PIN 2
# define LED_PIN 11

int buttonState() {
  return digitalRead(BUTTON_PIN);
}

void setup() {

  Serial.begin(9600);

  pinMode(BUTTON_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);

}

void loop() {

  if (buttonState() == HIGH) {
    Serial.println("Button is pressed!");
    digitalWrite(LED_PIN, HIGH);
  }
  else {
    Serial.println("Button is not pressed.");
    digitalWrite(LED_PIN, LOW);
  }

  delay(100);

}

Next, I wanted to create a program with three LED lights, where every time the button was clicked, the next light would turn on and the previous one would turn off. However, if the button was held down, then the lights would not change. They would only change once the button was lifted and then pressed again. Using the logic from the 'StateChangeDetection' example program in the Arduino IDE, I wrote this program...

# define BUTTON_PIN 2
# define LED_RED 9
# define LED_GREEN 10
# define LED_BLUE 11

int prevButtonState = 0;
int ledIndex = 0;
int delayTime = 50;

int ledArray[3] = {LED_RED, LED_GREEN, LED_BLUE};

int buttonState() {
  return digitalRead(BUTTON_PIN);
}

void ledState(int index, int state) {
  digitalWrite(ledArray[index], state);
}

void setup() {

  pinMode(BUTTON_PIN, INPUT);

  for (int led = 0; led < 3; led++) {
    pinMode(ledArray[led], OUTPUT);
  }

}

void loop() {

  if (buttonState() != prevButtonState) {
    if (buttonState() == HIGH) {
      ledState(ledIndex, HIGH);

      if (ledIndex == 0) {
        ledState(2, LOW);
      }
      else {
        ledState(ledIndex - 1, LOW);
      }

      if (ledIndex == 2) {
        ledIndex = 0;
      }
      else {
        ledIndex++;
      }

    }
    delay(delayTime);
  }
  prevButtonState = buttonState();

}

Unfortunately, this did not have the desired effect. It almost functioned, but was extremely unreliable, with the LED lights not always changing when they were supposed to. So, I decided to use the method from the 'StateChangeDetection' example program I mentioned earlier, in order to properly read the state of the push button. I modified the previous program and created this one, which had the desired effect and worked flawlessly...

# define BUTTON_PIN 2
# define LED_RED 9
# define LED_GREEN 10
# define LED_BLUE 11

int buttonState = 0;
int prevButtonState = 0;
int ledIndex = 0;
int delayTime = 50;

int ledArray[3] = {LED_RED, LED_GREEN, LED_BLUE};

void ledState(int index, int state) {
  digitalWrite(ledArray[index], state);
}

void setup() {

  pinMode(BUTTON_PIN, INPUT);

  for (int led = 0; led < 3; led++) {
    pinMode(ledArray[led], OUTPUT);
  }

}

void loop() {

  buttonState = digitalRead(BUTTON_PIN);

  if (buttonState != prevButtonState) {
    if (buttonState == HIGH) {
      ledState(ledIndex, HIGH);

      if (ledIndex == 0) {
        ledState(2, LOW);
      }
      else {
        ledState(ledIndex - 1, LOW);
      }

      if (ledIndex == 2) {
        ledIndex = 0;
      }
      else {
        ledIndex++;
      }

    }
    delay(delayTime);
  }
  prevButtonState = buttonState;

}

The difference between the two examples is that in the first, I created a function called 'buttonState' to read the state of the push-button, while in the second, I created a variable called 'buttonState', assigned it a value of zero, and reassigned it a value of 'digitalRead(BUTTON_PIN)' at the beginning of the 'void loop' (at least this is what I think is going on. I am still a beginner so I'm not sure if my explanation of the logic is accurate). So, my first question is, why does the function method not work while the variable method works flawlessly? What is it about the road map that does not allow the function I created to work with the three LED project, even though it worked just fine in the first project with just one LED?

My second question is regarding the nature of the second method used in my three LED project (the method that succeeded, with the variable 'buttonState'). How exactly does this variable work? Why do I assign it the value of 0 in the universal scope? I fiddled around with it, and this method also seemed to work...

# define BUTTON_PIN 2
# define LED_RED 9
# define LED_GREEN 10
# define LED_BLUE 11

int prevButtonState = 0;
int ledIndex = 0;
int delayTime = 50;

int ledArray[3] = {LED_RED, LED_GREEN, LED_BLUE};

void ledState(int index, int state) {
  digitalWrite(ledArray[index], state);
}

void setup() {

  pinMode(BUTTON_PIN, INPUT);

  for (int led = 0; led < 3; led++) {
    pinMode(ledArray[led], OUTPUT);
  }

}

void loop() {

  int buttonState = digitalRead(BUTTON_PIN);

  if (buttonState != prevButtonState) {
    if (buttonState == HIGH) {
      ledState(ledIndex, HIGH);

      if (ledIndex == 0) {
        ledState(2, LOW);
      }
      else {
        ledState(ledIndex - 1, LOW);
      }

      if (ledIndex == 2) {
        ledIndex = 0;
      }
      else {
        ledIndex++;
      }

    }
    delay(delayTime);
  }
  prevButtonState = buttonState;

}

In this method, I did not declare the 'buttonState' variable in the universal scope and assign it a value of zero. Instead, I did everything at the start of the 'void loop', declaring the variable for the first time, while specifying how to read the state of the push-button. This method works just the same as the previous one, so why did the 'StateChangeDetection' example program use the first method, with declaring the variable at the beginning and assigning it a value of 0? I kept fiddling around with this, and tried putting the line from the start of the 'void loop' in the universal scope, like this...

# define BUTTON_PIN 2
# define LED_RED 9
# define LED_GREEN 10
# define LED_BLUE 11

int buttonState = digitalRead(BUTTON_PIN);
int prevButtonState = 0;
int ledIndex = 0;
int delayTime = 50;

int ledArray[3] = {LED_RED, LED_GREEN, LED_BLUE};

void ledState(int index, int state) {
  digitalWrite(ledArray[index], state);
}

void setup() {

  pinMode(BUTTON_PIN, INPUT);

  for (int led = 0; led < 3; led++) {
    pinMode(ledArray[led], OUTPUT);
  }

}

void loop() {

  if (buttonState != prevButtonState) {
    if (buttonState == HIGH) {
      ledState(ledIndex, HIGH);

      if (ledIndex == 0) {
        ledState(2, LOW);
      }
      else {
        ledState(ledIndex - 1, LOW);
      }

      if (ledIndex == 2) {
        ledIndex = 0;
      }
      else {
        ledIndex++;
      }

    }
    delay(delayTime);
  }
  prevButtonState = buttonState;

}

This method did not work. In this method, I simply moved declaration and instructions of the variable from the void loop into the universal scope. I really do not understand why there is a difference between these two methods! Shouldn't variables in the universal scope function the same in any scope within it, such as the 'void setup' or 'void loop'? Why don't the instructions within the variable carry over into the 'void loop'?

My overall point is this. I originally created the 'buttonState' function because I did not know that variables could carry instructions. If I wanted to use an input pin to read a value, I thought I would need to create an entire function to do so. However, this is clearly not the case. Any answers to any of my numerous specific questions within this post would be greatly appreciated! I think the main issue is that I do not understand how variables containing instructions work, especially because they behave differently depending on the scope they are created and/or used in. Any resources regarding this type of variable and how to use it would also be appreciated tremendously! Thank you so much for reading through this long post, and I hope some of you can offer me some help!

Edit: Here is an image of my wiring, for anyone wondering!

Always show us a good schematic of your proposed circuit.

Show us a good image of your ‘actual’ wiring.

1 Like

How do you have the button connected to the Arduino?

The best approach is use a pin mode of INPUT_PULLUP and the button pulls the pin LOW (connects it to ground). If you are not using pull-up/pulldown resistors on the button pin then the value of digitalRead can return HIGH or LOW when not connected, and this can change randomly.

1 Like

I am using a pulldown resistor! I just edited the original post with an image of the wiring.

It should be connected to the pin of the Arduino... the white wire.

Here is a picture from the Arduino website of a pulldown resistor. I believe mine should work just the same

This is not a good way to do it, and relies wholly on the switch being oriented correctly... if you have it out by 90 degrees then all that resistor is doing is providing a short across the power supply lines. It is VERY easy to make that mistake, which is why you should connect the pulldown to the other side of the switch (same pin that goes to the Arduino).

For the same reason you should always connect the button using diagonal pins.

It's better to connect the button pin (pin mode INPUT_PULLUP) directly through the button to GND. I stick a jumper in the pin hole and ground the free end on the USB port box (grounded).

A pin moded as INPUT_PULLUP sources 5V as through 20K to 50K ohms, very weak current, safe to ground. Button up (open) reads HIGH, Button down (closed) reads LOW if connected to GND.

When you replace that delay() with timers, you will encounter button bounce which happens over a short period. You can delay for 10, 20, 50 ms, halting execution for 16000 cycles per ms or use a timer to watch the button and handle the leds more than 50 times per ms.

Good debounce detects state change (can get many during 2 ms bounce if you read real fast) that stays changed (stable state) for longer than bounces. Caveat: Crap buttons can bounce longer, might need a longer stable period.

So you write a button handler that updates a global button status variable when a debounced state change is detected... the handler ALWAYS runs when void loop runs.

My button status is 1 byte's bits as 8 reads over the appx last 4 ms, ~2 reads per ms.
All 1's (255 decimal) is button up, all 0's (0 decimal) is button down.
Binary 10000000 (128 dec) is the button was up and down for my stable state change.
Binary 01111111 (127 dec) is the button was down and up for my stable state change.
Everything else is unstable. All functions in the sketch can read button status.

Without delays you can have many fast-detect buttons and lots of leds. Just saying.

In your projects, wire your switches similar to the way S3 is wired below:

image


Try these changes to your sketch, wire your switch similar as S3 is wired:

#define LEDon                              HIGH
#define LEDoff                             LOW

#define PUSHED                             LOW
#define RELEASED                           HIGH

const byte BUTTON_PIN                    = 2;
const byte LED_RED                       = 9;
const byte LED_GREEN                     = 10;
const byte LED_BLUE                      = 11;
const byte heartbeatLED                  = 13;

byte buttonState;
byte prevButtonState;
byte ledIndex;

//                    0        1          2
int ledArray[3] = {LED_RED, LED_GREEN, LED_BLUE};

//timing stuff
unsigned long heartbeatMillis;
unsigned long switchMillis;


//                                       s e t u p ( )
//********************************************^************************************************
//
void setup()
{
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  for (int led = 0; led < 3; led++)
  {
    pinMode(ledArray[led], OUTPUT);
  }

  pinMode(heartbeatLED, OUTPUT);

} //END of   setup()


//                                        l o o p ( )
//********************************************^************************************************
//
void loop()
{
  //*********************************
  //this LED acts as a system heartbeat indication
  //is it time to toggle the heartbeatLED ?
  if (millis() - heartbeatMillis >= 500ul)
  {
    //restart this TIMER
    heartbeatMillis = millis();

    //toggle the heartbeatLED
    digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));
  }

  //*********************************
  //checking the switches every 50ms effectively acts as switch debouncing
  //is it time to check the switches ?
  if (millis() - switchMillis >= 50ul)
  {
    //restart this TIMER
    switchMillis = millis();

    checkSwitches();
  }

} //END of   loop()


//                              c h e c k S w i t c h e s ( )
//********************************************^************************************************
//
void checkSwitches()
{
  byte currentState;

  //*********************************                               B U T T O N _ P I N
  currentState = digitalRead(BUTTON_PIN);

  //has this switch changed state ?
  if (prevButtonState != currentState)
  {
    //update to the new switch state
    prevButtonState = currentState;

    //******************
    //has the switch been pushed ?
    if (currentState == PUSHED)
    {
      digitalWrite(ledArray[ledIndex], LEDon);

      //***************************
      //turning off the previous LED
      if (ledIndex == 0)
      {
        digitalWrite(ledArray[2], LEDoff);
      }

      else
      {
        digitalWrite(ledArray[ledIndex - 1], LEDoff);
      }

      //next LED
      ledIndex++;

      if (ledIndex > 2)
      {
        ledIndex = 0;
      }
    }
    
  } //END of  BUTTON_PIN 


  //************************************
  // Add other switches here
  //************************************

} //END of   checkSwitches()

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