Skip to content

Commit c0feacc

Browse files
committed
feat(gpio): new functional interrupt lambda example
1 parent 0a45a06 commit c0feacc

File tree

2 files changed

+528
-0
lines changed

2 files changed

+528
-0
lines changed
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
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

Comments
 (0)