Millis with multiple buttons

I have managed to write and use an 8 button /LED program using millis, but it is very longwinded with a seperate 6 lines of code in setup and another 9 lines in void for each button/LED.

Is there a way this could be simplified/neatened that I am missing?
I have seen uses of a kind but have not thought it would address the need for maybe all or most buttons being pressed in a few seconds with an LED on period of 10 seconds and then another being pressed .

post your code

look this over as an example for handling multiple buttons

// check multiple buttons and toggle LEDs

enum { Off = HIGH, On = LOW };

byte pinsLed [] = { 10, 11, 12 };
byte pinsBut [] = { A1, A2, A3 };
#define N_BUT   sizeof(pinsBut)

byte butState [N_BUT];

// -----------------------------------------------------------------------------
int
chkButtons ()
{
    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        byte but = digitalRead (pinsBut [n]);

        if (butState [n] != but)  {
            butState [n] = but;

            delay (10);     // debounce

            if (On == but)
                return n;
        }
    }
    return -1;
}

// -----------------------------------------------------------------------------
void
loop ()
{
    switch (chkButtons ())  {
    case 2:
        digitalWrite (pinsLed [2], ! digitalRead (pinsLed [2]));
        break;

    case 1:
        digitalWrite (pinsLed [1], ! digitalRead (pinsLed [1]));
        break;

    case 0:
        digitalWrite (pinsLed [0], ! digitalRead (pinsLed [0]));
        break;
    }
}

// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        pinMode (pinsBut [n], INPUT_PULLUP);
        butState [n] = digitalRead (pinsBut [n]);
    }

    for (unsigned n = 0; n < sizeof(pinsLed); n++)  {
        digitalWrite (pinsLed [n], Off);
        pinMode      (pinsLed [n], OUTPUT);
    }
}
1 Like

orif needed, take it further with an array of struct to hold any related stuff.

Hello 2rla

Welcome to the world's best Arduino forum ever.

Yes, do you have experience with programming in CPP?

My recommendation is to use a structured array.

This structured array contains all information about the pins and a service to debounce the pins and a service to detect state changes.
The results of the state change detection can be further processed with a switch/case statement to control a led as mentionend.

Have a nice day and enjoy coding in C++.

Thank you Paul for your welcome and comment.
No I have not real C++ knowledge just Basic and a little Sweptspeed, which shows how old I am!
I will research structured arrays and see how I get on.

1 Like

Just mentioning a few keywords is a rather short help.

It leaves open to search for a good tutorial which is pretty hard to find between 80% trash and 15% not so good tutorials.

How should a newcomer judge if a tutorial is explaining good?

There are a lot of different ways to handle buttons.
A pretty comfortable one is to use a library.

One example where the author is online here in the forum is the library MobaTools.

You can install the MobaTools from the library-manager of the Arduino-IDE

Here is an example code that shows the possabilities of the MobaTools library
It is taken from the MobaTools examples

// Show Events of buttons in serial monitor
#define MAX8BUTTONS
#include <MobaTools.h>
// define pin numbers
const byte buttonPin [] = { A0, A1, A2, A3 };
const byte buttonCnt = sizeof(buttonPin);
char txtBuf[50];

//this instance is without a callback ( pins are read directly )
// double click time is 400ms
MoToButtons Buttons( buttonPin, buttonCnt, 30, 500, 400 );

void printPressedEvent( uint8_t buttonNbr ) {
  if ( Buttons.pressed(buttonNbr) ) {
    sprintf( txtBuf, "button %d pressed", buttonNbr );
    Serial.println(txtBuf);
  }
}

void printReleasedEvent( uint8_t buttonNbr ) {
  if ( Buttons.released(buttonNbr) ) {
    sprintf( txtBuf, "button %d released", buttonNbr );
    Serial.println(txtBuf);
  }
}

void printlongpressEvent( uint8_t buttonNbr ) {
if ( Buttons.longPress(buttonNbr) ) {
    sprintf( txtBuf, "button %d pressed long", buttonNbr );
    Serial.println(txtBuf);
  }
}

void printshortpressEvent( uint8_t buttonNbr ) {
  if ( Buttons.shortPress(buttonNbr) ) {
    sprintf( txtBuf, "button %d pressed short", buttonNbr );
    Serial.println(txtBuf);
  }
}

