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)
6656volatile 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 ;
6958volatile bool buttonPressed = false ;
70- volatile bool buttonReleased = false ; // Flag for button release events
71- volatile bool button2Pressed = false ;
59+ volatile bool buttonReleased = false ;
7260volatile bool ledState = false ;
7361volatile 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 ;
8265const 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
11570void 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
243131void 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