Skip to content

Commit 06a19ee

Browse files
committed
feat(gpio): simplifies the example and documentation
1 parent e699b1a commit 06a19ee

File tree

10 files changed

+778
-665
lines changed

10 files changed

+778
-665
lines changed

cores/esp32/esp32-hal-timer.h

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ double timerReadSeconds(hw_timer_t *timer);
4747

4848
uint32_t timerGetFrequency(hw_timer_t *timer);
4949

50+
// Legacy C function pointer versions (for backward compatibility)
5051
void timerAttachInterrupt(hw_timer_t *timer, void (*userFunc)(void));
5152
void timerAttachInterruptArg(hw_timer_t *timer, void (*userFunc)(void *), void *arg);
5253
void timerDetachInterrupt(hw_timer_t *timer);
@@ -55,6 +56,23 @@ void timerAlarm(hw_timer_t *timer, uint64_t alarm_value, bool autoreload, uint64
5556

5657
#ifdef __cplusplus
5758
}
58-
#endif
5959

60-
#endif /* SOC_GPTIMER_SUPPORTED */
60+
// Modern C++ std::function overloads (matches attachInterrupt() style)
61+
#include <functional>
62+
63+
// New std::function versions - solves your exact problem!
64+
void timerAttachInterrupt(hw_timer_t *timer, const std::function<void()>& callback);
65+
void timerAttachInterruptArg(hw_timer_t *timer, const std::function<void(void*)>& callback, void *arg);
66+
67+
// Type aliases for convenience (following attachInterrupt() pattern)
68+
using TimerCallback = std::function<void()>;
69+
using TimerCallbackArg = std::function<void(void*)>;
70+
71+
// Convenience functions with type aliases
72+
inline void timerAttachInterrupt(hw_timer_t *timer, const TimerCallback& callback) {
73+
timerAttachInterrupt(timer, callback);
74+
}
75+
76+
#endif /* __cplusplus */
77+
78+
#endif /* SOC_GPTIMER_SUPPORTED */

libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino

Lines changed: 45 additions & 208 deletions
Original file line numberDiff line numberDiff line change
@@ -19,98 +19,53 @@
1919
========================================
2020
2121
This example demonstrates how to use lambda functions with FunctionalInterrupt
22-
for GPIO pin interrupt callbacks on ESP32. It shows different lambda patterns
23-
and capture techniques for interrupt handling with debouncing.
22+
for GPIO pin interrupt callbacks on ESP32. It shows CHANGE mode detection
23+
with LED toggle functionality and proper debouncing.
2424
2525
Hardware Setup:
26-
- Connect a button between GPIO 4 (BUTTON_PIN) and GND (with internal pullup)
27-
- Connect an LED with resistor to GPIO 2 (LED_PIN or use the built-in LED available on most boards)
28-
- Optionally connect a second button (BUTTON2_PIN) to another pin in case that there is no BOOT pin button available
26+
- Use BOOT Button or connect a button between BUTTON_PIN and GND (with internal pullup)
27+
- Use Builtin Board LED or connect an LED with resistor to GPIO 2 (LED_PIN)
2928
3029
Features Demonstrated:
31-
1. CHANGE mode lambda to handle both RISING and FALLING edges on same pin
32-
2. Lambda function with captured variables (pointers)
33-
3. Object method calls integrated within lambda functions
34-
4. Edge type detection using digitalRead() within ISR
35-
5. Hardware debouncing with configurable timeout
36-
6. Best practices for interrupt-safe lambda functions
37-
30+
1. CHANGE mode lambda to detect both RISING and FALLING edges
31+
2. LED toggle on button press (FALLING edge)
32+
3. Edge type detection using digitalRead() within ISR
33+
4. Hardware debouncing with configurable timeout
34+
3835
IMPORTANT NOTE ABOUT ESP32 INTERRUPT BEHAVIOR:
3936
- Only ONE interrupt handler can be attached per GPIO pin at a time
4037
- Calling attachInterrupt() on a pin that already has an interrupt will override the previous one
4138
- This applies regardless of edge type (RISING, FALLING, CHANGE)
4239
- If you need both RISING and FALLING detection on the same pin, use CHANGE mode
4340
and determine the edge type within your handler by reading the pin state
44-
45-
For detailed documentation, patterns, and best practices, see README.md
46-
47-
Note: This example uses proper pointer captures for static/global variables
48-
to avoid compiler warnings about non-automatic storage duration.
4941
*/
5042

5143
#include <Arduino.h>
5244
#include <FunctionalInterrupt.h>
5345

