Using 8 On/Off buttons to control 8 relays

Hello. I am having issues with programming.. I need to create a program that uses the Nano - button 1 when pushed in turns on Relay 1, hit the button again, and relay turns off.. 8 sets of these.. I am willing to pay someone to write the code for me, as I am new to this.

Thank you. Please email me at info@dan-jam.com

Welcome to the forum

You need to detect when the button becomes pressed rather than when it is pressed
See the StateChangeDetection example in the IDE

That will show you how to count how many times a button has been pressed. From that you can work out whether the associated LED should be on or off. On for odd numbers and off for even numbers, for instance

To scale the sketch up to using more buttons then arrays would be useful

Would you like this topic moved to the forum category where you can ask for paid help ?

You might like to edit your post and remove your email address to prevent spammers using it

Rather then answer your question I will highly recommend you get the Arduino Cookbook and skim it cover to cover. Pause on the sections relative to your project. Rest assured you will have solved your problem by the time you come to the end of it.

I would like it to be moved to the paid help please!

Dan Jamison

Moderator edit
Spam links removed

Done

There are several ways to do this - some don’t need an Arduino.
When you find a helper tell them what types of relays, switches you are planning to use.

If this is a school project, think hard what you’re asking, as it could prove you’ve learned nothing during your course.

This may or may not work.
Felt bored, might test later.

// Libraries used
// Uses Italo Coelho pushbutton library
//

#include <pushButton.h>

const int RELAY_OFF = LOW;
const int RELAY_ON = HIGH;

const int BUTTON1_PIN = 2;
const int BUTTON2_PIN = 3;
const int BUTTON3_PIN = 4;
const int BUTTON4_PIN = 5;

const int NUM_BUTTONS = 4;

const int RELAY1_PIN = 7;
const int RELAY2_PIN = 8;
const int RELAY3_PIN = 9;
const int RELAY4_PIN = 10;

// Create button array
pushButton buttons[NUM_BUTTONS] = {pushButton(BUTTON1_PIN),pushButton(BUTTON2_PIN),pushButton(BUTTON3_PIN),pushButton(BUTTON4_PIN) };

// Create relay array
int relays[NUM_BUTTONS] = {RELAY1_PIN, RELAY2_PIN, RELAY3_PIN, RELAY4_PIN};

void setup() 
{
    Serial.begin(115200);
    
    for (int i=0; i < NUM_BUTTONS; i++)
    {
        pinMode(relays[i], OUTPUT);
        digitalWrite(relays[i], RELAY_OFF);
    }
    Serial.println("button-relay 1.0");
}

void loop() 
{
    static char debug[80];
    delay(100);
    for (int i=0; i < NUM_BUTTONS; i++)
    {
        if (buttons[i].wasPressed() )
        {
            // Toggle relay
            bool state = digitalRead(relays[i]) ? RELAY_OFF : RELAY_ON;
            digitalWrite(relays[i], state);
            sprintf(debug, "button %d pressed. State=%d", i+1, state);
            Serial.println(debug);
        }
    }
}

It works, at least with one button and corresponding LED, no reason it shouldn't for more.

But it does not work without throttling the loop, here probably more severely than necessary, I would guess:

//    delay(100);

The pushButton.h library code has the word bounce in it, so it seems like it might shouldn't care if it was called at a high frequency.

I'll put that library on the list of push button libraries I have not yet torn apart. No time/energy for yet another. One I have seen doesn't like being called too slowly - no good reason for that to need to be, either.

Maybe there is something I am missing.

a7

You're not missing anything.
I started out with the 100ms delay through force of habit to avoid having to debounce. Then I realized that my initial code would require more work than I thought (state management for multiple buttons) so I looked around for a pushbutton library to make it easier. This was the first one I found and I just never removed the delay.

It does not work, at least not if you don't also include the Poor Man's debounce technique of throttling the loop or otherwise not calling the wasPressed() method too frequently.

It is fixable, but by the time you have done, it will just look like many other debounce code sequences.

