Easy to apply demo-code activate / deactivate code-execution with a momentary push-button used as toggle-switch

Hi everybody,

from time to time I enjoy writing demo-codes for a certain functionality.

EDIT 04.06.2022

user @dlloyd has written a library with toggle-functionality
This means all details how to debounce the button and detecting state-change etc. are hided away from the user inside his library. This makes it easier to apply this functionality but harder to understand.

So in post #3 there is a version that makes use of the toogle.h-library.
The toogle.h-library can be installed from the library-manager inside the Arduino-IDE

In post #4 is a most stripped down version of this demo-code. Both versions have their advantages and disadvantages

This thread contains a demo-code which uses a momentary push-button as a toggle-switch
push activated
push again DE-activated
push again activated
push again DE-activated
etc. etc.

pushing the push-button toggles activation / de-activation of the execution of the function called "myAction()"

This means you simply replace the code inside function "myAction()" with your code that shall only be executed if activated through the push-button
stopping execution of function "myAction()" is done by the next push of the same push-button

The code is well organised in functions. Where each function is a SUB-program doing a senseful unit of things. Splitting the code in this organised way makes function loop super-short

void loop () {
  activationMode = GetToggleSwitchState(); // must be executed all the time
  execute_if_Active(activationMode);       // function that does what its name says
}

The function GetToggleSwitchState() must be executed very often to keep the reaction to the buttonpresses responsive.

Function execute_if_Active() does what its name says

This code is dedicated to newcomers. I wrote it with the intention to be easy to understand. Though if you read this after just three hours of learning programming from zero it is yet challenging.

I'm interested in improving the easyness of understanding even more.
So if you have any questions just post them.

/* explanation of the most important details:

  realising a functionality where using a momentary push-button
  acts as a toogle-switch
  push       => activated  push again => DE-activated
  push again => activated  push again => DE-activated
  etc. etc. ...
  This needs quite some code. This code is well organised in MULTIPLE functions
  where each function is a senseful SUB-program

  This is the reason why function loop looks super-short:

  void loop () {
    activationMode = GetToggleSwitchState(); // must be executed all the time
    execute_if_Active(activationMode);       // function that does what its name says
  }

  Huh that's all? No. Of course there is more (below function loop)

  If you just want to APPLY the functionality to your own code
  replace the code inside function    void my_Action()
  with your own code that shall only be  executed if you activated execution
  with the momentary push-button

  I recommend to RENAME the function my_Action() to a SELF-explaining name
  This will cause a compiler-error which shows you where you have to rename
  the function-call of this function too (this is inside function execute_if_Active
  If you want to understand how it works read and analyse the rest of the functions.

*/


#define ProjectName "Toggle Button demonstration"

// define IO-states for inputs with pull-up-resistors
// pull-up-resistors invert the logig
#define unPressed HIGH
#define pressed   LOW

const byte ToggleButtonPin = A0;

bool activationMode = false;
unsigned long myCounter = 0;
unsigned long Print_Timer;


void setup() {
  Serial.begin(115200); // adjust baudrate in the serial monitor to match the number
  Serial.println( F("Setup-Start") );
  printFileNameDateTime();

  pinMode (LED_BUILTIN, OUTPUT);  // used for indicating logging active or not
  digitalWrite(LED_BUILTIN, LOW);
  // wire button between IO-pin and GND
  // Pull-up-resistor inverts the logic
  // unpressed: IO-pin detects HIGH
  // pressed:   IO-Pin detects LOW
  pinMode(ToggleButtonPin, INPUT_PULLUP);
}


void loop () {
  // function GetToggleSwitchState() must be executed repeatedly all the time
  // to be able to detect if the momentary button is pressed
  activationMode = GetToggleSwitchState();
  execute_if_Active(activationMode);       // function that does what its name says
}


bool GetToggleSwitchState() {
  // "static" makes variables persistant over function calls
  static bool toggleState     = false;
  static bool lastToggleState = false;

  static byte buttonStateOld = unPressed;
  static unsigned long buttonScanStarted  =  0;
  unsigned long buttonDebounceTime = 50;
  unsigned long buttonDebounceTimer;

  byte buttonStateNew;

  if ( TimePeriodIsOver(buttonDebounceTimer, buttonDebounceTime) ) {
    // if more time than buttonDebounceTime has passed by
    // this means let pass by some time until
    // bouncing of the button is over
    buttonStateNew = digitalRead(ToggleButtonPin);

    if (buttonStateNew != buttonStateOld) {
      // if button-state has changed
      buttonStateOld = buttonStateNew;
      if (buttonStateNew == unPressed) {
        // if button is released
        toggleState = !toggleState; // toggle state-variable
      } // the attention-mark is the NOT operator
    }   // which simply inverts the boolean state
  }     // !true  = false   NOT true  is false
  //       !false = true    NOT false is true
  return toggleState;
}


