Function over many cycles of loop()

You can use functions to organize your code. I can write a function to add two variables and call this function within the arduino main loop. Let's say you give the function two inputs a and b, and it returns an output c. I could also use the function several times within loop().

But what if you have a function which works over many cycles of the main loop()? Let's say I have a function to detect a double click event of a button. There will be many cycles of loop() required to detect the event. Therefore, all variables have to be declared as global variables because otherwise they are overwritten every time the function is called.

Now, for 1 button, I can do that. But what if I want to reuse the same code for 5 buttons? If I copy the whole code it will get messy soon. Is there a simple solution for that problem?

Hello
Either declare the variable as static per function or built a struc per button.
Have a nice day and enjoy coding in C++.

Yes, or use classes.

1 Like

Not true. Static variables in a function hold their values between iterations.

Thanks, did not know the static type. Then I can define all variables within the function and they will not be overwritten after one cycle.

But then I can still not use the same function twice in the loop(), just with other input arguments?

Hello t_guttata
My crystalball is inside the dish washer.
Post your sketch, well formated, with comments and in so called code tags "</>" to see how we can help.
Have a nice day and enjoy coding in C++.

that's why you were suggested to use a class. the function is "shared" (written once) but works on the variables defined for a specific instance. study any button library to get an understanding how this is done (for example OneButton from Matthias Hertel where he manages the state machine ➜ doc is here Arduino OneButton Library).

@ paulpaulson

It's a question regarding methodology, therefore it does not make sense if I post 100 lines of code. It's about the principle, not the specific code.

@ J-M-L

Thanks, will have a look.

Just a basic concept of using a class for the button to maintain the variable states for each button.

class button {
  private:
    byte pin = 0;
    unsigned long debounceTime = 0;
    unsigned long previousMillis = millis();

    int a = 10;
    int b = 20;
    int c = 0;

  public:
    button(const byte& a_pin, const unsigned long& a_debounceTime)
      : pin(a_pin)
      , debounceTime(a_debounceTime)
    {
      // nothing going on here yet...
    }

    bool pressed() {
      if (millis() - previousMillis >= debounceTime) {
        previousMillis = millis();
        return digitalRead(pin);
      }
      return false;
    }

    int doSomething() {
      return (c += a + b);
    }
};

button button1(2, 500);

void setup() {
  Serial.begin(115200);
}

void loop() {
  if (button1.pressed()) {
    Serial.print(F("Button 1 pressed:")); Serial.println(button1.doSomething());
  }
}

I write those functions as tasks. Tasks pass control info with variables.

My buttons get a handler task. My debounce simply updates a history byte twice per ms and that value is what triggers or not other functions. Multiple button/matrix code only checks 1 button per pass at almost 70k passes/second in examples... I have a loop counter task just to check on what my latest change did to loop speed.

Normally you learn to use millis() to time events and cause 2 or more leds to blink at random times first and then or soon after you learn simple State Machines to keep track of what's happening (process state) and run the suitable case to that state.

Doing multiple things at once, 101 complete.

Once you know that, the next step with State Machine example...

Links to loads of fundamental MCU know-how.

I was hoping it will be easier.
After several hours I finally managed to create my own 3-way button script. But since I have not used classes before I will have a hard time to create a piece of code which I can use for many buttons.

Here's another way to do it:

There are lots of answers to this, pick the one that you like best or invent your own.

You could think about an array[]] of buttons or structs.

Yes, but obviously at some point (pretty soon) there is no other way than OOP.

You can find plenty of information regarding OOP but I could only find a few examples which are good for beginners.

This is an exaggerated statement… you can do that with a non OOP approach.

For regular programming where all the lines of code are processed until the end, I never saw the point in learning OOP. I never had so many lines of code, it became necessary.

But if you program for arduino and you are always in the main loop, I see that the use of classes will simplify the code a lot.

In C or c++ you are always in main() or something called from main() (outside compiler instantiation at start). This is the case for the loop() function in arduino land.

Beginner-friendly version;

// renamed buttons because IDE 1.8.x doesn't like text separaters.
// add-a-sketch_buttons 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, May 6/2018 by GFS. Compiled on Arduino IDE 1.6.9.
/*  Button Debounce Example

  --- for this example connect a button between pin 7 and GND
  --- or stick a jumper in pin 7 and while holding the board steady
  --- tap the free end onto the grounded USB port box to press.
  --- then do the same for pin 6

  Yes I'm using a 16 bit micros timer to time fractions of millis as micros.
  The button reader only reads 1 button per call so as to not block void loop().
  Each button has a history byte that holds the last 8 reads with 256 possible
  states but only 4 of them being significant.
  0 is the button held down
  255 is the button left up
  127 is the buton transitioning from up to down, button just released.
  128 is the button transititioning from down to up, button just pressed.
  everything else is to be ignored as bounce.

  For multiple buttons on 1:1 pins the between-reads time is reduced and each
  pin is read in turn. This demo makes pin 0 be ON-RESET/OFF and pin 1 adjusts
  blink time.
*/