I'll leave it to whoever cares to do what they need to see for themselves why it fails. It would take 3X the time I've already wasted spent to produce a lab report detailing the experiments.

This library shall join too many others at which I have looked closely until I throw them in the trash.

a7

Huh.
I wired it up and tested it with 1 input 1 output and it worked fine. Guess I should make sure to keep the delay in place.

When your code doesn't block execution, you can put a button-handler in void loop() that only updates a read status for the rest of the sketch to use.

here is a one-button example from 2018. I have a multi-button version as well, might have a matrix-button version somewhere too, the big lesson is to check 1 button per void loop() pass with that running at over 50KHz reads a lot of pins per ms.
I do have a faster executing timing method since 2018 but can't find a sketch saved that uses it, instead of end time - start time it simply watches for bit 9 of micros() to flip (512 us rather than 500) which could be a different bit for faster or slower read intervals.

Non-blocking code allows tasking on a single thread is OLD tech.

// add-a-sketch_button 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, Apr 30/2018 by GFS. Compiled on Arduino IDE 1.6.9.
// Update May 6/2018, Aug 11, 2018

/*  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.
  --- Press and hold the Button to toggle led13 blinking.

  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 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.
*/

// button vars
byte buttonPin = 7;
byte buttonHistory;
word markButtonTime;        // 16-bit micros timers
const word waitButtonTime = 500; // micros

// 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 buttonTask()   // yup, this is my new button debounce compromise method.
{ // read twice per milli, bits 0 to 6 all same sets the state
  if ( word( micros()) - markButtonTime >= waitButtonTime ) // read occaisoinally
  {
    buttonHistory <<= 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 += digitalRead( buttonPin ); // read history streams through buttonHistory
    markButtonTime = micros(); // gets the low 16 bits of micros(), time to 60 ms + margin
  }
  /*        buttonHistory bits read as values:
    0 is button held down, 8 reads of 0 in a row
    255 is button left up, 8 reads of 1 in a row
    127 is buton changing from up to down, 7 1's and 1 0 = just released
    128 is button changing from down to up, 7 0's and 1 1 = just pressed.
    everything else is to be ignored as bounce.
    Understand that 7 same bits in a row counts as pin state stable.
  */
}

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 );
  for ( byte i = 0; i < 66; i++ )  Serial.println();
  Serial.println( F( "\n  Button Debounce Example, free by GoForSmoke\n" ));
  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( buttonPin, INPUT_PULLUP );
  pinMode( ledPin, OUTPUT );

  buttonHistory = 255;
  waitBlink = 500;
};


void loop()
{
  buttonTask();
  /*
    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.
  */

  switch ( buttonHistory ) 
  {
    case 128 : // pin is HIGH in bit 7, LOW for 7 reads, up to down detected
      buttonHistory = 0; // change detected, make it into no change now
      Serial.print( F( "press detected     " ));
      Serial.println( millis());
      if ( waitBlink == 0 ) // toggle action tied to button press
      {
        waitBlink = 500; // makes the blinking start
        startBlink = millis(); // gets the low 16 bits
      }
      else
      {
        waitBlink = 0; // makes the blinking stop
      }
      break;
    case 127 : // pin is LOW in bit 7, HIGH for 7 reads, down to up detected
      buttonHistory = 255; // change detected, make it into no change now
      Serial.print( F( "release detected   " ));
      Serial.println( millis());
      break;
  }

  OnOffBlinker();
}

Yes. Simplest.

As I read it if the delay in the loop is longer than the debounce period of the buttons you are using, the library will be OK.

Or write something a bit more complicated and make the loop run at a fixed frequency, here use 50 Hz for example, giving the buttons 20 milliseconds of bouncing time. Code from blink without delay kinda thing.

It's a shame as that library seems to want to hit all the correct notes for doing the usual simple compact button debounce that just works.

It's almost like the author asked chatGPT to write it - plausible but on inspection srsly flawed.

ezButton doesn't suck, but curiously it doesn't like being serviced at less than some unknown frequency.

In a loop that runs free, it works well.

a7

In the example above, the button handler is a function that runs in loop(), copy&paste that and the associated global vars.