5446
// Pin definitions
55-
#define BUTTON_PIN 4 // Button pin (GPIO 4) - change as needed
56-
#define BUTTON2_PIN BOOT_PIN // BOOT BUTTON - change as needed
47+
#define BUTTON_PIN BOOT_PIN // BOOT BUTTON - change as needed
5748
#ifdef LED_BUILTIN
5849
#define LED_PIN LED_BUILTIN
5950
#else
6051
#warning Using LED_PIN = GPIO 2 as default - change as needed
6152
#define LED_PIN 2 // change as needed
6253
#endif
6354

64-
65-
// Global variables for interrupt counters (volatile for ISR safety)
55+
// Global variables for interrupt handling (volatile for ISR safety)
6656
volatile uint32_t buttonPressCount = 0;
67-
volatile uint32_t buttonReleaseCount = 0; // Track button releases separately
68-
volatile uint32_t button2PressCount = 0;
57+
volatile uint32_t buttonReleaseCount = 0;
6958
volatile bool buttonPressed = false;
70-
volatile bool buttonReleased = false; // Flag for button release events
71-
volatile bool button2Pressed = false;
59+
volatile bool buttonReleased = false;
7260
volatile bool ledState = false;
7361
volatile bool ledStateChanged = false; // Flag to indicate LED needs updating
7462

75-
// Variables to demonstrate lambda captures
76-
volatile uint32_t totalInterrupts = 0;
77-
volatile unsigned long lastInterruptTime = 0;
78-
7963
// Debouncing variables (volatile for ISR safety)
80-
volatile unsigned long lastButton1InterruptTime = 0;
81-
volatile unsigned long lastButton2InterruptTime = 0;
64+
volatile unsigned long lastButtonInterruptTime = 0;
8265
const unsigned long DEBOUNCE_DELAY_MS = 50; // 50ms debounce delay
8366

8467
// State-based debouncing to prevent hysteresis issues
85-
volatile bool lastButton1State = HIGH; // Track last stable state (HIGH = released)
86-
volatile bool lastButton2State = HIGH; // Track last stable state (HIGH = released)
87-
88-
// Class example for demonstrating lambda with object methods
89-
class InterruptHandler {
90-
public:
91-
volatile uint32_t objectPressCount = 0;
92-
volatile bool stateChanged = false;
93-
String name;
94-
95-
InterruptHandler(const String& handlerName) : name(handlerName) {}
96-
97-
void handleButtonPress() {
98-
uint32_t temp = objectPressCount;
99-
temp++;
100-
objectPressCount = temp;
101-
stateChanged = true;
102-
}
103-
104-
void printStatus() {
105-
if (stateChanged) {
106-
Serial.printf("Handler '%s': Press count = %lu\r\n", name.c_str(), objectPressCount);
107-
stateChanged = false;
108-
}
109-
}
110-
};
111-
112-
// Global handler instance for object method example
113-
static InterruptHandler globalHandler("ButtonHandler");
68+
volatile bool lastButtonState = HIGH; // Track last stable state (HIGH = released)
11469