// multi_buttons vars
const byte buttons = 2;
byte buttonIndex, lastIndex; // only process 1 button every waitButtonTime micros
byte buttonPin[ buttons ] = { 6, 7 };
byte buttonHistory[ buttons ];
word markButtonTime;        // 16-bit micros timers
const word waitButtonTime = 250; // micros, 20 to 500 / more to fewer buttons.
// type word as micros can time across 65.535 millis before rollover, can be a few late

// added sketch task, on-off blinker vars
byte ledState, ledPin = 13; // use byte for small values, int cost 2 bytes
word startBlink, waitBlink; // 16 bit millis is good to time 65.535 seconds


void multiButtonsTask()
{ // read twice per milli, bits 0 to 6 all same sets the state
  if ( word( micros()) - markButtonTime >= waitButtonTime ) // read occaisoinally
  {
    buttonHistory[ buttonIndex ] <<= 1; // if you don't know <<= look it up in the Arduino Reference
    // keep a browser open to that page when you use the IDE.
    buttonHistory[ buttonIndex ] += digitalRead( buttonPin[ buttonIndex ] ); // read history streams through buttonHistory
    markButtonTime = micros(); // gets the low 16 bits of micros(), time to 60 ms + margin
  }

  // ++buttonIndex pre-increments buttonIndex before comparing to buttons
  if ( ++buttonIndex >= buttons )   buttonIndex = 0;
}

void OnOffBlinker() // only blinks if there's a wait time, can be switched on/off
{
  if ( waitBlink > 0 ) // this is the on/off switch
  {
    // word( millis()) gets the low 16 bits of the 32-bit millis() return.
    if ( word( millis()) - startBlink >= waitBlink ) // difference in time by subtracting start from end
    {
      ledState = !ledState;  // ! is NOT: not_0/true becomes 0/false else 0 becomes 1.
      digitalWrite( ledPin, ledState ); // the led changes state.
      startBlink += waitBlink; // next blink starts when it should, where diff > wait.
    }
  }
  else if ( ledState > 0 ) // waitBlink == 0 turns blinking off
  {
    digitalWrite( ledPin, ledState = LOW ); //  make sure the led is OFF
  } // yes, you can set a variable during calculation in C, the write here is LOW.
}


void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Button Debounce Example, free by GoForSmoke\n" ));
  Serial.println( F( "This code shows use of struct, union, bit fields and 16 bit timers." ));
  Serial.println( F( "It can work with many buttons, uses structs to hold button data." ));
  Serial.println( F( "Button data structs can be arrayed even for matrix arrangements." ));
  Serial.println( F( "\n-- for this example connect a button between pin 7 and GND" ));
  Serial.println( F( "--- or stick a jumper in pin 7 and while holding the board steady" ));
  Serial.println( F( "--- tap the free end onto the grounded USB port box to press." ));

  pinMode( ledPin, OUTPUT );

  for ( byte i = 0; i < buttons; i++ )
  {
    pinMode( buttonPin[ i ], INPUT_PULLUP );
    buttonHistory[ i ] = 255;
  }
};


void loop()
{
  multiButtonsTask();
  /*
    0 is the button held down
    255 is the button left up
    127 is the buton changing from up to down, button just released.
    128 is the button changing from down to up, button just pressed.
    everything else is to be ignored as bounce.
  */

  if ( buttonIndex != lastIndex )
  {
    lastIndex = buttonIndex;

    switch ( buttonHistory[ buttonIndex ] ) // buttonHistory does not change as fast as this runs
    {
      case 128 : // pin is HIGH in bit 7, LOW for 7 reads, up to down detected
        buttonHistory[ buttonIndex ] = 0; // change detected, make it into no change now
        Serial.print( F( "button " ));
        Serial.print( buttonIndex );
        Serial.print( F( "  press detected     " ));
        Serial.println( millis());
        if ( buttonIndex == 0 )
        {
          if ( waitBlink == 0 ) // toggle action tied to button press
          {
            waitBlink = 500; // makes the blinking start
            startBlink = millis(); // gets the low 16 bits
          }
          else // press button 2 changes the blink rate
          {
            waitBlink = 0; // makes the blinking stop
          }
        }
        else
        {
          if (( waitBlink -= 100 ) < 100 ) // setting a var in an expression is okay C
          {
            waitBlink = 1000;
          }
        }
        break;
      case 127 : // pin is LOW in bit 7, HIGH for 7 reads, down to up detected
        buttonHistory[ buttonIndex ] = 255; // change detected, make it into no change now
        Serial.print( F( "button " ));
        Serial.print( buttonIndex );
        Serial.print( F( "  release detected   " ));
        Serial.println( millis());
        break;
    }
  }

  OnOffBlinker();
}