void printClickedEvent( uint8_t buttonNbr ) {
  switch ( Buttons.clicked(buttonNbr) ) {
    case NOCLICK:
      ; // do nothing
      break;
    case DOUBLECLICK:
      sprintf( txtBuf, "button %d double clicked", buttonNbr );
      Serial.println(txtBuf);
      break;
    case SINGLECLICK:
      sprintf( txtBuf, "button %d single clicked", buttonNbr );
      Serial.println(txtBuf);
      break;
  }
}

void setup()
{
  Serial.begin(115200);
  while(!Serial);       // only for Leonardo/Micro ( mega32u4 based boards 
  
  for (int i = 0; i < buttonCnt; i++)  {    
    // buttons must switch to Gnc
    pinMode(buttonPin[i], INPUT_PULLUP); 
  }
  Serial.println("Starting loop");
  sprintf( txtBuf, "max. managable buttons: %d", sizeof(button_t)*8 );
  Serial.println( txtBuf );
  //Buttons.forceChanged();
}

void loop() {
  //--------------------------------------------------------
  // read and process buttons
  Buttons.processButtons();
  // 
  //--------------------------------------------------------
  // print state of buttons if at least one changed
  if ( Buttons.changed() ) {
    sprintf( txtBuf, "------------ State: %d %d %d %d - ", Buttons.state(0), Buttons.state(1), Buttons.state(2), Buttons.state(3) );
    Serial.print( txtBuf ); Serial.println( Buttons.allStates(),BIN );
  }
  // print to serial monitor if an event happens ( pressing or releasing )
  // Button 0 checked for all events
  printPressedEvent(0 );
  printReleasedEvent( 0 );
  printshortpressEvent( 0 );
  printlongpressEvent( 0 );
  printClickedEvent( 0 );

  // Button 1 checked for releasing
  printReleasedEvent( 1 );
  
  // Button 2 checked for short/long press
  printshortpressEvent( 2 );
  printlongpressEvent( 2 );

  // Button 3 checked for double Click
  printClickedEvent( 3 );
}

best regards Stefan

Thank you Stephan,
What you say is so true about having to find a direction which works, but without assurance it's going to be any better.

I have looked at #JC button, but have not got my head around how to fit my wants into that frame.
As well as buttons to dedicated leds I need tones and relays working under other requirements with the operation of the buttons.

As I posted I know my method is long winded and over sized, over worded but it works, most of the time although there are erratic events at times.

I will have a look at what you have shown and the Mobar offering.
Cheers

Still the best thing is to post your complete sketch. Regardless of what it looks like. If others can see your code they will much better understand what you need.

so please post your complete sketch

best regards Stefan

why haven't you posted you're code?

After so many requests I attach my code, don't laugh too much !

//Global Variables   //CLEANED UP
// NOT LUMEN CONTROLLED
// ADDed BACK DOOR FOR NIGHT LIGHTING
// blocked beam addition
//working version

const byte GATE1 = 2;
const byte BEAM = 3;
// const byte = 4;        // not working
const byte GATE2 = 5;
const byte spare = 6;
const int  beam = 3;
int buzzer  = 7;
const byte RED_led1 = 8;
const byte YELLOW_led2 = 9;
const byte LED3 = 10;
const byte BLUE_led4 = 11;
const int  Relay1 = 12;

int beamState = 0;
//int lastButtonState;

unsigned long gate1PushedMillis;
// when button was released
unsigned long RED_led1TurnedOnAt;
// when led was turned on
unsigned long turn1OnDelay = 10;
// wait to turn on LED
unsigned long turn1OffDelay = 15000;
// turn off LED after this time
bool RED_led1Ready = false;
// flag for when button is let go
bool RED_led1State = false;
// for LED is on or not.

unsigned long gate2PushedMillis;
// when button was released
unsigned long YELLOW_led2TurnedOnAt;
// when led was turned on
unsigned long turn2OnDelay = 10;
// wait to turn on LED
unsigned long turn2OffDelay = 15000;
// turn off LED after this time
bool YELLOW_led2Ready = false;
// flag for when button is let go
bool YELLOW_led2State = false;
// for LED is on or not.

