push button class using attachInterrupt() in a class.

Hello. This code is for UNO.
I made this simple class to detect click and double clicks, it use attachInterrupt(). This is for my 3D table. One click command = 0|1, double click command = 1|2. I want the attachInterrupt() which is in setup to be initialized in class constructor.But it not allow me due attachInterrupt() need static function which can't call non-static function in object. For the moment I let attachInterrupt() out of class, and it works fine. Any idea how to put the attachInterrupt() in a class ?
And any mistake you see in code, pls correct me. :slight_smile:

this is the code:

class Button {
private:
int BUTTON_PIN;
bool debug; //Enable logs
bool pushTriger;
unsigned long time_pushed; //Time when the button pushed
int detection_click_time; //mseconds. During that period of time check double click
int delay_time_click_not_work; // mseconds. When you push button,the volt can tremble and see it like second click. After click, during that period of time ignore clicks.
int click_counter;
//bool buttonStatus=0;
int buff; //The time passed from the last batton push buff=millis()-time_pushed
int command=0;
int last_command_turn=1;

public:

int button=0;

Button(){
BUTTON_PIN=2;
//BUTTON_PIN=pin;
buff=0;
detection_click_time;
click_counter=0;
delay_time_click_not_work;
debug=false;
pinMode(BUTTON_PIN,INPUT);
//attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), test, RISING);
}

Button(bool debug_initialize,int pin=2, int detection_click_time_initialize=1000,int delay_time_click_not_work_iitialize=200){
BUTTON_PIN=pin;
debug=debug_initialize;
buff=0;
detection_click_time=detection_click_time_initialize;
click_counter=0;
delay_time_click_not_work=delay_time_click_not_work_iitialize;
pinMode(pin,INPUT);
//attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), test, RISING);

}

void buttonPushed(){
if(debug){Serial.println("Interrupt pushButton Button");}
if(!pushTriger) { //remember time when is triger triggered
time_pushed=millis();
buttonCheck();
}
buff=millis()-time_pushed;
if((buff<=detection_click_time) && !pushTriger){
//time_pushed=millis();
pushTriger=true;
click_counter=1;
if(debug){Serial.println("Interrupt pushButton Button pushed first");}

}else if((buff<=detection_click_time && (buff>delay_time_click_not_work)) && pushTriger){
//pushTriger=true;
click_counter++;
time_pushed=millis();
if(debug){Serial.print("Interrupt pushButton Button pushed second time: ");}
if(debug){Serial.println(click_counter);}
}
}

int buttonCheck(){
buff=millis()-time_pushed;
if(pushTriger && (buff>detection_click_time)){
if(debug){Serial.println("Begins check ");}
if(click_counter==1){
if(command==0){
command=last_command_turn;
}else{
command=0;
}
}else if(click_counter>=2 ){
if(command==2){
command=1;
last_command_turn=1;
}else if(command==1){
command=2;
last_command_turn=2;
}else{
if(last_command_turn==1){
last_command_turn=2;
}else{
last_command_turn=1;
}
}
}
time_pushed=0;
click_counter=0;
pushTriger=false;
if(debug){Serial.print("pushTriger: ");}
if(debug){Serial.println(pushTriger);}
if(debug){Serial.print("***** command: ");}
if(debug){Serial.println(command);}
}else{

}
return command;
}

};

Button button1(true); // first argument is debug true|false
void button_press(){
button1.buttonPushed();
}

int BUTTON_PIN=2;

void setup() {
Serial.begin(115200);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN),button_press, RISING);
}

int command_for_loop=0; //This takes the command returned buttonCheck()

void loop() {
command_for_loop=button1.buttonCheck();
//Serial.println(command_for_loop);

}

Creating a class is a nice learning exercise. In this particular case, though, to manage a single, hard coded button, it is over kill. Also, using (external) interrupts to manage buttons is not necessary and limits you two pins on, say, an Arduino Uno.

But, anyway, to your question about attacthInterrupt(). You create a begin() method in your class and use that for such functions which are dependeint on the Arduino environment initialisation (init() ). The constructor should not be used for this because it is called before such initialisation is complete.

Pointers to class instance functions are different than pointers to regular and class static functions. So, you can't use them with attachInterrupt(). For more information, see: Standard C++

A class static function can indeed call a class instance function, but you need a pointer or reference to the particular instance to do so. For an example of one way to do this, see the Rotary Encoder Library at My GitHub.

Ah yes. I didn't read the OP fully. @gfvalvo's explanation looks good. The problem is the call back routine referenced in the attachInterrupt() function.

It is not a general case, because it is not very useful, but you could make the callback routine a static method in your class definition:

. . .
  static void callback() {
    Serial.println( "in Callback") ;  // not nice in an ISR call chain.
  }

  void begin() 
  {
    attachInterrupt( digitalPinToInterrupt( 2 ) , callback, FALLING ) ;
  }

. . .

All that led to use static function which will call somehow class instance.
if I understand correctly, gfvalvo in his class use:

#define CALL_MEMBER_FN(objectPtr, functPtr) ((objectPtr)->*(functPtr))()

static void isr00(void) {
CALL_MEMBER_FN(_isrTable[0].objectPtr, _isrTable[0].functPtr);
}

What exactly that function do ?

The macro is defined to make calling a member function given a pointer to the instance and a pointer to the particular function easier. The syntax for that is tricky and the article I linked above advises using a macro like this.

My application needed to dynamically set both the instance and function being called on it. That's why I used this technique. Pointers to both are stored in an array of struct that's indexed by the interrupt number.

If you're always calling the same function, then you don't need to use a function pointer. Just store a pointer to the instance and use it to call the required function from the static ISR.

Finely, I managed to put the attachInterrupt() in class so the attachInterrupt() can call object instance from inside.
Looks like it works fine, But Only one thing that is bothering, is it possible to put the:

Button* Button::objPnt[]={NULL};

in class too ? or at least bypass that initialization for this static pointer.
Without that initialization, compiler shows error:

Arduino: 1.8.13 (Windows 10), Board: "Arduino Uno"
collect2.exe: error: ld returned 1 exit status
exit status 1
Error compiling for board Arduino Uno.

in general, if you see something wrong in code, pls correct me :)

class Button {
private:

public:

int BUTTON_PIN;
static Button *objPnt[13]; //Object pointers array
void (*pntF[13])(); //Function Pointers array
Button(int pin=2){
BUTTON_PIN=pin;
pinMode(pin,INPUT);
begin(this);
}
bool begin(Button *obj_b){
void (*pntFunction)()=NULL;
objPnt[BUTTON_PIN]=this;
if(obj_b->BUTTON_PIN==2){
pntF[BUTTON_PIN]=callback_2;
}else if(obj_b->BUTTON_PIN==3){
pntF[BUTTON_PIN]=callback_3;
}
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN),pntF[BUTTON_PIN], RISING);
}
static void callback_2() {
objPnt[2]->buttonPushed();
}
static void callback_3() {
objPnt[3]->buttonPushed();
}
void buttonPushed(){
Serial.print("Buttont: ");
Serial.println(BUTTON_PIN);
}
};

Button* Button::objPnt[]={NULL};
Button button1(2);
Button button2(3);

void setup() {
Serial.begin(115200);
}

void loop() {
}