I am not sure if I need to understand function pointers a bit better here but I'll try and explain the problem I am having.
I have a project where I need upwards of 64 input buttons. I have used the excellent OneButton FSM library (GitHub - mathertel/OneButton: An Arduino library for using a single button for multiple purpose input.) in the past for de-bouncing, called via a timer ISR and it works fine. Now that I have so many input buttons it all becomes a little more complex. Instead of just one or two callback functions that I have previously just duplicated code in, now I am looking at having near 64 identical callback functions. In my final project a button press should generate a CANbus message. In my old way of doing things I would have to have 64 functions that only differed by a CANbus ID.
What I think I need to do is pass a function pointer into the button object instantiation that includes a button ID argument but I don't know how to do this or if I need to modify the OneButton class to achieve this.
I don't really understand this bit of code but I assume the callback function that the class expects does not allow me to pass in an argument for my button ID.
My test code:
#include "OneButton.h"
typedef enum {
ACTION1_OFF,
ACTION1_ON,
ACTION2_OFF,
ACTION2_ON
}
buttonActions;
OneButton button1(10, true);
OneButton button2(8, true);
buttonActions nextAction1 = ACTION1_OFF;
buttonActions nextAction2 = ACTION2_OFF;
#define LED1 (9)
#define LED2 (7)
void setup() {
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
button1.attachClick(myClickFunction1);
button1.setClickTicks(200);
button1.setPressTicks(5000);
button2.attachClick(myClickFunction2);
button2.setClickTicks(200);
button2.setPressTicks(5000);
}
void loop() {
unsigned long now = millis();
button1.tick();
button2.tick();
if (nextAction1 == ACTION1_OFF) {
digitalWrite(LED1, LOW);
} else if (nextAction1 == ACTION1_ON) {
digitalWrite(LED1, HIGH);
}
if (nextAction2 == ACTION2_OFF) {
digitalWrite(LED2, LOW);
} else if (nextAction2 == ACTION2_ON) {
digitalWrite(LED2, HIGH);
}
}
void myClickFunction1() {
if (nextAction1 == ACTION1_OFF) {
nextAction1 = ACTION1_ON;
} else {
nextAction1 = ACTION1_OFF;
}
//send CANbus message for the specific button ID and include state information
}
void myClickFunction2() {
if (nextAction2 == ACTION2_OFF) {
nextAction2 = ACTION2_ON;
} else {
nextAction2 = ACTION2_OFF;
}
//send CANbus message for the specific button ID and include state information
}
Can anyone give me some pointers (no pun intended) on how I can have the callback function allow me to pass in arguments and prevent me having to write 64 separate callback functions.
In my old way of doing things I would have to have 64 functions that only differed by a CANbus ID.
Why not just one function that takes the CANbus ID as a parameter ?
Read the buttons and determine which became pressed. Use that as the index to an array of corresponding CANbus IDs
Which Arduino board are you using, how are the 64 buttons connected to it and how do you propose to determine which has become pressed ? The answer is surely not 64 if statements, or is it ?
Why not just one function that takes the CANbus ID as a parameter ?
Read the buttons and determine which became pressed.
Because the library uses callback functions so that I don't have to read 64 buttons within the loop to determine if 1 has changed.
Which Arduino board are you using, how are the 64 buttons connected to it and how do you propose to determine which has become pressed ? The answer is surely not 64 if statements, or is it ?
Not that it matters for the purpose of the question but I am using 2 x NXP PCA9698 40-bit I/O expanders to handle the I/O. I am prototyping on an Arduino Nano but I will be implementing on a Teensy 4.0. The whole point of using call;back functions is so that I only execute something when a button event happens. I thought that the best way of handling this is to pass in a callback function at instantiation time that has a parameter for the ID of the button being pressed or maybe better, pass in a pointer to an object method for a particular button.
The library on GitHub supports parameterized callback functions. When called, your callback will be passed a void *. This is customizable on a per-button basis and you can have it point to anything you want. Could be an array element identifying the button or even the button instance itself.
gfvalvo:
The library on GitHub supports parameterized callback functions. When called, your callback will be passed a void *. This is customizable on a per-button basis and you can have it point to anything you want. Could be an array element identifying the button or even the button instance itself.
But doesn't the library only accept a pointer with a (void) parameter?
typedef void (*callbackFunction)(void);
I would need to pass in a function pointer with a parameter to refer somehow to the actual button when its instantiated wouldn't I? Thats the basis of my original question.
solderdog:
But doesn't the library only accept a pointer with a (void) parameter?
void *p means p is a pointer to an arbitrary object - p could point to a string, array, struct, float, etc. This means you can pass a single object of any type to your callback function.
solderdog:
I would need to pass in a function pointer with a parameter to refer somehow to the actual button when its instantiated wouldn't I? Thats the basis of my original question.
You could have a single callback function that accepts the pin-number as a parameter. Then you would automatically know which button was pressed without testing them all. Something like this:
button1.attachClick(myClickFunction, 10);
Where:
void myClickFunction(const uint8_t &pinNum) {
//find button state?
//send CANbus message for the specific button ID and include state information
}
Power_Broker: void *p means p is a pointer to an arbitrary object - p could point to a string, array, struct, float, etc. This means you can pass a single object of any type to your callback function.
There are 2 'voids' in that class. Doesn't the first one mean there is no return? What does the second one mean?
Power_Broker:
You could have a single callback function that accepts the pin-number as a parameter. Then you would automatically know which button was pressed without testing them all. Something like this:
button1.attachClick(myClickFunction, 10);
The library would still need to be modified:
/Users/xxx/Documents/Arduino/libraries/OneButton/src/OneButton.h:56:8: note: candidate expects 1 argument, 2 provided
exit status 1
no matching function for call to 'OneButton::attachClick(void (&)(), int)'
I have written a test class to try and understand how to pass in the function. I'll post below.
So I have written a test class to try and understand how to pass in the function and an id for each button. I think I will have to write a wrapper class around the OneButton library to make it do what I think I need it to do!
It does not work unfortunately. As I've been saying the library typedef expects 1 parameter which is a pointer.
candidate expects 1 argument, 2 provided
/Users/xxxx/Documents/Arduino/sketch_apr16b/sketch_apr16b.ino: In function 'void setup()':
sketch_apr16b:11:43: error: no matching function for call to 'OneButton::attachClick(void (&)(uint8_t), const uint8_t&)'
button.attachClick(clickFunc, BUTTON_PIN);
^
In file included from /Users/xxxx/Documents/Arduino/sketch_apr16b/sketch_apr16b.ino:1:0:
/Users/xxxx/Documents/Arduino/libraries/OneButton/src/OneButton.h:56:8: note: candidate: void OneButton::attachClick(callbackFunction)
void attachClick(callbackFunction newFunction);
^
/Users/xxxx/Documents/Arduino/libraries/OneButton/src/OneButton.h:56:8: note: candidate expects 1 argument, 2 provided
exit status 1
no matching function for call to 'OneButton::attachClick(void (&)(uint8_t), const uint8_t&)'
This works, but it errors when I uncomment the callback handler in OneButtonTwo.cpp
/Users/xxxx/Documents/Arduino/libraries/OneButtonTwo/OneButtonTwo.cpp: In member function 'void OneButtonTwo::begin()':
/Users/xxxx/Documents/Arduino/libraries/OneButtonTwo/OneButtonTwo.cpp:15:47: error: invalid use of non-static member function
button.attachClick(OneButtonTwo::createMsg);
^
exit status 1
Error compiling for board ATmega328.
I don't yet understand what "invalid use of non-static member function" means.
solderdog:
So I have written a test class to try and understand how to pass in the function and an id for each button. I think I will have to write a wrapper class around the OneButton library to make it do what I think I need it to do!
i don't see any other option if you want to use that library. you may be better off writing your own.
if the callback has an integer argument, the presumption is that the library would call the function with the button id as the argument. But the library is designed to call a unique funciton.
Are you telling me to modify the OneButton library? I know I could have done this, but I was trying not to modify the base library and thought I would have to write a wrapper to make it do what I wanted it to do.