Add the loop counter task and defines from the code below to the code above and get average loop speed. If you make a change, compare before and after speeds.

// loopCounter.ino 
// add-a-sketch_loop_counter 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, Apr 29/2018 by GFS. Compiled on Arduino IDE 1.6.9.
// This sketch counts times that loop has run each second and prints it.
// It uses the void LoopCounter() function that does not block other code.

#define microsInOneSecond 1000000UL

void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Loop Counter, free by GoForSmoke\n" ));
  Serial.println( F( "This sketch counts times that loop has run each second and prints it." ));
}

void LoopCounter() // tells the average response speed of void loop()
{ // inside a function, static variables keep their value from run to run
  static unsigned long count, countStartMicros; // only this function sees these

  count++; // adds 1 to count after any use in an expression, here it just adds 1.
  if ( micros() - countStartMicros >= microsInOneSecond ) // 1 second
  {
    countStartMicros += microsInOneSecond; // for a regular second
    Serial.println( count ); // 32-bit binary into decimal text = many micros
    count = 0; // don't forget to reset the counter 
  }
}

void loop()  // runs over and over, see how often with LoopCounter()
{
  LoopCounter(); // the function runs as a task, the optimizer will inline the code.
}

This is a function run over many cycles that replaces delays with non-blocking waits.
Adding a stop/go button that works all the time is possible with code that does not block execution.

// undelay.c
// add-a-sketch_un-delay 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, Apr 30/18 by GFS. Compiled on Arduino IDE 1.6.9.
// This sketch shows a general method to get rid of delays in code.
// You could upgrade code with delays to work with add-a-sketch.

#include <avr/io.h>
#include "Arduino.h"

const byte ledPin = 13;
unsigned long delayStart, delayWait;

void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Un-Delay Example, free by GoForSmoke\n" ));
  Serial.println( F( "This sketch shows how to get rid of delays in code.\n" ));

  pinMode( ledPin, OUTPUT );
};


/* The section of the original sketch with delays:
 * 
 * digitalWrite( ledPin, HIGH );   --  0
 * delay( 500 );
 * digitalWrite( ledPin, LOW );    --  1
 * delay( 250 );
 * digitalWrite( ledPin, HIGH );   --  2
 * delay( 250 );
 * digitalWrite( ledPin, LOW );    --  3
 * delay( 250 );
 * digitalWrite( ledPin, HIGH );   --  4
 * delay( 1000 );
 * digitalWrite( ledPin, LOW );    --  5
 * delay( 1000 );
 */

byte blinkStep; // state tracking for BlinkPattern() below

void BlinkPattern()
{
  // This one-shot timer replaces every delay() removed in one spot.  
  // start of one-shot timer
  if ( delayWait > 0 ) // one-shot timer only runs when set
  {
    if ( millis() - delayStart < delayWait )
    {
      return; // instead of blocking, the undelayed function returns
    }
    else
    {
      delayWait = 0; // time's up! turn off the timer and run the blinkStep case
    }
  }
  // end of one-shot timer

  // here each case has a timed wait but cases could change Step on pin or serial events.
  switch( blinkStep )  // runs the case numbered in blinkStep
  {
    case 0 :
    digitalWrite( ledPin, HIGH );
    Serial.println( F( "Case 0 doing something unspecified here at " ));
    Serial.println( delayStart = millis()); // able to set a var to a value I pass to function
    delayWait = 500; // for the next half second, this function will return on entry.
    blinkStep = 1;   // when the switch-case runs again it will be case 1 that runs
    break; // exit switch-case

    case 1 :
    digitalWrite( ledPin, LOW );
    Serial.println( F( "Case 1 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 250;
    blinkStep = 2;
    break;

    case 2 :
    digitalWrite( ledPin, HIGH );
    Serial.println( F( "Case 2 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 250;
    blinkStep = 3;
    break;

    case 3 :
    digitalWrite( ledPin, LOW );
    Serial.println( F( "Case 3 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 250;
    blinkStep = 4;
    break;

    case 4 :
    digitalWrite( ledPin, HIGH );
    Serial.println( F( "Case 4 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 1000;
    blinkStep = 5;
    break;

    case 5 :
    digitalWrite( ledPin, LOW );
    Serial.print( F( "Case 5 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 1000;
    blinkStep = 0;
    break;
  }
}


void loop()  // runs over and over, see how often
{            
  BlinkPattern();
}

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