Train panel

Arduino button class

I’m currently working on power control and switching system for a model railway, and I was in the need of a good way to handle buttons, so I created a really simple and really good button class.

First it needs to take care of debouncing. If you’re not familiar with the concept, when you press a push button or a switch, it does not just go from one state to the other, it bounces between off and on state before settling in either state. This is not noticable to the human senses, but it can lead to annoying and confusing problems when doing electronic circuits.

Secondly, I wanted to get away from the loop. The code gets cluttry if you are to read the button states from the main loop. So, I decided I wanted to use callback functions. I pass a reference to a callback function to the button instance, and when the state of the button changes the callback function is called.

The class uses the built in pullup resistors. The atmega chip has built in pullup resistors that can be used for reading buttons, so you don’t have add a resistor yourself. All you need to do is to ground the pin when it’s pressed. When not grounded the pin reads HIGH via the pullup resistor to +5V, when grounded (pressed) the button reads LOW. So, just connect a pin to the one of the buttons connectors, and connect ground to the other. Not sure if all boards/versions supports the pullup resistors.

So, here comes the code. First, example usage:

#include "Button.h"

//Declare two buttons on pin 9 and 10, pass the address (&) of the callback functions
Button myButton = Button(9, &myCallback);
Button myOtherButton = Button(10, &myOtherCallback);

//Keep pointers to all the buttons in an array for simpler handling of many buttons
const int buttonAmount = 2;
Button* buttons[buttonAmount] = {
  &myButton,
  &myOtherButton
};

void setup() {
}

//Read the buttons in the loop
void loop() {
  for (int i = 0; i < buttonAmount; i++){
    buttons[i]->read();
  }
}

void myCallback(Button* button)
{
  if (button->pressed){
    //Button pressed
  }
  else{
    //Button released
  }
}

void myOtherCallback(Button* button)
{
  if (button->pressed){
    //Button pressed
  }
  else{
    //Button released
  }
}

A pointer to the instance of the Button being changed is passed to the callback function.

Here’s the code for the actual button class. Create a folder in the Arduino libraries folder, name it “Button”. Then add the following files to it.

“Button.h”

#include "Arduino.h"
#ifndef Button_h
#define Button_h

class Button
{
  private:
    int lastValue;
    unsigned long lastDebounceTime;  
    void (*pCallback)(Button*);
  public:
	String label;
    int pin;
	int state;
    bool pressed;
    void read();
    Button(int PIN, void (*pCallbackFunction)(Button*));
};

#endif

“Button.cpp”

#include "Arduino.h"
#include "Button.h"

Button::Button(int PIN, void (*pCallbackFunction)(Button*))
{
  pin = PIN;
  pCallback = pCallbackFunction;
  pinMode(pin, INPUT_PULLUP);
  state = digitalRead(pin);
  pressed = state == LOW;
  lastValue = state;
  lastDebounceTime = 0;
}

void Button::read()
{
  int value = digitalRead(pin);
  if (value != lastValue){
    lastDebounceTime = millis();
  }
  if ((millis() - lastDebounceTime) > 10){
    if (value != state){
      state = value;
	  pressed = state == LOW;
      pCallback(this);
    }
  }
  lastValue = value;
}

This code makes use of c pointers. That’s what the cryptical * and & signs are about.

* means that a variable is a pointer. A pointer is a variable that points to the address of something else.

& means “the address of” something.

This is the c way of passing things “by reference”.

Note that in the callback functions the “pressed” property of the button is read using -> instead of just a dot. This is just because this is a pointer, you just need to do it like that.

So the Button class holds a variable called pCallback that is a pointer to a function that takes a pointer to a Button as argument.

void (*pCallback)(Button*);

The address of the callback function is passed to the constructor of the button using the & sign.

Button myButton = Button(9, &myCallback);

(Actually I think the & is optional, but I keep it there for clarity)

Then it’s simply called, passing this, which is the address of the current Button instance.

pCallback(this);

Hope you like it. Any feedback is appreciated.

  • David

    Thank you very much! Fantastic code that is incredibly helpful and taught me a lot! Works far better than any method that I’ve used in the past. Very much appreciated.