11570
void setup() {
11671
Serial.begin(115200);
@@ -121,197 +76,79 @@ void setup() {
12176

12277
// Configure pins
12378
pinMode(BUTTON_PIN, INPUT_PULLUP);
124-
pinMode(BUTTON2_PIN, INPUT_PULLUP);
12579
pinMode(LED_PIN, OUTPUT);
12680
digitalWrite(LED_PIN, LOW);
12781

128-
// Example 1: CHANGE mode lambda to handle both RISING and FALLING edges
129-
// This demonstrates how to properly handle both edges on the same pin
130-
// Includes: object method calls, pointer captures, and state-based debouncing
131-
Serial.println("Setting up Example 1: CHANGE mode lambda for both edges");
132-
133-
// Create pointers for safe capture (avoiding non-automatic storage duration warnings)
134-
InterruptHandler* handlerPtr = &globalHandler;
135-
volatile unsigned long* lastButton1TimePtr = &lastButton1InterruptTime;
136-
volatile bool* lastButton1StatePtr = &lastButton1State;
137-
const unsigned long* debounceDelayPtr = &DEBOUNCE_DELAY_MS;
82+
// CHANGE mode lambda to handle both RISING and FALLING edges
83+
// This toggles the LED on button press (FALLING edge)
84+
Serial.println("Setting up CHANGE mode lambda for LED toggle");
13885

139-
std::function<void()> changeModeLambda = [handlerPtr, lastButton1TimePtr, lastButton1StatePtr, debounceDelayPtr]() {
140-
// Debouncing: check if enough time has passed since last interrupt
86+
// Simplified lambda with minimal captures
87+
std::function<void()> changeModeLambda = []() {
88+
// Simple debouncing: check if enough time has passed since last interrupt
14189
unsigned long currentTime = millis();
142-
if (currentTime - (*lastButton1TimePtr) < (*debounceDelayPtr)) {
90+
if (currentTime - lastButtonInterruptTime < DEBOUNCE_DELAY_MS) {
14391
return; // Ignore this interrupt due to bouncing
14492
}
14593

14694
// Read current pin state to determine edge type
14795
bool currentState = digitalRead(BUTTON_PIN);
14896

14997
// State-based debouncing: only process if state actually changed
150-
if (currentState == (*lastButton1StatePtr)) {
98+
if (currentState == lastButtonState) {
15199
return; // No real state change, ignore (hysteresis/noise)
152100
}
153101

154102
// Update timing and state
155-
(*lastButton1TimePtr) = currentTime;
156-
(*lastButton1StatePtr) = currentState;
103+
lastButtonInterruptTime = currentTime;
104+
lastButtonState = currentState;
157105

158106
if (currentState == LOW) {
159-
// FALLING edge detected (button pressed)
160-
uint32_t temp = buttonPressCount;
161-
temp++;
162-
buttonPressCount = temp;
107+
// FALLING edge detected (button pressed) - set flag for main loop
108+
buttonPressCount++;
163109
buttonPressed = true;
164-
165-
// Call object method for press events
166-
handlerPtr->handleButtonPress();
110+
ledStateChanged = true; // Signal main loop to toggle LED
167111
} else {
168-
// RISING edge detected (button released)
169-
uint32_t temp = buttonReleaseCount;
170-
temp++;
171-
buttonReleaseCount = temp;
112+
// RISING edge detected (button released) - set flag for main loop
113+
buttonReleaseCount++;
172114
buttonReleased = true;
173-
174-
// Object method calls can be different for release events
175-
// For demonstration, we'll call the same method but could be different
176-
handlerPtr->handleButtonPress();
177115
}
178116
};
117+
179118
attachInterrupt(BUTTON_PIN, changeModeLambda, CHANGE);
180119

181-
// Example 2: Lambda with captured variables (Pointer Captures)
182-
// This demonstrates safe capture of global variables via pointers
183-
// NOTE: We avoid calling digitalWrite() directly in ISR to prevent FreeRTOS scheduler issues
184-
Serial.println("Setting up Example 2: Lambda with pointer captures");
185-
186-
// Create pointers to avoid capturing static/global variables directly
187-
volatile uint32_t* totalInterruptsPtr = &totalInterrupts;
188-
volatile unsigned long* lastInterruptTimePtr = &lastInterruptTime;
189-
volatile bool* ledStatePtr = &ledState;
190-
volatile bool* ledStateChangedPtr = &ledStateChanged;
191-
volatile unsigned long* lastButton2TimePtr = &lastButton2InterruptTime;
192-
volatile bool* lastButton2StatePtr = &lastButton2State;
193-
194-
std::function<void()> captureLambda = [totalInterruptsPtr, lastInterruptTimePtr, ledStatePtr, ledStateChangedPtr, lastButton2TimePtr, lastButton2StatePtr, debounceDelayPtr]() {
195-
// Debouncing: check if enough time has passed since last interrupt
196-
unsigned long currentTime = millis();
197-
if (currentTime - (*lastButton2TimePtr) < (*debounceDelayPtr)) {
198-
return; // Ignore this interrupt due to bouncing
199-
}
200-
201-
// Read current pin state and check for real state change
202-
bool currentState = digitalRead(BUTTON2_PIN);
203-
204-
// State-based debouncing: only process if state actually changed to LOW (pressed)
205-
// and the last state was HIGH (released)
206-
if (currentState != LOW || (*lastButton2StatePtr) != HIGH) {
207-
return; // Not a valid press event, ignore
208-
}
209-
210-
// Update timing and state
211-
(*lastButton2TimePtr) = currentTime;
212-
(*lastButton2StatePtr) = currentState;
213-
214-
// Update button press count
215-
uint32_t temp = button2PressCount;
216-
temp++;
217-
button2PressCount = temp;
218-
button2Pressed = true;
219-
220-
// Update captured variables via pointers
221-
(*totalInterruptsPtr)++;
222-
(*lastInterruptTimePtr) = currentTime;
223-
224-
// Toggle LED state and set flag for main loop to handle
225-
(*ledStatePtr) = !(*ledStatePtr);
226-
(*ledStateChangedPtr) = true; // Signal main loop to update LED
227-
};
228-
attachInterrupt(BUTTON2_PIN, captureLambda, FALLING);
229-
230120
Serial.println();
231-
Serial.println("Lambda interrupts configured:");
232-
Serial.printf("- Button 1 (Pin %d): CHANGE mode lambda (handles both press and release)\r\n", BUTTON_PIN);
233-
Serial.printf("- Button 2 (Pin %d): FALLING mode lambda with LED control\r\n", BUTTON2_PIN);
234-
Serial.printf("- Debounce delay: %lu ms for both buttons\r\n", DEBOUNCE_DELAY_MS);
121+
Serial.printf("Lambda interrupt configured on Pin %d (CHANGE mode)\r\n", BUTTON_PIN);
122+
Serial.printf("Debounce delay: %lu ms\r\n", DEBOUNCE_DELAY_MS);
235123
Serial.println();
236-
Serial.println("Press and release the buttons to see lambda interrupts in action!");
237-
Serial.println("Button 1 will detect both press (FALLING) and release (RISING) events.");
238-
Serial.println("Button 2 (FALLING only) will toggle the LED.");
239-
Serial.println("Both buttons include debouncing to prevent mechanical bounce issues.");
124+
Serial.println("Press the button to toggle the LED!");
125+
Serial.println("Button press (FALLING edge) will toggle the LED.");
126+
Serial.println("Button release (RISING edge) will be detected and reported.");
127+
Serial.println("Button includes debouncing to prevent mechanical bounce issues.");
240128
Serial.println();
241129
}
242130

243131
void loop() {
244-
static unsigned long lastPrintTime = 0;
245-
static uint32_t lastButton1PressCount = 0;
246-
static uint32_t lastButton1ReleaseCount = 0;
247-
static uint32_t lastButton2Count = 0;
248-
249132
// Handle LED state changes (ISR-safe approach)
250133
if (ledStateChanged) {
251134
ledStateChanged = false;
135+
ledState = !ledState; // Toggle LED state in main loop
252136
digitalWrite(LED_PIN, ledState);
253137
}
254138

255-
// Update button states in main loop (for proper state tracking)
256-
// This helps prevent hysteresis issues by updating state when buttons are actually released
257-
static bool lastButton2Reading = HIGH;
258-
bool currentButton2Reading = digitalRead(BUTTON2_PIN);
259-
if (currentButton2Reading == HIGH && lastButton2Reading == LOW) {
260-
// Button 2 was released, update state
261-
lastButton2State = HIGH;
262-
}
263-
lastButton2Reading = currentButton2Reading;
264-
265-
// Check for button 1 presses and releases (CHANGE mode lambda)
139+
// Check for button presses
266140
if (buttonPressed) {
267141
buttonPressed = false;
268-
Serial.printf("==> Button 1 PRESSED! Count: %lu (FALLING edge detected)\r\n", buttonPressCount);
142+
Serial.printf("==> Button PRESSED! Count: %lu, LED: %s (FALLING edge)\r\n",
143+
buttonPressCount, ledState ? "ON" : "OFF");
269144
}
270145

146+
// Check for button releases
271147
if (buttonReleased) {
272148
buttonReleased = false;
273-
Serial.printf("==> Button 1 RELEASED! Count: %lu (RISING edge detected)\r\n", buttonReleaseCount);
149+
Serial.printf("==> Button RELEASED! Count: %lu (RISING edge)\r\n",
150+
buttonReleaseCount);
274151
}
275152

276-
// Check for button 2 presses (capture lambda)
277-
if (button2Pressed) {
278-
button2Pressed = false;
279-
Serial.printf("==> Button 2 pressed! Count: %lu, LED: %s (Capture lambda)\r\n",
280-
button2PressCount, ledState ? "ON" : "OFF");
281-
}
282-
283-
// Check object handler status (object method lambda)
284-
globalHandler.printStatus();
285-
286-
// Print statistics every 5 seconds if there's been activity
287-
if (millis() - lastPrintTime >= 5000) {
288-
lastPrintTime = millis();
289-
290-
bool hasActivity = (buttonPressCount != lastButton1PressCount ||
291-
buttonReleaseCount != lastButton1ReleaseCount ||
292-
button2PressCount != lastButton2Count);
293-
294-
if (hasActivity) {
295-
Serial.println("============================");
296-
Serial.println("Lambda Interrupt Statistics:");
297-
Serial.println("============================");
298-
Serial.printf("Button 1 presses: %8lu\r\n", buttonPressCount);
299-
Serial.printf("Button 1 releases: %8lu\r\n", buttonReleaseCount);
300-
Serial.printf("Button 2 presses: %8lu\r\n", button2PressCount);
301-
Serial.printf("Object handler calls: %8lu\r\n", globalHandler.objectPressCount);
302-
Serial.printf("Total interrupts: %8lu\r\n", totalInterrupts);
303-
Serial.printf("LED state: %8s\r\n", ledState ? "ON" : "OFF");
304-
if (lastInterruptTime > 0) {
305-
Serial.printf("Last interrupt: %8lu ms ago\r\n", millis() - lastInterruptTime);
306-
}
307-
Serial.println();
308-
309-
lastButton1PressCount = buttonPressCount;
310-
lastButton1ReleaseCount = buttonReleaseCount;
311-
lastButton2Count = button2PressCount;
312-
}
313-
}
314-
315-
// Small delay to prevent overwhelming the serial output
316153
delay(10);
317-
}
154+
}

0 commit comments

Comments
 (0)