unsigned long button4PushedMillis;
// when button was released
unsigned long BLUE_led4TurnedOnAt;
// when led was turned on
unsigned long turn4OnDelay = 10;
// wait to turn on LED
unsigned long turn4OffDelay = 15000;
// turn off LED after this time
bool BLUE_led4Ready = false;
// flag for when button is let go
bool BLUE_led4State = false;
// for LED is on or not.

unsigned long beamPushedMillis;
// when button was released
unsigned long Relay1TurnedOnAt;
// when relay was turned on
unsigned long turn5OnDelay = 10;
// wait to turn on relay
unsigned long turn5OffDelay = 50000;
// turn off relay after this time
bool Relay1Ready = false;
// flag for when beam contact is made
bool Relay1State = false;
// for relay is on or not.

void setup() {

  Serial.begin(9600);
  pinMode(GATE1,  INPUT_PULLUP);
  pinMode(BEAM,   INPUT_PULLUP);
  pinMode(GATE2,  INPUT_PULLUP);
  pinMode(spare,  INPUT_PULLUP);
  pinMode(beam,   INPUT_PULLUP);
  pinMode(RED_led1,   OUTPUT);
  pinMode(YELLOW_led2,   OUTPUT);
  pinMode(LED3,   OUTPUT);
  pinMode(BLUE_led4,   OUTPUT);
  pinMode(Relay1,   OUTPUT);
  pinMode(buzzer, OUTPUT);
  digitalWrite(RED_led1, LOW);
  digitalWrite(YELLOW_led2, LOW);
  digitalWrite(LED3, LOW);
  digitalWrite(BLUE_led4, LOW);
  digitalWrite(Relay1, LOW);
}

