use Button objects in more than 1 file.

I have develloped a class to dynamically read and debounce buttons. This class is eventually to be used with my state machine within separate files. At this point I am not entirely sure if a class is the proper solution. Perhaps that a struct is better for this use-case.

.cpp:

Button::Button(unsigned char _pin) {
	pinMode(_button, INPUT_PULLUP);
	pin = _pin; }

Button::unsigned char readButton() {
	return state; }

Button::unsigned char updateButton() {
	static bool oldSample = false;
	static unsigned char statePrev;
	bool newSample = digitalRead(pin);

	if(newSample == oldSample) {	// if the same state is detected atleast twice in 20ms...
	
		if(newSample != statePrev) { // if a flank change occured return RISING or FALLING
			statePrev = newSample;

			if(newSample)	state = RISING; 
			else			state = FALLING; }

		else {						// or if there is no flank change return PRESSED or RELEASED
			if(newSample)	state = PRESSED; 
			else			state = RELEASED; } } 
		
	oldSample = newSample; 
	return state; }

.h:

#ifndef button_h
#define	button_h

enum states {
	RISING,
	FALLING,
	PRESSED,
	RELEASED };

class Button {
public:
	Button(unsigned char _pin);
	readButton();
	updateButton();

private:
	unsigned char state;
	unsigned char pin; };
	
#endif

The works/idea:

To avoid confusions, I’ll use the word ‘level’ to indicate the actual voltage of the input pin. And the word ‘state’ refers to one of the 4 possible states of a button.

The function ‘updateButton()’ is called as a round robin tasks, once every ~20ms.

newSample is compared to oldSample for the debouncing part. If the same level is detected within 2 iterations, the button state is updated. This may be: rising, falling, pressed or released.

After I am sure that the flank is changed. I compare the new ‘level’ with the old one (‘statePrev’) to determen the new state of the button.

The first problem:
For every button there is the ‘updateButton’ function. I know I can stuff all buttons in the array using:

.. in io.h

#define buttonAmmount 3

enum buttonNames {
    button1,
    button2,
    button3 };

.. setup:

Button buttons[buttonAmmount ] = {
	Button(11),
	Button(12),
	Button(13) };

I am busy with letting scripts generate code for me and I want to manage my IO in a separate file “IO.h”.

To update the buttons in roundRobinTasks.c;

for(int i=0;i<buttonAmmount;i++) buttons[i].updateButton(); // right?

The problem:
The button objects are to be accessible in more than 1 file. Every state machine has it’s own cpp and h files and the round robin tasks are also in separate files. But how does one access all button objects from all files? I suppose I have to use a pointer, but how?

I know that with structs that I can simply make them extern (global) and every other file which includes that files has acces to these structs.

Am I good to go with a class or should I move back to structs?

P.S.
The button class is neither tested nor compiled yet.

Classes and structs are essentially the same thing. The only difference is that members of a struct are public by default while members of a class are private by default.

Yes that much I know. What I like about structs is that you can make them extern or global which is IMO usefull for things like GPIO definitions and functions.

But again:
If I make 5 button objects inside main.ino, how can I address them from other .cpp files?
And is that smart to do?

Right now I am feeling to ditch the class altogether. I am not seeing any benefit anymore.

I have also decided to abondon the rising and falling states. For a flank change to take effect would take 40ms. Reading the state is done with a seprate get function which would return RISING or FALLING for 40ms straight. I can think for workarounds, but I honestly doubt the practical use of a falling and rising state. Whether a flank rises or falls, that can be handled in the part which needs to read the button.

bask185:
Yes that much I know. What I like about structs is that you can make them extern or global which is IMO usefull for things like GPIO definitions and functions.

Because structs and classes are (nearly) identical you can do exactly the same thing with instances of a class. For example, on an AVR board, "Serial" is a global instance of the "HardwareSerial" class.

Another example:

MainFile.ino:

#include "Arduino.h"
#include "OtherFile.h"

void setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.print("Instance 1 ID: ");
  instance1.printID(Serial);
  Serial.println();

  Serial.print("Instance 2 ID: ");
  instance2.printID(Serial);
  Serial.println();

}

void loop() {

}

OtherFile.h:

#ifndef OTHERFILE_H_
#define OTHERFILE_H_

#include "MyClass.h"

extern MyClass instance1;
extern MyClass instance2;

#endif /* OTHERFILE_H_ */

MyClass.h:

#ifndef MYCLASS_H_
#define MYCLASS_H_

#include "Arduino.h"

class MyClass {
public:
  MyClass(uint8_t i) :
      myID(i) {
  }

  void printID(Stream &printHere) {
    printHere.print(myID);
  }

private:
  const uint8_t myID;

};

#endif /* MYCLASS_H_ */

OtherFile.cpp:

#include "OtherFile.h"

MyClass instance1(1);
MyClass instance2(2);

The variables instance1 and instance2 are global and available to any files that #include(s) “OtherFile.h”.

First of all: Never use "pinMode" in constructors, create a "begin" method to assign a pin and set its mode. The reason being that the constructor for global variables may be called before the hardware is initialized / ready.

Whenever I have declarations that are to be shared in multiple files, I create a file which I prefer to call "globals". In your case it would look something like this:

globals.h

#include "Button.h"
#ifndef GLOBALS_H
#define GLOBALS_H

extern Button button1;
extern Button button2;
extern Button button3;

void setup_globals();
void loop_globals();

#endif

globals.cpp

#include "globals.h"

Button button1;
Button button2;
Button button3;

void setup_globals()
{
  button1.begin(pin1);
  button2.begin(pin2);
  button3.begin(pin3);

  //Initialize other global stuff

}

void loop_globals()
{
  button1.updateButton();
  button2.updateButton();
  button3.updateButton();

  //Update other globals stuff

}

By including "globals.h" every file in your project will be able to access the buttons. Your main sketch would look something like this:

sketch.ino

#include "globals.h"

void setup()
{
  setup_globals(); //Init buttons etc.
}

void loop()
{
  loop_globals(); //Update button states etc.
}

Simple, effective and easy to maintain :slight_smile:

Thank you all, really helpfull!

The reason being that the constructor for global variables may be called before the hardware is initialized / ready

never knew that. Hypothetically speaking what could go wrong if I would do precisely that?

what could go wrong if I would do precisely that?

The mode of the pin might not be set how you specifically want it

This may not actually be the end of the World because digital pins default to INPUT and you don't need to set the mode of analogue pins. However, if your intention is to set digital pins to OUTPUT or INPUT_PULLUP then you may have problems.