19
19
========================================
20
20
21
21
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.
24
24
25
25
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)
29
28
30
29
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
+
38
35
IMPORTANT NOTE ABOUT ESP32 INTERRUPT BEHAVIOR:
39
36
- Only ONE interrupt handler can be attached per GPIO pin at a time
40
37
- Calling attachInterrupt() on a pin that already has an interrupt will override the previous one
41
38
- This applies regardless of edge type (RISING, FALLING, CHANGE)
42
39
- If you need both RISING and FALLING detection on the same pin, use CHANGE mode
43
40
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.
49
41
*/
50
42
51
43
#include < Arduino.h>
52
44
#include < FunctionalInterrupt.h>
53
45
54
46
// 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
57
48
#ifdef LED_BUILTIN
58
49
#define LED_PIN LED_BUILTIN
59
50
#else
60
51
#warning Using LED_PIN = GPIO 2 as default - change as needed
61
52
#define LED_PIN 2 // change as needed
62
53
#endif
63
54
64
-
65
- // Global variables for interrupt counters (volatile for ISR safety)
55
+ // Global variables for interrupt handling (volatile for ISR safety)
66
56
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 ;
69
58
volatile bool buttonPressed = false ;
70
- volatile bool buttonReleased = false ; // Flag for button release events
71
- volatile bool button2Pressed = false ;
59
+ volatile bool buttonReleased = false ;
72
60
volatile bool ledState = false ;
73
61
volatile bool ledStateChanged = false ; // Flag to indicate LED needs updating
74
62
75
- // Variables to demonstrate lambda captures
76
- volatile uint32_t totalInterrupts = 0 ;
77
- volatile unsigned long lastInterruptTime = 0 ;
78
-
79
63
// Debouncing variables (volatile for ISR safety)
80
- volatile unsigned long lastButton1InterruptTime = 0 ;
81
- volatile unsigned long lastButton2InterruptTime = 0 ;
64
+ volatile unsigned long lastButtonInterruptTime = 0 ;
82
65
const unsigned long DEBOUNCE_DELAY_MS = 50 ; // 50ms debounce delay
83
66
84
67
// 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)
114
69
115
70
void setup () {
116
71
Serial.begin (115200 );
@@ -121,197 +76,79 @@ void setup() {
121
76
122
77
// Configure pins
123
78
pinMode (BUTTON_PIN, INPUT_PULLUP);
124
- pinMode (BUTTON2_PIN, INPUT_PULLUP);
125
79
pinMode (LED_PIN, OUTPUT);
126
80
digitalWrite (LED_PIN, LOW);
127
81
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" );
138
85
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
141
89
unsigned long currentTime = millis ();
142
- if (currentTime - (*lastButton1TimePtr) < (*debounceDelayPtr) ) {
90
+ if (currentTime - lastButtonInterruptTime < DEBOUNCE_DELAY_MS ) {
143
91
return ; // Ignore this interrupt due to bouncing
144
92
}
145
93
146
94
// Read current pin state to determine edge type
147
95
bool currentState = digitalRead (BUTTON_PIN);
148
96
149
97
// State-based debouncing: only process if state actually changed
150
- if (currentState == (*lastButton1StatePtr) ) {
98
+ if (currentState == lastButtonState ) {
151
99
return ; // No real state change, ignore (hysteresis/noise)
152
100
}
153
101
154
102
// Update timing and state
155
- (*lastButton1TimePtr) = currentTime;
156
- (*lastButton1StatePtr) = currentState;
103
+ lastButtonInterruptTime = currentTime;
104
+ lastButtonState = currentState;
157
105
158
106
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++;
163
109
buttonPressed = true ;
164
-
165
- // Call object method for press events
166
- handlerPtr->handleButtonPress ();
110
+ ledStateChanged = true ; // Signal main loop to toggle LED
167
111
} 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++;
172
114
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 ();
177
115
}
178
116
};
117
+
179
118
attachInterrupt (BUTTON_PIN, changeModeLambda, CHANGE);
180
119
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
-
230
120
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);
235
123
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." );
240
128
Serial.println ();
241
129
}
242
130
243
131
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
-
249
132
// Handle LED state changes (ISR-safe approach)
250
133
if (ledStateChanged) {
251
134
ledStateChanged = false ;
135
+ ledState = !ledState; // Toggle LED state in main loop
252
136
digitalWrite (LED_PIN, ledState);
253
137
}
254
138
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
266
140
if (buttonPressed) {
267
141
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" );
269
144
}
270
145
146
+ // Check for button releases
271
147
if (buttonReleased) {
272
148
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);
274
151
}
275
152
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
316
153
delay (10 );
317
- }
154
+ }
0 commit comments