void loop() {

  int vred = analogRead(A0);  //vred = 0..1023
  int beamState = digitalRead(beam);

  Serial.println (vred);
  delay(10);

  // get the time at the start of this loop()
  unsigned long currentMillis = millis();


  if ( digitalRead(BEAM) == LOW) {

    beamPushedMillis = currentMillis;

    if ( vred >70 ) {
  goto bailout;
} else {
  
    Relay1Ready = true;
  }

  // make sure this code isn't checked until after button has been let go
  if (Relay1Ready ) {
    //this is typical millis code here:

    if ((unsigned long)(currentMillis - beamPushedMillis) >= turn5OnDelay) {
      // enough time has passed
      digitalWrite(Relay1, HIGH);
      // setup our next "state"

      Relay1State = true;
      // save when the relay turned on
      Relay1TurnedOnAt = currentMillis;










      // wait for next button press
      Relay1Ready = false;
    }
  }
  //  time to turn off relay
  if (Relay1State) {
    //  relay on, check for now long
    if ((unsigned long)(currentMillis - Relay1TurnedOnAt) >= turn5OffDelay) {
      Relay1State = false;
      digitalWrite(Relay1, LOW);
    }
  }


bailout:

  // get the time at the start of this loop()

  if (digitalRead(GATE1) == LOW) {
    // update the time when button was pushed
    gate1PushedMillis = currentMillis;

    RED_led1Ready = true;
  }
  // make sure this code isn't checked until after button has been let go
  if (RED_led1Ready) {


    //this is typical millis code here:
    if ((unsigned long)(currentMillis - gate1PushedMillis) >= turn1OnDelay) {
      // okay, enough time has passed since the button was let go.
      digitalWrite(RED_led1, HIGH);
      // setup our next "state"
      RED_led1State = true;
      // save when the LED turned on
      RED_led1TurnedOnAt = currentMillis;

      tone (buzzer, 1455, 1200);
      delay(320);
      tone (buzzer, 1400, 1200);
      delay(300);
      tone(buzzer, 600, 200);
      noTone;
      // wait for next button press
      RED_led1Ready = false;
    }
  }
  // see if we are watching for the time to turn off LED
  if (RED_led1State) {
    // okay, led on, check for now long
    if ((unsigned long)(currentMillis - RED_led1TurnedOnAt) >= turn1OffDelay)
    { RED_led1State = false;
      digitalWrite(RED_led1, LOW);
    }
  }
  // check the button2

  if (digitalRead(BEAM) == LOW) {
    // update the time when button was pushed
    beamPushedMillis = currentMillis;
    BLUE_led4Ready = true;
  }

  // make sure this code isn't checked until after button has been let go
  if (BLUE_led4Ready) {
    //this is typical millis code here:

    if ((unsigned long)(currentMillis - beamPushedMillis) >= turn2OnDelay) {
      // okay, enough time has passed since the button was let go.
      digitalWrite(BLUE_led4, HIGH);
      // setup our next "state"
      BLUE_led4State = true;
      // save when the LED turned on
      BLUE_led4TurnedOnAt = currentMillis;
      tone (buzzer, 155, 1200);
      delay(320);
      tone (buzzer, 1600, 200);
      delay(300);
      tone(buzzer, 600, 200);
      noTone;
      // wait for next button press
      BLUE_led4Ready = false;
    }
  }
  // see if we are watching for the time to turn off LED
  if (BLUE_led4State) {
    // okay, led on, check for now long
    if ((unsigned long)(currentMillis - BLUE_led4TurnedOnAt) >= turn2OffDelay) {
      BLUE_led4State = false;
      digitalWrite(BLUE_led4, LOW);
    }
  }
  // check the gate2

  if (digitalRead(GATE2) == LOW) {
    // update the time when button was pushed
    gate2PushedMillis = currentMillis;

    YELLOW_led2Ready = true;
  }

  // make sure this code isn't checked until after button has been let go
  if (YELLOW_led2Ready) {

    //this is typical millis code here:
    if ((unsigned long)(currentMillis - gate2PushedMillis) >= turn2OnDelay) {
      // okay, enough time has passed since the button was let go.
      digitalWrite(YELLOW_led2, HIGH);
      // setup our next "state"
      YELLOW_led2State = true;
      // save when the LED turned on
      YELLOW_led2TurnedOnAt = currentMillis;
      tone (buzzer, 155, 1200);
      delay(320);
      tone (buzzer, 1600, 200);
      delay(300);
      tone(buzzer, 600, 200);
      noTone;
      // wait for next button press
      YELLOW_led2Ready = false;
    }
  }

  // see if we are watching for the time to turn off LED
  if (YELLOW_led2State) {
    // okay, led on, check for now long
    if ((unsigned long)(currentMillis - YELLOW_led2TurnedOnAt) >= turn2OffDelay) {
      YELLOW_led2State = false;
      digitalWrite(YELLOW_led2, LOW);
    }
  }
}}

We only laugh at over confident people that post rubbish code.
Actually, looking at your code helps us to help you at an appropriate level of sophistication...

2 Likes

I am not laughing.
I am happy about people who learn independently and look for success.

Keep up the good work.

  • use of arrays would significantly reduce the size of the code
  • a single sub block of code could check if some activity is being timed and to perform some action when the time is expired using using arrays
  • don't understand the need to wait "until after button has been let go"
  • doubt there is a need for a goto
  • functions can be used instead of duplicating code

This type cast should not be needed...

Hi @2rla ,

I think you're being a little "hard" at yourself, first times are always hard.

Yor're saying you consider your code long and winded... I started to write a library for the push buttons signals treatment (yes, just pushbuttons!!)... and it's now more than 2.800 lines long, even trying to keep it short!

Keep in mind the classic pushbutton is pretty much useless without post-processing.

If it's of any help for you take a look at my WIP and feel free to ask!

Good Luck!
Gaby.//

Thank you all, just incase it makes a difference, I am calling the inputs buttons but they are make break contacts, magnetic switches and rocker switches.
Which is another complication I need to address. When the contact has changed state for maybe hours not just a passing on off.

Actually your code is not that winding. It us a bit monolithic. You could break up your code in pieces (functions).

As an example:
Your buzzing could be moved to a function with arguments.

void buzz(int freq, int duration) {
   Blablabla
}

Also yor variable naming does not comply with conventions.
CAPS are for constants.
Pin addresses are named relayPin.
That also saves you from confusing pin addresses with the results from a pinRead...

analogRead(somePin, someValue);

look this over
tries to codify what you're doing and capturing parameters in arrays

