I'm working on an Arduino project in pure C with multiple buttons. Each button has a different action associated with it, and I want to handle button presses using interrupts.
Currently, the typical approach is to create one ISR per button, like this:
#define NUM_BUTTONS 4
int currentAction[NUM_BUTTONS];
int buttonPins[NUM_BUTTONS] = {2, 3, 4, 5};
void handleButton(int index, int action) {
// Do something with the button
}
static void buttonHandler0() { handleButton(0, currentAction[0]); }
static void buttonHandler1() { handleButton(1, currentAction[1]); }
static void buttonHandler2() { handleButton(2, currentAction[2]); }
static void buttonHandler3() { handleButton(3, currentAction[3]); }
static void (*buttonISRs[NUM_BUTTONS])() = {
buttonHandler0, buttonHandler1, buttonHandler2, buttonHandler3
};
void setup() {
for (int i = 0; i < NUM_BUTTONS; i++) {
pinMode(buttonPins[i], INPUT);
enableInterrupt(buttonPins[i], buttonISRs[i], RISING);
}
}
Problem:
This requires manually writing N wrapper functions, which is cumbersome if NUM_BUTTONS grows.
`enableInterrupt` only allows a pointer to a function with no parameters, so I cannot directly pass the button index or action.
What I want:
A scalable solution in C/Arduino that allows N buttons, each with a separate action.
Avoid writing one hardcoded ISR per button.
Keep it compatible with `enableInterrupt` or Arduino-style interrupts.
EDIT
Here is a brief explanation of the project and the relevant part of my I/O manager. The game shows a random sequence of 4 digits (1–4) on the LCD, and the player must press the corresponding buttons in the correct order and within a time limit. Each button B[i] turns on LED L[i]. If the sequence is completed on time, the score increases and the next round becomes faster; if the player makes a mistake or runs out of time, the game ends and the red LED lights up. The system uses interrupts for handling button presses, debouncing logic, and a queue of inputs collected during each round.
Below is an extract of my I/O manager, specifically the part that handles debouncing, interrupt handlers, and the array of per-button ISRs:
#define BOUNCING_TIME 100
#define MAX_QUEUE (NUM_BUTTONS + 50)
// Queue
static volatile int inputQueue[MAX_QUEUE];
static volatile int queueLast = 0;
// Debounce
static unsigned long lastButtonPressedTimestamps[NUM_BUTTONS];
static ButtonAction currentAction[NUM_BUTTONS];
// Generated handlers
static void genericButtonHandler0() { buttonHandler(0, currentAction[0]); }
static void genericButtonHandler1() { buttonHandler(1, currentAction[1]); }
static void genericButtonHandler2() { buttonHandler(2, currentAction[2]); }
static void genericButtonHandler3() { buttonHandler(3, currentAction[3]); }
static void (*buttonISRs[NUM_BUTTONS])() = {
genericButtonHandler0,
genericButtonHandler1,
genericButtonHandler2,
genericButtonHandler3
};
// Queue management
void clearQueue() { queueLast = 0; }
int inputQueueLen() { return queueLast; }
int inputQueueGet(int index) {
return (index >= 0 && index < queueLast) ? inputQueue[index] : -1;
}
// Button handling
void addActionToButton(int buttonIndex, ButtonAction action) {
if (buttonIndex < 0 || buttonIndex >= NUM_BUTTONS) return;
disableInterrupt(pair[buttonIndex].buttonPin);
enableInterrupt(
pair[buttonIndex].buttonPin,
buttonISRs[buttonIndex],
RISING
);
}
void disableAllButtons() {
for (int i = 0; i < NUM_BUTTONS; i++) {
disableInterrupt(pair[i].buttonPin);
}
}
static void buttonHandler(int i, ButtonAction action) {
unsigned long ts = millis();
if (ts - lastButtonPressedTimestamps[i] > BOUNCING_TIME) {
lastButtonPressedTimestamps[i] = ts;
int status = digitalRead(pair[i].buttonPin);
#ifdef __DEBUG__
Serial.println("Pressed button " + String(i + 1));
#endif
if (status == HIGH && queueLast < MAX_QUEUE) {
inputQueue[queueLast] = i + 1;
queueLast++;
}
}
}