void execute_if_Active(bool p_IsActivated) {
  if (p_IsActivated) {
    // insert code here that shall only be executed if activated
    my_Action();
  }
  
  // just for demonstration purposes
  printStateChangeToSerialMonitor(p_IsActivated);
}

// helper-function ignore at first
void printFileNameDateTime() {
  Serial.print( F("File   : ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("Date   : ") );
  Serial.println( F(__DATE__) );
  Serial.print( F("Project: ") );
  Serial.println( F(ProjectName) );
}

// ignore at first
// helper-function for easy to use non-blocking timing
boolean TimePeriodIsOver (unsigned long & expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - expireTime >= TimePeriod ) {
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}


// this is the function that is called
// inside function execute_if_Active()
void my_Action() {

  // as a dummy-Action print to the serial monitor but only once per second
  if ( TimePeriodIsOver(Print_Timer, 1000) ) {
    // if 1000 milliseconds have passed by
    myCounter++;
    Serial.println(myCounter);
    Serial.println("this function is a template");
    Serial.println("that demonstrates where to place");
    Serial.println("your code that shall only be executed");
    Serial.println("if execution is activated by the");
    Serial.println("push-button-toggle-switch functionality");
    Serial.println();
    Serial.println();
  }
}


// function used ONLY for demonstration purposes
void printStateChangeToSerialMonitor(boolean activationState) {
  static bool lastactivationState; // flag-variable to remember the state at last call

  if (activationState != lastactivationState) {
    // only if state of parameter activationState has CHANGED
    if (activationState == true) {
      Serial.println();
      Serial.println("start executing");
      Serial.println();
      digitalWrite(LED_BUILTIN, HIGH);
    }
    else { // not activated
      Serial.println();
      Serial.println("stopp executing");
      Serial.println();
      digitalWrite(LED_BUILTIN, LOW);
    }    
    lastactivationState = activationState; // update variable lastIsActivated
  }
}

Be the change you want to see in the world
best regards Stefan

1 Like

you are addressing many things in there:

  • offering tons of side stuff that is informative (file name, compilation time, project name) and could be useful as a tutorial per se but makes reading the core code way more complicated than it needs to be

  • handling a button with debounce. This should be its own tutorial and made into a class and use that class to keep the focus on the main purpose

  • a state machine making decisions based on the historical pressing of the button (is this the core of your tutorial?)

  • finally the conditional activity which is the core goal

So with this in mind and as I like the easyRun library and especially the button class there) as it's lean, I would have offered something like

#include <easyRun.h>
const byte buttonPin = 2;      //wire : pin---btn---GND
button toggleButton(buttonPin);

void action() {
  // do something
}

void handleActionIfNeeded() {
  static bool modeActivated = false;
  easyRun();
  if (toggleButton) { // we got a press
    modeActivated = !modeActivated; // toggle the current mode
    if (modeActivated) {
      Serial.println("ACTIVATED");
      // do other stuff upon mode change
    } else {
      Serial.println("STOPPED");
      // do other stuff upon mode change
    }
  }

  if (modeActivated) action();  // execute the action only if we are activated
}

void setup() {
  pinMode (LED_BUILTIN, OUTPUT);  // used for indicating logging active or not
  digitalWrite(LED_BUILTIN, LOW);
  Serial.begin(115200); Serial.println(F("\nReady. Press button to start or stop the action."));
}

void loop() {
  handleActionIfNeeded();
}

In my opinion, examples work best when they are short, tidy and focused. You could refer to your other tutorials to add more meat around this and prop a full code with all the bells and whistles as an extra final step.

1 Like

Here is the code-version that makes use of the toggle.h-library

/* explanation of the most important details:

  realising a functionality where using a momentary push-button
  acts as a toogle-switch
  push       => activated  push again => DE-activated
  push again => activated  push again => DE-activated
  etc. etc. ...
  This needs quite some code. This code is well organised in MULTIPLE functions
  where each function is a senseful SUB-program

  This is the reason why function loop looks super-short:

  void loop () {
    myButton.poll();

    latchedButtonState = myButton.toggle();
    execute_if_Active(latchedButtonState); // function that does what its name says
  }
  Huh that's all? No. Of course there is more (below function loop)

  If you just want to APPLY the functionality to your own code
  replace the code inside function    void my_Action()
  with your own code that shall only be  executed if you activated execution
  with the momentary push-button

  I recommend to RENAME the function my_Action() to a SELF-explaining name
  This will cause a compiler-error which shows you where you have to rename
  the function-call of this function too (this is inside function execute_if_Active
  If you want to understand how it works read and analyse the rest of the functions.

*/


#define ProjectName "Toggle.h-library demo start/stop executing-code on buttonpress"

const byte switchedON  = 1;
const byte switchedOFF = 0;

const byte myTestButton  =  4; // uses a button connected to IO-pin 4

// take Arduino-Uno / Mega Onboard LED
// as indicator to show status of latched switch
const byte OnBoardLedPin = LED_BUILTIN;

unsigned long myCounter = 0;
unsigned long myTimer;

byte latchedButtonState;


#include <Toggle.h> // this line adds the code inside the file Toggle.h that makes it work

Toggle myButton(myTestButton); // create object that uses IO-pin myTestButton


void setup() {
  Serial.begin(115200); // adjust baudrate in the serial monitor to match the number
  Serial.println( F("Setup-Start") );
  printFileNameDateTime();

  pinMode (OnBoardLedPin, OUTPUT);
  myButton.begin(myTestButton); // start the button-object
}


void loop () {
  // the function poll() must be called very often
  // to read in and update the actual state of the button
  myButton.poll();

  // the function toggle() returns the state of a SOFWTARE EMULATED latched button
  // after a first  press of the button the function returns a "1" like I'm in ON-mode
  // after the next press of the button the function returns a "0" like I'm in OFF-mode
  // after the next press of the button the function returns a "1" like I'm in ON-mode
  // etc. etc. etc. etc. etc. ....
  // this means that only AFTER releasing the button and then
  // pressing DOWN the button NEW
  // the value that function toggle() returns is CHANGED
  latchedButtonState = myButton.toggle();
  execute_if_Active(latchedButtonState); // function that does what its name says

  // switch onboard-LED to state of the latched switch to indicate ON/OFF
  digitalWrite(OnBoardLedPin, latchedButtonState);
}


// this is the function that is called
// inside function execute_if_Active()
void my_Action() {

  // to prevent the serial monitor from beeing flooded
  // with text the code below is only executed once every second
  if ( TimePeriodIsOver(myTimer, 1000) ) {
    // if 1000 milliseconds time have passed by
    myCounter++;
    Serial.println(myCounter);
    Serial.println("this function is a template");
    Serial.println("that demonstrates where to place");
    Serial.println("your code that shall only be executed");
    Serial.println("if execution is activated by the");
    Serial.println("push-button-toggle-switch functionality");
    Serial.println();
    Serial.println();
  }
}



void execute_if_Active(bool p_IsActivated) {

  printStateChangeToSerialMonitor();

  if (p_IsActivated) {
    // function my_Action is only executed if parameter p_IsActivated is true
    // this parameter "p_IsActivated" is set true or false
    // with each press of the button called "myTestButton"
    my_Action();
  }
}

// helper-function to visiulises in the serial monitor
// what the code does
void printStateChangeToSerialMonitor() {
  static bool lastSwitchState; // static make values persistant

  // check if state has CHANGED
  if (myButton.toggle() != lastSwitchState) {
    // only if state of switch has CHANGED

    lastSwitchState = myButton.toggle(); // update variable lastSwitchState

    if (myButton.toggle() == switchedON) {
      Serial.println();
      Serial.println("start executing");
      Serial.println();
    }
    else { // not activated
      Serial.println();
      Serial.println("stopp executing");
      Serial.println();
    }
  }
}


// helper-function ignore at first
void printFileNameDateTime() {
  Serial.print( F("File   : ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("Date   : ") );
  Serial.println( F(__DATE__) );
  Serial.print( F("Project: ") );
  Serial.println( F(ProjectName) );
}

// ignore at first
// helper-function for easy to use non-blocking timing
boolean TimePeriodIsOver (unsigned long & expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - expireTime >= TimePeriod ) {
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}

and here is the most stripped down version with almost no explanation for codebangers that like to waiste hours of time by always quick guessings not really understanding what they are doing

// for all users that are in a real hurry
// trying to throw together code as fast as possible
// This is the most stripped down version of
// making code stop / execute with a button-press

const byte myTestButton  =  4; // uses a button connected to IO-pin 4

// take Arduino-Uno / Mega Onboard LED
// as indicator to show status of latched switch
const byte OnBoardLedPin = LED_BUILTIN;

#include <Toggle.h> // this line adds the code inside the file Toggle.h that makes it work

Toggle myButton(myTestButton); // create object that uses IO-pin myTestButton


void setup() {
  Serial.begin(115200); // adjust baudrate in the serial monitor to match the number
  Serial.println( F("Setup-Start") );

  pinMode (OnBoardLedPin, OUTPUT);
  myButton.begin(myTestButton); // start the button-object
}


void loop () {
  myButton.poll();

  if(myButton.toggle() ) {
    Serial.println("switched ON");
    Serial.println("executing");    
  }
  else {
    Serial.println("just looping WITHOUT executing");    
  }

  digitalWrite(OnBoardLedPin, myButton.toggle() );
  delay(10);
}

// I'm pretty sure you will stumble over some hurdles 
// later on that you are unsatisfied with the function.
// UNsmart users will fiddle around for MULTIPLE hours 
// trying to make it work with quick GUESSINGS
// REALLY smart users will take time ONE hour to learn
// and UNDERSTAND the more advanced version

Still too many comments :wink:

Interesting topic and thanks for mentioning the Toggle Library! More new features, updated documentation and examples on GitHub to follow (as time permits).

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