1
+ /*
2
+ SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3
+
4
+ SPDX-License-Identifier: Apache-2.0
5
+
6
+ Licensed under the Apache License, Version 2.0 (the "License");
7
+ you may not use this file except in compliance with the License.
8
+ You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing, software
13
+ distributed under the License is distributed on an "AS IS" BASIS,
14
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ See the License for the specific language governing permissions and
16
+ limitations under the License.
17
+
18
+ ESP32 Lambda FunctionalInterrupt Example
19
+ ========================================
20
+
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.
24
+
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
29
+
30
+ 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
+
38
+ IMPORTANT NOTE ABOUT ESP32 INTERRUPT BEHAVIOR:
39
+ - Only ONE interrupt handler can be attached per GPIO pin at a time
40
+ - Calling attachInterrupt() on a pin that already has an interrupt will override the previous one
41
+ - This applies regardless of edge type (RISING, FALLING, CHANGE)
42
+ - If you need both RISING and FALLING detection on the same pin, use CHANGE mode
43
+ 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
+ */
50
+
51
+ #include < Arduino.h>
52
+ #include < FunctionalInterrupt.h>
53
+
54
+ // 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
57
+ #ifdef LED_BUILTIN
58
+ #define LED_PIN LED_BUILTIN
59
+ #else
60
+ #warning Using LED_PIN = GPIO 2 as default - change as needed
61
+ #define LED_PIN 2 // change as needed
62
+ #endif
63
+
64
+
65
+ // Global variables for interrupt counters (volatile for ISR safety)
66
+ volatile uint32_t buttonPressCount = 0 ;
67
+ volatile uint32_t buttonReleaseCount = 0 ; // Track button releases separately
68
+ volatile uint32_t button2PressCount = 0 ;
69
+ volatile bool buttonPressed = false ;
70
+ volatile bool buttonReleased = false ; // Flag for button release events
71
+ volatile bool button2Pressed = false ;
72
+ volatile bool ledState = false ;
73
+ volatile bool ledStateChanged = false ; // Flag to indicate LED needs updating
74
+
75
+ // Variables to demonstrate lambda captures
76
+ uint32_t totalInterrupts = 0 ;
77
+ unsigned long lastInterruptTime = 0 ;
78
+
79
+ // Debouncing variables (volatile for ISR safety)
80
+ volatile unsigned long lastButton1InterruptTime = 0 ;
81
+ volatile unsigned long lastButton2InterruptTime = 0 ;
82
+ const unsigned long DEBOUNCE_DELAY_MS = 50 ; // 50ms debounce delay
83
+
84
+ // 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" );
114
+
115
+ void setup () {
116
+ Serial.begin (115200 );
117
+ delay (1000 ); // Allow serial monitor to connect
118
+
119
+ Serial.println (" ESP32 Lambda FunctionalInterrupt Example" );
120
+ Serial.println (" ========================================" );
121
+
122
+ // Configure pins
123
+ pinMode (BUTTON_PIN, INPUT_PULLUP);
124
+ pinMode (BUTTON2_PIN, INPUT_PULLUP);
125
+ pinMode (LED_PIN, OUTPUT);
126
+ digitalWrite (LED_PIN, LOW);
127
+
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;
138
+
139
+ std::function<void ()> changeModeLambda = [handlerPtr, lastButton1TimePtr, lastButton1StatePtr, debounceDelayPtr]() {
140
+ // Debouncing: check if enough time has passed since last interrupt
141
+ unsigned long currentTime = millis ();
142
+ if (currentTime - (*lastButton1TimePtr) < (*debounceDelayPtr)) {
143
+ return ; // Ignore this interrupt due to bouncing
144
+ }
145
+
146
+ // Read current pin state to determine edge type
147
+ bool currentState = digitalRead (BUTTON_PIN);
148
+
149
+ // State-based debouncing: only process if state actually changed
150
+ if (currentState == (*lastButton1StatePtr)) {
151
+ return ; // No real state change, ignore (hysteresis/noise)
152
+ }
153
+
154
+ // Update timing and state
155
+ (*lastButton1TimePtr) = currentTime;
156
+ (*lastButton1StatePtr) = currentState;
157
+
158
+ if (currentState == LOW) {
159
+ // FALLING edge detected (button pressed)
160
+ uint32_t temp = buttonPressCount;
161
+ temp++;
162
+ buttonPressCount = temp;
163
+ buttonPressed = true ;
164
+
165
+ // Call object method for press events
166
+ handlerPtr->handleButtonPress ();
167
+ } else {
168
+ // RISING edge detected (button released)
169
+ uint32_t temp = buttonReleaseCount;
170
+ temp++;
171
+ buttonReleaseCount = temp;
172
+ 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
+ }
178
+ };
179
+ attachInterrupt (BUTTON_PIN, changeModeLambda, CHANGE);
180
+
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
+ uint32_t * totalInterruptsPtr = &totalInterrupts;
188
+ 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) = millis ();
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
+ 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);
235
+ 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." );
240
+ Serial.println ();
241
+ }
242
+
243
+ 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
+ // Handle LED state changes (ISR-safe approach)
250
+ if (ledStateChanged) {
251
+ ledStateChanged = false ;
252
+ digitalWrite (LED_PIN, ledState);
253
+ }
254
+
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)
266
+ if (buttonPressed) {
267
+ buttonPressed = false ;
268
+ Serial.printf (" ==> Button 1 PRESSED! Count: %lu (FALLING edge detected)\r\n " , buttonPressCount);
269
+ }
270
+
271
+ if (buttonReleased) {
272
+ buttonReleased = false ;
273
+ Serial.printf (" ==> Button 1 RELEASED! Count: %lu (RISING edge detected)\r\n " , buttonReleaseCount);
274
+ }
275
+
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
+ delay (10 );
317
+ }
0 commit comments