The 4 ms button history shows the last 8 reads, in binary it's a picture. 0's and 1's mixed is bounce, all 0 or 1 is a held state, 10000000 and 01111111 are change+stable detections in a 4ms period. Buttons clean or dirty all end in the same pictures/values.
To the functions you add
If button==picture then do something. Is that hard?

The between read period can be changed. History can be 16 or 32 or even 64 bits though to me it'd be wasting RAM. The history needs to cover stability + 1 read to detect change+stable where stable is > the longest bounce.

Any time the sketch wants button state, the picture is there without waiting and it will compare as a value.

Yes, a method covered in any good treatment of software debouncing.

Key is not doing the shifting too frequently. I've seen examples that work only because there is some inherent limit to how fast loop is looping. Here of course you allow for that.

One disadvantage is that it is not (or is it?) a library that one can just tell another to install and use.

I have no trouble picking out the relevant parts and repurposing them, but doing might be more trouble for someone trying to make eight buttons turn eight relays on and off.

If I was going to make someone also understand debouncing, I would start with simpler software that builds on things we might hope they already know, or at least know they don't know yet.

a7

I like to read code and decide what the problems with it are, then run the code to see if I was right.

The code in #12 is harder to use than necessary. While the shifting bits deglitcher / debouncer is clever, it may be more suited to other tasks than watching a button.

Normally open pushbuttons do not show themselves as closed unless they are on to the way to being closed, or or are already closed and stable.

Nor do normally open pushbuttons show themselves as open unless they are on the way to being open, or are already open and stable.

If you have pushbuttons that do either, throw them out. :expressionless:

Usually, what is of first interest for watching a button is "did it get pressed". While the shift register technique provides it, it does so only for a very brief period.

Many button libraries offer an indication of a button event. If the library worked, that is the purpose of the wasPressed() method:

        if (buttons[i].wasPressed() )

Again, if it worked, this would be true once for each button going down event. As there is no service call equivalent to

  buttonTask();

we infer that the event will sit around and wait until we ask about it.

In libraries that do use a loop service call, the choice of the designer is to implement a persistent flag that sits and waits for the user to check, no matter the service call is made any number of times, or to implement a "use it or lose it" philosophy that means one must check for the event after one service call and before the next. In most libraries, it does not matter how frequently one calls the service method, just that it be called before any new information can be expected.

"use it or lose it" fits perfectly with the IPO model for programming. Once per loop we service the button(s), and in that loop see and dispose of any events that turned up.

The code in #12 does not allow event detection if you are not right there in the small window of time it exists in the "picture". It's beyond "use it or lose it" in the sense that the event will not wait to be used.

Anything that doesn't come around in waitButtonTime time (500; // micros) will miss it.

The technique as presented needs way too much inside knowledge of its operation and limits. Practically speaking, it would usually need state change detection to be implemented in the higher level code.

If what you want is to do something when a button becomes pressed, you need to debounce and detect the state change. Most libraries allow this to be placed in higher level code with next to no idea of how they work (or don't, too often).

The shift register can't be shifted too fast, and can't be looked at too slowly.

Running hand-made state detection alongside the "picture" recognition of press and release at 250 Hz yields

switch is one way
switch is                 the other way
switch is one way
switch is                 the other way
release detected   20044
switch is one way
switch is                 the other way
switch is one way
switch is                 the other way
switch is one way
switch is                 the other way
release detected   21192
switch is one way
switch is                 the other way
press detected     21444
switch is one way

State of the button is fine, detecting presses and releases a matter of probability.

a7

Show me another who did the same. I didn't learn that from anyone, I developed that in 2018.

It is a function and variables that one can just add to any non-blocking sketch. BTW, libraries are not magical nor required.
The example shows how but you need to able to see that.

I did mention that I have multi-buttons and matrix-buttons versions and that I will post them if asked. I gave the simplest one to get the idea across with less code.

And in this thread there are no examples of that?

I don't want to hand out black boxes to beginners.
I want them to think and to learn. That's the price and the reward.