//Global Variables   //CLEANED UP
// NOT LUMEN CONTROLLED
// ADDed BACK DOOR FOR NIGHT LIGHTING
// blocked beam addition
//working version

const byte GATE1       = 2;
const byte BEAM        = 3;
const byte GATE2       = 5;

const byte LedRed      = 8;
const byte LedYel      = 9;
const byte LedBlu      = 11;
const byte Relay1      = 12;
const byte PinBuzzer   = 7;

const byte PinInps [] = { BEAM,   GATE1,  GATE2 };
const int  Ninp       = sizeof(PinInps);
byte       inpState [Ninp];

const byte PinOuts [] = { LedBlu, LedRed, LedYel, Relay1 };
const int  Nout       = sizeof(PinOuts);
bool       active  [Nout];
unsigned long  msecOn [Nout] = { 1000, 2000, 3000, 5000 };  // test values
    // { 15000, 15000, 15000, 50000  };
unsigned long  msec   [Nout];

int   freq1   [Nout] = { 1455,  155,  155 };
int   freq2   [Nout] = { 1400, 1600, 1600 };
int   freq3   [Nout] = {  600,  600,  600 };

long  period1 [Nout] = { 1200, 1200,  200 };
long  period2 [Nout] = { 1200, 1200,  200 };
long  period3 [Nout] = { 1200,  200,  200 };

enum { Off = LOW, On = HIGH };

char s [90];

// -----------------------------------------------------------------------------

void
buzzer (
    int  n )
{
    tone (PinBuzzer, freq1 [n], period1 [n]);
    delay (320);
    tone (PinBuzzer, freq2 [n], period2 [n]);
    delay (300);
    tone (PinBuzzer, freq3 [n], period3 [n]);
    noTone (PinBuzzer);
}

// -----------------------------------------------------------------------------
void loop ()
{
#if 0
    if (digitalRead (BEAM) == HIGH)
        return;
#endif

    unsigned long currentMillis = millis ();

    // check for timed events
    for (int n = 0; n < Nout; n++)  {
        if (active [n] && currentMillis - msec [n] >= msecOn [n])  {
            active [n] = false;
            digitalWrite (PinOuts [n], Off);
        }
    }

    // monitor digital inputs
    for (int n = 0; n < Ninp; n++)  {
        byte inp = digitalRead (PinInps [n]);

        if (inpState [n] != inp)  {
            inpState [n] = inp;
            delay (10);             // debounce

            if (LOW == inp)  {
                sprintf (s, " %d  %3d  %d", n, PinInps [n], inp);
                Serial.println (s);

                active [n] = true;
                msec   [n] = currentMillis;
                digitalWrite (PinOuts [n], On);
                buzzer (n);

                sprintf (s, " %d  %3d  %d", n, PinInps [n], inp);
                Serial.println (s);
            }
        }
    }

    // monitor analog input
    int anlg = analogRead (A0);
    if (70 < anlg)  {
        int n = 3;
        if (! active [n])  {
            Serial.print   ("vred ");
            Serial.println (anlg);

            active [n] = true;
            msec   [n] = currentMillis;
            digitalWrite (PinOuts [n], On);
        }
    }
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);

    for (int n = 0; n < Ninp; n++)  {
        pinMode (PinInps [n],  INPUT_PULLUP);
        inpState [n] = digitalRead (PinInps [n]);
    }

    for (int n = 0; n < Nout; n++)  {
        pinMode      (PinOuts [n],  OUTPUT);
        digitalWrite (PinOuts [n], Off);
    }

    pinMode (PinBuzzer, OUTPUT);
    noTone (PinBuzzer);
}

gcjr,
Wow !
I don't know how you can do that so quickly, it will take me ages to understand the processes you have used but having this with the near layout of my effort will be a tremendous help.
Thank you very much.

can be done but
is waaaaaaaaayyyyyyyyy tooo much underestimating how much learning must be done for a real new-comer to UNDERSTAND your code

@gcjr

to a real newcomer your code is the same as if I ask you

please take this BMX-bike and run down a 30 feet half pipe and then do a double back-salto !!!!
man !!! You should either setup a whole programming course or about arrays
or carefully asking for the knowledge-level of the TO
instead of posting your usual complex coding !
Damm it !!!