From c0feacc6978f848d063c2614e3e1f0929b19407b Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Sat, 12 Jul 2025 18:01:38 -0300 Subject: [PATCH 01/10] feat(gpio): new functional interrupt lambda example --- .../FunctionalInterruptLambda.ino | 317 ++++++++++++++++++ .../GPIO/FunctionalInterruptLambda/README.md | 211 ++++++++++++ 2 files changed, 528 insertions(+) create mode 100644 libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino create mode 100644 libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino new file mode 100644 index 00000000000..96d717181f3 --- /dev/null +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino @@ -0,0 +1,317 @@ +/* + SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + + SPDX-License-Identifier: Apache-2.0 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + ESP32 Lambda FunctionalInterrupt Example + ======================================== + + This example demonstrates how to use lambda functions with FunctionalInterrupt + for GPIO pin interrupt callbacks on ESP32. It shows different lambda patterns + and capture techniques for interrupt handling with debouncing. + + Hardware Setup: + - Connect a button between GPIO 4 (BUTTON_PIN) and GND (with internal pullup) + - Connect an LED with resistor to GPIO 2 (LED_PIN or use the built-in LED available on most boards) + - Optionally connect a second button (BUTTON2_PIN) to another pin in case that there is no BOOT pin button available + + Features Demonstrated: + 1. CHANGE mode lambda to handle both RISING and FALLING edges on same pin + 2. Lambda function with captured variables (pointers) + 3. Object method calls integrated within lambda functions + 4. Edge type detection using digitalRead() within ISR + 5. Hardware debouncing with configurable timeout + 6. Best practices for interrupt-safe lambda functions + + IMPORTANT NOTE ABOUT ESP32 INTERRUPT BEHAVIOR: + - Only ONE interrupt handler can be attached per GPIO pin at a time + - Calling attachInterrupt() on a pin that already has an interrupt will override the previous one + - This applies regardless of edge type (RISING, FALLING, CHANGE) + - If you need both RISING and FALLING detection on the same pin, use CHANGE mode + and determine the edge type within your handler by reading the pin state + + For detailed documentation, patterns, and best practices, see README.md + + Note: This example uses proper pointer captures for static/global variables + to avoid compiler warnings about non-automatic storage duration. +*/ + +#include +#include + +// Pin definitions +#define BUTTON_PIN 4 // Button pin (GPIO 4) - change as needed +#define BUTTON2_PIN BOOT_PIN // BOOT BUTTON - change as needed +#ifdef LED_BUILTIN +#define LED_PIN LED_BUILTIN +#else +#warning Using LED_PIN = GPIO 2 as default - change as needed +#define LED_PIN 2 // change as needed +#endif + + +// Global variables for interrupt counters (volatile for ISR safety) +volatile uint32_t buttonPressCount = 0; +volatile uint32_t buttonReleaseCount = 0; // Track button releases separately +volatile uint32_t button2PressCount = 0; +volatile bool buttonPressed = false; +volatile bool buttonReleased = false; // Flag for button release events +volatile bool button2Pressed = false; +volatile bool ledState = false; +volatile bool ledStateChanged = false; // Flag to indicate LED needs updating + +// Variables to demonstrate lambda captures +uint32_t totalInterrupts = 0; +unsigned long lastInterruptTime = 0; + +// Debouncing variables (volatile for ISR safety) +volatile unsigned long lastButton1InterruptTime = 0; +volatile unsigned long lastButton2InterruptTime = 0; +const unsigned long DEBOUNCE_DELAY_MS = 50; // 50ms debounce delay + +// State-based debouncing to prevent hysteresis issues +volatile bool lastButton1State = HIGH; // Track last stable state (HIGH = released) +volatile bool lastButton2State = HIGH; // Track last stable state (HIGH = released) + +// Class example for demonstrating lambda with object methods +class InterruptHandler { + public: + volatile uint32_t objectPressCount = 0; + volatile bool stateChanged = false; + String name; + + InterruptHandler(const String& handlerName) : name(handlerName) {} + + void handleButtonPress() { + uint32_t temp = objectPressCount; + temp++; + objectPressCount = temp; + stateChanged = true; + } + + void printStatus() { + if (stateChanged) { + Serial.printf("Handler '%s': Press count = %lu\r\n", name.c_str(), objectPressCount); + stateChanged = false; + } + } +}; + +// Global handler instance for object method example +static InterruptHandler globalHandler("ButtonHandler"); + +void setup() { + Serial.begin(115200); + delay(1000); // Allow serial monitor to connect + + Serial.println("ESP32 Lambda FunctionalInterrupt Example"); + Serial.println("========================================"); + + // Configure pins + pinMode(BUTTON_PIN, INPUT_PULLUP); + pinMode(BUTTON2_PIN, INPUT_PULLUP); + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + // Example 1: CHANGE mode lambda to handle both RISING and FALLING edges + // This demonstrates how to properly handle both edges on the same pin + // Includes: object method calls, pointer captures, and state-based debouncing + Serial.println("Setting up Example 1: CHANGE mode lambda for both edges"); + + // Create pointers for safe capture (avoiding non-automatic storage duration warnings) + InterruptHandler* handlerPtr = &globalHandler; + volatile unsigned long* lastButton1TimePtr = &lastButton1InterruptTime; + volatile bool* lastButton1StatePtr = &lastButton1State; + const unsigned long* debounceDelayPtr = &DEBOUNCE_DELAY_MS; + + std::function changeModeLambda = [handlerPtr, lastButton1TimePtr, lastButton1StatePtr, debounceDelayPtr]() { + // Debouncing: check if enough time has passed since last interrupt + unsigned long currentTime = millis(); + if (currentTime - (*lastButton1TimePtr) < (*debounceDelayPtr)) { + return; // Ignore this interrupt due to bouncing + } + + // Read current pin state to determine edge type + bool currentState = digitalRead(BUTTON_PIN); + + // State-based debouncing: only process if state actually changed + if (currentState == (*lastButton1StatePtr)) { + return; // No real state change, ignore (hysteresis/noise) + } + + // Update timing and state + (*lastButton1TimePtr) = currentTime; + (*lastButton1StatePtr) = currentState; + + if (currentState == LOW) { + // FALLING edge detected (button pressed) + uint32_t temp = buttonPressCount; + temp++; + buttonPressCount = temp; + buttonPressed = true; + + // Call object method for press events + handlerPtr->handleButtonPress(); + } else { + // RISING edge detected (button released) + uint32_t temp = buttonReleaseCount; + temp++; + buttonReleaseCount = temp; + buttonReleased = true; + + // Object method calls can be different for release events + // For demonstration, we'll call the same method but could be different + handlerPtr->handleButtonPress(); + } + }; + attachInterrupt(BUTTON_PIN, changeModeLambda, CHANGE); + + // Example 2: Lambda with captured variables (Pointer Captures) + // This demonstrates safe capture of global variables via pointers + // NOTE: We avoid calling digitalWrite() directly in ISR to prevent FreeRTOS scheduler issues + Serial.println("Setting up Example 2: Lambda with pointer captures"); + + // Create pointers to avoid capturing static/global variables directly + uint32_t* totalInterruptsPtr = &totalInterrupts; + unsigned long* lastInterruptTimePtr = &lastInterruptTime; + volatile bool* ledStatePtr = &ledState; + volatile bool* ledStateChangedPtr = &ledStateChanged; + volatile unsigned long* lastButton2TimePtr = &lastButton2InterruptTime; + volatile bool* lastButton2StatePtr = &lastButton2State; + + std::function captureLambda = [totalInterruptsPtr, lastInterruptTimePtr, ledStatePtr, ledStateChangedPtr, lastButton2TimePtr, lastButton2StatePtr, debounceDelayPtr]() { + // Debouncing: check if enough time has passed since last interrupt + unsigned long currentTime = millis(); + if (currentTime - (*lastButton2TimePtr) < (*debounceDelayPtr)) { + return; // Ignore this interrupt due to bouncing + } + + // Read current pin state and check for real state change + bool currentState = digitalRead(BUTTON2_PIN); + + // State-based debouncing: only process if state actually changed to LOW (pressed) + // and the last state was HIGH (released) + if (currentState != LOW || (*lastButton2StatePtr) != HIGH) { + return; // Not a valid press event, ignore + } + + // Update timing and state + (*lastButton2TimePtr) = currentTime; + (*lastButton2StatePtr) = currentState; + + // Update button press count + uint32_t temp = button2PressCount; + temp++; + button2PressCount = temp; + button2Pressed = true; + + // Update captured variables via pointers + (*totalInterruptsPtr)++; + (*lastInterruptTimePtr) = millis(); + + // Toggle LED state and set flag for main loop to handle + (*ledStatePtr) = !(*ledStatePtr); + (*ledStateChangedPtr) = true; // Signal main loop to update LED + }; + attachInterrupt(BUTTON2_PIN, captureLambda, FALLING); + + Serial.println(); + Serial.println("Lambda interrupts configured:"); + Serial.printf("- Button 1 (Pin %d): CHANGE mode lambda (handles both press and release)\r\n", BUTTON_PIN); + Serial.printf("- Button 2 (Pin %d): FALLING mode lambda with LED control\r\n", BUTTON2_PIN); + Serial.printf("- Debounce delay: %lu ms for both buttons\r\n", DEBOUNCE_DELAY_MS); + Serial.println(); + Serial.println("Press and release the buttons to see lambda interrupts in action!"); + Serial.println("Button 1 will detect both press (FALLING) and release (RISING) events."); + Serial.println("Button 2 (FALLING only) will toggle the LED."); + Serial.println("Both buttons include debouncing to prevent mechanical bounce issues."); + Serial.println(); +} + +void loop() { + static unsigned long lastPrintTime = 0; + static uint32_t lastButton1PressCount = 0; + static uint32_t lastButton1ReleaseCount = 0; + static uint32_t lastButton2Count = 0; + + // Handle LED state changes (ISR-safe approach) + if (ledStateChanged) { + ledStateChanged = false; + digitalWrite(LED_PIN, ledState); + } + + // Update button states in main loop (for proper state tracking) + // This helps prevent hysteresis issues by updating state when buttons are actually released + static bool lastButton2Reading = HIGH; + bool currentButton2Reading = digitalRead(BUTTON2_PIN); + if (currentButton2Reading == HIGH && lastButton2Reading == LOW) { + // Button 2 was released, update state + lastButton2State = HIGH; + } + lastButton2Reading = currentButton2Reading; + + // Check for button 1 presses and releases (CHANGE mode lambda) + if (buttonPressed) { + buttonPressed = false; + Serial.printf("==> Button 1 PRESSED! Count: %lu (FALLING edge detected)\r\n", buttonPressCount); + } + + if (buttonReleased) { + buttonReleased = false; + Serial.printf("==> Button 1 RELEASED! Count: %lu (RISING edge detected)\r\n", buttonReleaseCount); + } + + // Check for button 2 presses (capture lambda) + if (button2Pressed) { + button2Pressed = false; + Serial.printf("==> Button 2 pressed! Count: %lu, LED: %s (Capture lambda)\r\n", + button2PressCount, ledState ? "ON" : "OFF"); + } + + // Check object handler status (object method lambda) + globalHandler.printStatus(); + + // Print statistics every 5 seconds if there's been activity + if (millis() - lastPrintTime >= 5000) { + lastPrintTime = millis(); + + bool hasActivity = (buttonPressCount != lastButton1PressCount || + buttonReleaseCount != lastButton1ReleaseCount || + button2PressCount != lastButton2Count); + + if (hasActivity) { + Serial.println("============================"); + Serial.println("Lambda Interrupt Statistics:"); + Serial.println("============================"); + Serial.printf("Button 1 presses: %8lu\r\n", buttonPressCount); + Serial.printf("Button 1 releases: %8lu\r\n", buttonReleaseCount); + Serial.printf("Button 2 presses: %8lu\r\n", button2PressCount); + Serial.printf("Object handler calls: %8lu\r\n", globalHandler.objectPressCount); + Serial.printf("Total interrupts: %8lu\r\n", totalInterrupts); + Serial.printf("LED state: %8s\r\n", ledState ? "ON" : "OFF"); + if (lastInterruptTime > 0) { + Serial.printf("Last interrupt: %8lu ms ago\r\n", millis() - lastInterruptTime); + } + Serial.println(); + + lastButton1PressCount = buttonPressCount; + lastButton1ReleaseCount = buttonReleaseCount; + lastButton2Count = button2PressCount; + } + } + + // Small delay to prevent overwhelming the serial output + delay(10); +} \ No newline at end of file diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md new file mode 100644 index 00000000000..30f0f56b17b --- /dev/null +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md @@ -0,0 +1,211 @@ +# ESP32 Lambda FunctionalInterrupt Example + +This example demonstrates how to use lambda functions with FunctionalInterrupt for GPIO pin interrupt callbacks on ESP32. It showcases different lambda patterns and capture techniques for interrupt handling with advanced debouncing through two comprehensive examples: + +**Example 1**: CHANGE mode lambda with object method integration, state-based debouncing, and dual-edge detection +**Example 2**: FALLING mode lambda with pointer captures, LED control, and comprehensive variable capture patterns + +## Features Demonstrated + +1. **CHANGE mode lambda** to handle both RISING and FALLING edges on same pin +2. **Lambda function with captured variables** (pointers) +3. **Object method calls integrated within lambda functions** +4. **Edge type detection** using digitalRead() within ISR +5. **Hardware debouncing** with configurable timeout and anti-hysteresis +6. **Best practices** for interrupt-safe lambda functions +7. **Cross-platform terminal compatibility** (Windows, Linux, macOS) + +## Hardware Setup + +- Connect a button between GPIO 4 and GND (with internal pullup) +- Connect an LED with resistor to GPIO 2 (built-in LED on most ESP32 boards) +- Optionally connect a second button to BOOT pin for advanced examples + +``` +ESP32 Board Button 1 Button 2 (Optional) LED +─────────── ──────── ────────────────── ─── +GPIO 4 ──────────── [BUTTON] ──── GND +GPIO 0 (BOOT) ────── [BUTTON] ──── GND +GPIO 2 ──────────── [LED] ──────── GND + │ + [330Ω] + │ + 3V3 +``` + +## Important ESP32 Interrupt Behavior + +**CRITICAL:** Only ONE interrupt handler can be attached per GPIO pin at a time on ESP32. + +- Calling `attachInterrupt()` on a pin that already has an interrupt will **override** the previous one +- This applies regardless of edge type (RISING, FALLING, CHANGE) +- If you need both RISING and FALLING detection on the same pin, use **CHANGE mode** and determine the edge type within your handler by reading the pin state + +## Code Examples Overview + +This example contains **two main implementations** that demonstrate different lambda interrupt patterns: + +### Example 1: CHANGE Mode Lambda (Button 1 - GPIO 4) +- **Pattern**: Comprehensive CHANGE mode with edge detection +- **Features**: Dual-edge detection (RISING/FALLING), object method calls, state-based debouncing +- **Use Case**: Single pin handling both button press and release events +- **Lambda Type**: Complex lambda with multiple pointer captures and object interaction + +### Example 2: FALLING Mode Lambda (Button 2 - BOOT Pin) +- **Pattern**: Pointer capture lambda with LED control +- **Features**: Single-edge detection, extensive variable capture, hardware control +- **Use Case**: Traditional button press with peripheral control (LED toggle) +- **Lambda Type**: Capture-heavy lambda demonstrating safe variable access patterns + +## Lambda Function Patterns Explained + +### 1. CHANGE Mode Lambda (Recommended for Multiple Edges) +```cpp +attachInterrupt(pin, lambda, CHANGE); +``` +- Detects both RISING and FALLING edges on the same pin +- Use `digitalRead()` inside the lambda to determine edge type +- Most efficient way to handle both press and release events +- Avoids the "handler override" issue entirely + +### 2. Pointer Capture Lambda +```cpp +[ptr1, ptr2]() { *ptr1 = value; } +``` +- Captures pointers to global/static variables +- Avoids compiler warnings about non-automatic storage duration +- Allows safe modification of external variables in ISR + +### 3. Object Method Lambda +```cpp +[objectPtr]() { objectPtr->method(); } +``` +- Captures pointer to object instance +- Enables calling object methods from interrupt +- Useful for object-oriented interrupt handling + +### 4. State-Based Debouncing Pattern (Anti-Hysteresis) +```cpp +bool currentState = digitalRead(pin); +if (currentState == lastKnownState) return; // No real change +lastKnownState = currentState; +``` +- Prevents counting multiple edges from electrical noise/hysteresis +- Ensures press/release counts remain synchronized +- Tracks actual state changes rather than just edge triggers +- Critical for reliable button input with mechanical switches + +### 5. Time-Based Debouncing Pattern +```cpp +unsigned long currentTime = millis(); +if (currentTime - lastInterruptTime < DEBOUNCE_DELAY) return; +lastInterruptTime = currentTime; +``` +- Prevents multiple triggers from mechanical switch bounce +- Uses `millis()` for timing (fast and ISR-safe) +- Typical debounce delay: 20-100ms depending on switch quality + +## Edge Detection Pattern in CHANGE Mode + +```cpp +if (digitalRead(pin) == LOW) { + // FALLING edge detected (button pressed) +} else { + // RISING edge detected (button released) +} +``` + +## Best Practices for Lambda Interrupts + +✅ **Keep lambda functions short and simple** +✅ **Use volatile for variables modified in ISR** +✅ **Avoid complex operations** (delays, Serial.print, etc.) +✅ **NEVER call digitalWrite() or other GPIO functions directly in ISR** +✅ **Use flags and handle GPIO operations in main loop** +✅ **Use pointer captures for static/global variables** +✅ **Use std::function for type clarity** +✅ **Implement state-based debouncing to prevent hysteresis issues** +✅ **Track last known pin state to avoid counting noise as real edges** +✅ **Test thoroughly with real hardware** +✅ **Consider using flags and processing in loop()** + +## Lambda Capture Syntax Guide + +| Syntax | Description | +|-----------------|--------------------------------------| +| `[]` | Capture nothing | +| `[var]` | Capture var by value (copy) | +| `[&var]` | Capture var by reference (avoid!) | +| `[ptr]` | Capture pointer (recommended) | +| `[=]` | Capture all by value | +| `[&]` | Capture all by reference (avoid!) | +| `[var1, ptr2]` | Mixed captures | + +## Why Use Pointer Captures? + +• **Avoids "non-automatic storage duration" compiler warnings** +• **More explicit about what's being accessed** +• **Better performance than reference captures** +• **Safer for static and global variables** +• **Compatible with all ESP32 compiler versions** + +## Debouncing Strategy + +This example implements a **dual-layer debouncing approach**: + +1. **Time-based debouncing**: Ignores interrupts within 50ms of the previous one +2. **State-based debouncing**: Only processes interrupts that represent actual state changes + +This combination effectively eliminates: +- Mechanical switch bounce +- Electrical noise and hysteresis +- Multiple false triggers +- Synchronized press/release count mismatches + +## Expected Output + +``` +ESP32 Lambda FunctionalInterrupt Example +======================================== +Setting up Example 1: CHANGE mode lambda for both edges +Setting up Example 2: Lambda with pointer captures + +Lambda interrupts configured: +- Button 1 (Pin 4): CHANGE mode lambda (handles both press and release) +- Button 2 (Pin 0): FALLING mode lambda with LED control +- Debounce delay: 50 ms for both buttons + +Press and release the buttons to see lambda interrupts in action! +Button 1 will detect both press (FALLING) and release (RISING) events. +Button 2 (FALLING only) will toggle the LED. +Both buttons include debouncing to prevent mechanical bounce issues. + +==> Button 1 PRESSED! Count: 1 (FALLING edge detected) +Handler 'ButtonHandler': Press count = 1 +==> Button 1 RELEASED! Count: 1 (RISING edge detected) +Handler 'ButtonHandler': Press count = 2 +==> Button 2 pressed! Count: 1, LED: ON (Capture lambda) + +============================ +Lambda Interrupt Statistics: +============================ +Button 1 presses: 2 +Button 1 releases: 2 +Button 2 presses: 1 +Object handler calls: 2 +Total interrupts: 1 +LED state: ON +Last interrupt: 1234 ms ago +``` + +## Pin Configuration + +The example uses these default pins (configurable in the source): + +- `BUTTON_PIN`: GPIO 4 (change as needed) +- `BUTTON2_PIN`: BOOT_PIN (GPIO 0 on most ESP32 boards) +- `LED_PIN`: LED_BUILTIN (GPIO 2 on most ESP32 boards) + +## Compilation Notes + +This example uses proper pointer captures for static/global variables to avoid compiler warnings about non-automatic storage duration. \ No newline at end of file From a0963601c55e7ad3f4c0a3be3cd2c1a1b0bec238 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sat, 12 Jul 2025 18:16:08 -0300 Subject: [PATCH 02/10] fix(readme): schematic diagram allignment --- .../GPIO/FunctionalInterruptLambda/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md index 30f0f56b17b..fa3f2552670 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md @@ -22,15 +22,15 @@ This example demonstrates how to use lambda functions with FunctionalInterrupt f - Optionally connect a second button to BOOT pin for advanced examples ``` -ESP32 Board Button 1 Button 2 (Optional) LED -─────────── ──────── ────────────────── ─── +ESP32 Board Buttons + LED +─────────── ──────────────── GPIO 4 ──────────── [BUTTON] ──── GND -GPIO 0 (BOOT) ────── [BUTTON] ──── GND -GPIO 2 ──────────── [LED] ──────── GND - │ - [330Ω] - │ - 3V3 +GPIO 0 (BOOT) ───── [BUTTON] ──── GND +GPIO 2 ───── [R1] ─── [LED] ───── GND * R1 = resistor 220 ohms + │ + [330Ω] + │ + 3V3 ``` ## Important ESP32 Interrupt Behavior @@ -208,4 +208,4 @@ The example uses these default pins (configurable in the source): ## Compilation Notes -This example uses proper pointer captures for static/global variables to avoid compiler warnings about non-automatic storage duration. \ No newline at end of file +This example uses proper pointer captures for static/global variables to avoid compiler warnings about non-automatic storage duration. From 5756b2dcaf4d49c364db918c1d0adad069e4d3e9 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sat, 12 Jul 2025 18:18:08 -0300 Subject: [PATCH 03/10] fix(example): uses volatile for ISR variables Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../FunctionalInterruptLambda/FunctionalInterruptLambda.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino index 96d717181f3..c89bfb10314 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino @@ -73,7 +73,7 @@ volatile bool ledState = false; volatile bool ledStateChanged = false; // Flag to indicate LED needs updating // Variables to demonstrate lambda captures -uint32_t totalInterrupts = 0; +volatile uint32_t totalInterrupts = 0; unsigned long lastInterruptTime = 0; // Debouncing variables (volatile for ISR safety) From 51ff9fc6e20f5e160968f20e02313080b8a17cc3 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sat, 12 Jul 2025 18:18:19 -0300 Subject: [PATCH 04/10] fix(example): uses volatile for ISR variables Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../FunctionalInterruptLambda/FunctionalInterruptLambda.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino index c89bfb10314..f269c16f91f 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino @@ -74,7 +74,7 @@ volatile bool ledStateChanged = false; // Flag to indicate LED needs updating // Variables to demonstrate lambda captures volatile uint32_t totalInterrupts = 0; -unsigned long lastInterruptTime = 0; +volatile unsigned long lastInterruptTime = 0; // Debouncing variables (volatile for ISR safety) volatile unsigned long lastButton1InterruptTime = 0; From 71b87206c811f90e0fc6a13a3defce08dc47aae3 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sat, 12 Jul 2025 18:33:14 -0300 Subject: [PATCH 05/10] fix(example): uses volatile data type also for the pointers --- .../FunctionalInterruptLambda/FunctionalInterruptLambda.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino index f269c16f91f..e9bc35f2b47 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino @@ -184,8 +184,8 @@ void setup() { Serial.println("Setting up Example 2: Lambda with pointer captures"); // Create pointers to avoid capturing static/global variables directly - uint32_t* totalInterruptsPtr = &totalInterrupts; - unsigned long* lastInterruptTimePtr = &lastInterruptTime; + volatile uint32_t* totalInterruptsPtr = &totalInterrupts; + volatile unsigned long* lastInterruptTimePtr = &lastInterruptTime; volatile bool* ledStatePtr = &ledState; volatile bool* ledStateChangedPtr = &ledStateChanged; volatile unsigned long* lastButton2TimePtr = &lastButton2InterruptTime; From c486b4d161b7979a2739edf9588b6fed16ca9fdb Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sat, 12 Jul 2025 18:36:58 -0300 Subject: [PATCH 06/10] fix(readme): clear documentation --- .../ESP32/examples/GPIO/FunctionalInterruptLambda/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md index fa3f2552670..72749f81019 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md @@ -12,8 +12,6 @@ This example demonstrates how to use lambda functions with FunctionalInterrupt f 3. **Object method calls integrated within lambda functions** 4. **Edge type detection** using digitalRead() within ISR 5. **Hardware debouncing** with configurable timeout and anti-hysteresis -6. **Best practices** for interrupt-safe lambda functions -7. **Cross-platform terminal compatibility** (Windows, Linux, macOS) ## Hardware Setup From e699b1a7ad5144e5808c5c043fc0229ef7a4463b Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sat, 12 Jul 2025 18:40:41 -0300 Subject: [PATCH 07/10] feat(example): improves ISR execution time Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../FunctionalInterruptLambda/FunctionalInterruptLambda.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino index e9bc35f2b47..7338a45236c 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino @@ -219,7 +219,7 @@ void setup() { // Update captured variables via pointers (*totalInterruptsPtr)++; - (*lastInterruptTimePtr) = millis(); + (*lastInterruptTimePtr) = currentTime; // Toggle LED state and set flag for main loop to handle (*ledStatePtr) = !(*ledStatePtr); From 36522c157b4b5a3195dc88a3c83016e4df149c1f Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Mon, 14 Jul 2025 12:29:36 -0300 Subject: [PATCH 08/10] feat(gpio): simplifies the example and documentation --- .../FunctionalInterruptLambda.ino | 253 +++-------------- .../GPIO/FunctionalInterruptLambda/README.md | 260 +++++++----------- 2 files changed, 143 insertions(+), 370 deletions(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino index 7338a45236c..bfae3077ebe 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino @@ -19,41 +19,32 @@ ======================================== This example demonstrates how to use lambda functions with FunctionalInterrupt - for GPIO pin interrupt callbacks on ESP32. It shows different lambda patterns - and capture techniques for interrupt handling with debouncing. + for GPIO pin interrupt callbacks on ESP32. It shows CHANGE mode detection + with LED toggle functionality and proper debouncing. Hardware Setup: - - Connect a button between GPIO 4 (BUTTON_PIN) and GND (with internal pullup) - - Connect an LED with resistor to GPIO 2 (LED_PIN or use the built-in LED available on most boards) - - Optionally connect a second button (BUTTON2_PIN) to another pin in case that there is no BOOT pin button available + - Use BOOT Button or connect a button between BUTTON_PIN and GND (with internal pullup) + - Use Builtin Board LED or connect an LED with resistor to GPIO 2 (LED_PIN) Features Demonstrated: - 1. CHANGE mode lambda to handle both RISING and FALLING edges on same pin - 2. Lambda function with captured variables (pointers) - 3. Object method calls integrated within lambda functions - 4. Edge type detection using digitalRead() within ISR - 5. Hardware debouncing with configurable timeout - 6. Best practices for interrupt-safe lambda functions - + 1. CHANGE mode lambda to detect both RISING and FALLING edges + 2. LED toggle on button press (FALLING edge) + 3. Edge type detection using digitalRead() within ISR + 4. Hardware debouncing with configurable timeout + IMPORTANT NOTE ABOUT ESP32 INTERRUPT BEHAVIOR: - Only ONE interrupt handler can be attached per GPIO pin at a time - Calling attachInterrupt() on a pin that already has an interrupt will override the previous one - This applies regardless of edge type (RISING, FALLING, CHANGE) - If you need both RISING and FALLING detection on the same pin, use CHANGE mode and determine the edge type within your handler by reading the pin state - - For detailed documentation, patterns, and best practices, see README.md - - Note: This example uses proper pointer captures for static/global variables - to avoid compiler warnings about non-automatic storage duration. */ #include #include // Pin definitions -#define BUTTON_PIN 4 // Button pin (GPIO 4) - change as needed -#define BUTTON2_PIN BOOT_PIN // BOOT BUTTON - change as needed +#define BUTTON_PIN BOOT_PIN // BOOT BUTTON - change as needed #ifdef LED_BUILTIN #define LED_PIN LED_BUILTIN #else @@ -61,56 +52,20 @@ #define LED_PIN 2 // change as needed #endif - -// Global variables for interrupt counters (volatile for ISR safety) +// Global variables for interrupt handling (volatile for ISR safety) volatile uint32_t buttonPressCount = 0; -volatile uint32_t buttonReleaseCount = 0; // Track button releases separately -volatile uint32_t button2PressCount = 0; +volatile uint32_t buttonReleaseCount = 0; volatile bool buttonPressed = false; -volatile bool buttonReleased = false; // Flag for button release events -volatile bool button2Pressed = false; +volatile bool buttonReleased = false; volatile bool ledState = false; volatile bool ledStateChanged = false; // Flag to indicate LED needs updating -// Variables to demonstrate lambda captures -volatile uint32_t totalInterrupts = 0; -volatile unsigned long lastInterruptTime = 0; - // Debouncing variables (volatile for ISR safety) -volatile unsigned long lastButton1InterruptTime = 0; -volatile unsigned long lastButton2InterruptTime = 0; +volatile unsigned long lastButtonInterruptTime = 0; const unsigned long DEBOUNCE_DELAY_MS = 50; // 50ms debounce delay // State-based debouncing to prevent hysteresis issues -volatile bool lastButton1State = HIGH; // Track last stable state (HIGH = released) -volatile bool lastButton2State = HIGH; // Track last stable state (HIGH = released) - -// Class example for demonstrating lambda with object methods -class InterruptHandler { - public: - volatile uint32_t objectPressCount = 0; - volatile bool stateChanged = false; - String name; - - InterruptHandler(const String& handlerName) : name(handlerName) {} - - void handleButtonPress() { - uint32_t temp = objectPressCount; - temp++; - objectPressCount = temp; - stateChanged = true; - } - - void printStatus() { - if (stateChanged) { - Serial.printf("Handler '%s': Press count = %lu\r\n", name.c_str(), objectPressCount); - stateChanged = false; - } - } -}; - -// Global handler instance for object method example -static InterruptHandler globalHandler("ButtonHandler"); +volatile bool lastButtonState = HIGH; // Track last stable state (HIGH = released) void setup() { Serial.begin(115200); @@ -121,25 +76,18 @@ void setup() { // Configure pins pinMode(BUTTON_PIN, INPUT_PULLUP); - pinMode(BUTTON2_PIN, INPUT_PULLUP); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); - // Example 1: CHANGE mode lambda to handle both RISING and FALLING edges - // This demonstrates how to properly handle both edges on the same pin - // Includes: object method calls, pointer captures, and state-based debouncing - Serial.println("Setting up Example 1: CHANGE mode lambda for both edges"); - - // Create pointers for safe capture (avoiding non-automatic storage duration warnings) - InterruptHandler* handlerPtr = &globalHandler; - volatile unsigned long* lastButton1TimePtr = &lastButton1InterruptTime; - volatile bool* lastButton1StatePtr = &lastButton1State; - const unsigned long* debounceDelayPtr = &DEBOUNCE_DELAY_MS; + // CHANGE mode lambda to handle both RISING and FALLING edges + // This toggles the LED on button press (FALLING edge) + Serial.println("Setting up CHANGE mode lambda for LED toggle"); - std::function changeModeLambda = [handlerPtr, lastButton1TimePtr, lastButton1StatePtr, debounceDelayPtr]() { - // Debouncing: check if enough time has passed since last interrupt + // Simplified lambda with minimal captures + std::function changeModeLambda = []() { + // Simple debouncing: check if enough time has passed since last interrupt unsigned long currentTime = millis(); - if (currentTime - (*lastButton1TimePtr) < (*debounceDelayPtr)) { + if (currentTime - lastButtonInterruptTime < DEBOUNCE_DELAY_MS) { return; // Ignore this interrupt due to bouncing } @@ -147,171 +95,60 @@ void setup() { bool currentState = digitalRead(BUTTON_PIN); // State-based debouncing: only process if state actually changed - if (currentState == (*lastButton1StatePtr)) { + if (currentState == lastButtonState) { return; // No real state change, ignore (hysteresis/noise) } // Update timing and state - (*lastButton1TimePtr) = currentTime; - (*lastButton1StatePtr) = currentState; + lastButtonInterruptTime = currentTime; + lastButtonState = currentState; if (currentState == LOW) { - // FALLING edge detected (button pressed) - uint32_t temp = buttonPressCount; - temp++; - buttonPressCount = temp; + // FALLING edge detected (button pressed) - set flag for main loop + buttonPressCount++; buttonPressed = true; - - // Call object method for press events - handlerPtr->handleButtonPress(); + ledStateChanged = true; // Signal main loop to toggle LED } else { - // RISING edge detected (button released) - uint32_t temp = buttonReleaseCount; - temp++; - buttonReleaseCount = temp; + // RISING edge detected (button released) - set flag for main loop + buttonReleaseCount++; buttonReleased = true; - - // Object method calls can be different for release events - // For demonstration, we'll call the same method but could be different - handlerPtr->handleButtonPress(); } }; + attachInterrupt(BUTTON_PIN, changeModeLambda, CHANGE); - // Example 2: Lambda with captured variables (Pointer Captures) - // This demonstrates safe capture of global variables via pointers - // NOTE: We avoid calling digitalWrite() directly in ISR to prevent FreeRTOS scheduler issues - Serial.println("Setting up Example 2: Lambda with pointer captures"); - - // Create pointers to avoid capturing static/global variables directly - volatile uint32_t* totalInterruptsPtr = &totalInterrupts; - volatile unsigned long* lastInterruptTimePtr = &lastInterruptTime; - volatile bool* ledStatePtr = &ledState; - volatile bool* ledStateChangedPtr = &ledStateChanged; - volatile unsigned long* lastButton2TimePtr = &lastButton2InterruptTime; - volatile bool* lastButton2StatePtr = &lastButton2State; - - std::function captureLambda = [totalInterruptsPtr, lastInterruptTimePtr, ledStatePtr, ledStateChangedPtr, lastButton2TimePtr, lastButton2StatePtr, debounceDelayPtr]() { - // Debouncing: check if enough time has passed since last interrupt - unsigned long currentTime = millis(); - if (currentTime - (*lastButton2TimePtr) < (*debounceDelayPtr)) { - return; // Ignore this interrupt due to bouncing - } - - // Read current pin state and check for real state change - bool currentState = digitalRead(BUTTON2_PIN); - - // State-based debouncing: only process if state actually changed to LOW (pressed) - // and the last state was HIGH (released) - if (currentState != LOW || (*lastButton2StatePtr) != HIGH) { - return; // Not a valid press event, ignore - } - - // Update timing and state - (*lastButton2TimePtr) = currentTime; - (*lastButton2StatePtr) = currentState; - - // Update button press count - uint32_t temp = button2PressCount; - temp++; - button2PressCount = temp; - button2Pressed = true; - - // Update captured variables via pointers - (*totalInterruptsPtr)++; - (*lastInterruptTimePtr) = currentTime; - - // Toggle LED state and set flag for main loop to handle - (*ledStatePtr) = !(*ledStatePtr); - (*ledStateChangedPtr) = true; // Signal main loop to update LED - }; - attachInterrupt(BUTTON2_PIN, captureLambda, FALLING); - Serial.println(); - Serial.println("Lambda interrupts configured:"); - Serial.printf("- Button 1 (Pin %d): CHANGE mode lambda (handles both press and release)\r\n", BUTTON_PIN); - Serial.printf("- Button 2 (Pin %d): FALLING mode lambda with LED control\r\n", BUTTON2_PIN); - Serial.printf("- Debounce delay: %lu ms for both buttons\r\n", DEBOUNCE_DELAY_MS); + Serial.printf("Lambda interrupt configured on Pin %d (CHANGE mode)\r\n", BUTTON_PIN); + Serial.printf("Debounce delay: %lu ms\r\n", DEBOUNCE_DELAY_MS); Serial.println(); - Serial.println("Press and release the buttons to see lambda interrupts in action!"); - Serial.println("Button 1 will detect both press (FALLING) and release (RISING) events."); - Serial.println("Button 2 (FALLING only) will toggle the LED."); - Serial.println("Both buttons include debouncing to prevent mechanical bounce issues."); + Serial.println("Press the button to toggle the LED!"); + Serial.println("Button press (FALLING edge) will toggle the LED."); + Serial.println("Button release (RISING edge) will be detected and reported."); + Serial.println("Button includes debouncing to prevent mechanical bounce issues."); Serial.println(); } void loop() { - static unsigned long lastPrintTime = 0; - static uint32_t lastButton1PressCount = 0; - static uint32_t lastButton1ReleaseCount = 0; - static uint32_t lastButton2Count = 0; - // Handle LED state changes (ISR-safe approach) if (ledStateChanged) { ledStateChanged = false; + ledState = !ledState; // Toggle LED state in main loop digitalWrite(LED_PIN, ledState); } - // Update button states in main loop (for proper state tracking) - // This helps prevent hysteresis issues by updating state when buttons are actually released - static bool lastButton2Reading = HIGH; - bool currentButton2Reading = digitalRead(BUTTON2_PIN); - if (currentButton2Reading == HIGH && lastButton2Reading == LOW) { - // Button 2 was released, update state - lastButton2State = HIGH; - } - lastButton2Reading = currentButton2Reading; - - // Check for button 1 presses and releases (CHANGE mode lambda) + // Check for button presses if (buttonPressed) { buttonPressed = false; - Serial.printf("==> Button 1 PRESSED! Count: %lu (FALLING edge detected)\r\n", buttonPressCount); + Serial.printf("==> Button PRESSED! Count: %lu, LED: %s (FALLING edge)\r\n", + buttonPressCount, ledState ? "ON" : "OFF"); } + // Check for button releases if (buttonReleased) { buttonReleased = false; - Serial.printf("==> Button 1 RELEASED! Count: %lu (RISING edge detected)\r\n", buttonReleaseCount); + Serial.printf("==> Button RELEASED! Count: %lu (RISING edge)\r\n", + buttonReleaseCount); } - // Check for button 2 presses (capture lambda) - if (button2Pressed) { - button2Pressed = false; - Serial.printf("==> Button 2 pressed! Count: %lu, LED: %s (Capture lambda)\r\n", - button2PressCount, ledState ? "ON" : "OFF"); - } - - // Check object handler status (object method lambda) - globalHandler.printStatus(); - - // Print statistics every 5 seconds if there's been activity - if (millis() - lastPrintTime >= 5000) { - lastPrintTime = millis(); - - bool hasActivity = (buttonPressCount != lastButton1PressCount || - buttonReleaseCount != lastButton1ReleaseCount || - button2PressCount != lastButton2Count); - - if (hasActivity) { - Serial.println("============================"); - Serial.println("Lambda Interrupt Statistics:"); - Serial.println("============================"); - Serial.printf("Button 1 presses: %8lu\r\n", buttonPressCount); - Serial.printf("Button 1 releases: %8lu\r\n", buttonReleaseCount); - Serial.printf("Button 2 presses: %8lu\r\n", button2PressCount); - Serial.printf("Object handler calls: %8lu\r\n", globalHandler.objectPressCount); - Serial.printf("Total interrupts: %8lu\r\n", totalInterrupts); - Serial.printf("LED state: %8s\r\n", ledState ? "ON" : "OFF"); - if (lastInterruptTime > 0) { - Serial.printf("Last interrupt: %8lu ms ago\r\n", millis() - lastInterruptTime); - } - Serial.println(); - - lastButton1PressCount = buttonPressCount; - lastButton1ReleaseCount = buttonReleaseCount; - lastButton2Count = button2PressCount; - } - } - - // Small delay to prevent overwhelming the serial output delay(10); -} \ No newline at end of file +} diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md index 72749f81019..d7d73197b5c 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md @@ -1,34 +1,29 @@ # ESP32 Lambda FunctionalInterrupt Example -This example demonstrates how to use lambda functions with FunctionalInterrupt for GPIO pin interrupt callbacks on ESP32. It showcases different lambda patterns and capture techniques for interrupt handling with advanced debouncing through two comprehensive examples: - -**Example 1**: CHANGE mode lambda with object method integration, state-based debouncing, and dual-edge detection -**Example 2**: FALLING mode lambda with pointer captures, LED control, and comprehensive variable capture patterns +This example demonstrates how to use lambda functions with FunctionalInterrupt for GPIO pin interrupt callbacks on ESP32. It shows CHANGE mode detection with LED toggle functionality and proper debouncing. ## Features Demonstrated -1. **CHANGE mode lambda** to handle both RISING and FALLING edges on same pin -2. **Lambda function with captured variables** (pointers) -3. **Object method calls integrated within lambda functions** -4. **Edge type detection** using digitalRead() within ISR -5. **Hardware debouncing** with configurable timeout and anti-hysteresis +1. **CHANGE mode lambda** to detect both RISING and FALLING edges +2. **LED toggle on button press** (FALLING edge) +3. **Edge type detection** using digitalRead() within ISR +4. **Hardware debouncing** with configurable timeout ## Hardware Setup -- Connect a button between GPIO 4 and GND (with internal pullup) -- Connect an LED with resistor to GPIO 2 (built-in LED on most ESP32 boards) -- Optionally connect a second button to BOOT pin for advanced examples +- Use BOOT Button or connect a button between BUTTON_PIN and GND (with internal pullup) +- Use Builtin Board LED (no special hardware setup) or connect an LED with resistor to GPIO assigned as LED_PIN.\ + Some boards have an RGB LED that needs no special hardware setup to work as a simple white on/off LED. ``` -ESP32 Board Buttons + LED -─────────── ──────────────── -GPIO 4 ──────────── [BUTTON] ──── GND -GPIO 0 (BOOT) ───── [BUTTON] ──── GND -GPIO 2 ───── [R1] ─── [LED] ───── GND * R1 = resistor 220 ohms - │ - [330Ω] - │ - 3V3 +ESP32 Board Button LED +----------- ------ --- +BOOT_PIN ------------ [BUTTON] ---- GND +LED_PIN ----------- [LED] ------- GND + ¦ + [330O] (*) Only needed when using an external LED attached to the GPIO. + ¦ + 3V3 ``` ## Important ESP32 Interrupt Behavior @@ -39,72 +34,55 @@ GPIO 2 ───── [R1] ─── [LED] ───── GND * R1 = resistor - This applies regardless of edge type (RISING, FALLING, CHANGE) - If you need both RISING and FALLING detection on the same pin, use **CHANGE mode** and determine the edge type within your handler by reading the pin state -## Code Examples Overview - -This example contains **two main implementations** that demonstrate different lambda interrupt patterns: - -### Example 1: CHANGE Mode Lambda (Button 1 - GPIO 4) -- **Pattern**: Comprehensive CHANGE mode with edge detection -- **Features**: Dual-edge detection (RISING/FALLING), object method calls, state-based debouncing -- **Use Case**: Single pin handling both button press and release events -- **Lambda Type**: Complex lambda with multiple pointer captures and object interaction +## Code Overview -### Example 2: FALLING Mode Lambda (Button 2 - BOOT Pin) -- **Pattern**: Pointer capture lambda with LED control -- **Features**: Single-edge detection, extensive variable capture, hardware control -- **Use Case**: Traditional button press with peripheral control (LED toggle) -- **Lambda Type**: Capture-heavy lambda demonstrating safe variable access patterns +This example demonstrates a simple CHANGE mode lambda interrupt that: -## Lambda Function Patterns Explained - -### 1. CHANGE Mode Lambda (Recommended for Multiple Edges) -```cpp -attachInterrupt(pin, lambda, CHANGE); -``` -- Detects both RISING and FALLING edges on the same pin -- Use `digitalRead()` inside the lambda to determine edge type -- Most efficient way to handle both press and release events -- Avoids the "handler override" issue entirely +- **Detects both button press and release** using a single interrupt handler +- **Toggles LED only on button press** (FALLING edge) +- **Reports both press and release events** to Serial output +- **Uses proper debouncing** to prevent switch bounce issues +- **Implements minimal lambda captures** for simplicity -### 2. Pointer Capture Lambda -```cpp -[ptr1, ptr2]() { *ptr1 = value; } -``` -- Captures pointers to global/static variables -- Avoids compiler warnings about non-automatic storage duration -- Allows safe modification of external variables in ISR +## Lambda Function Pattern -### 3. Object Method Lambda +### CHANGE Mode Lambda (No Captures) ```cpp -[objectPtr]() { objectPtr->method(); } +std::function changeModeLambda = []() { + // Debouncing check + unsigned long currentTime = millis(); + if (currentTime - lastButtonInterruptTime < DEBOUNCE_DELAY_MS) { + return; // Ignore bouncing + } + + // Determine edge type + bool currentState = digitalRead(BUTTON_PIN); + if (currentState == lastButtonState) { + return; // No real state change + } + + // Update state and handle edges + lastButtonInterruptTime = currentTime; + lastButtonState = currentState; + + if (currentState == LOW) { + // Button pressed (FALLING edge) + buttonPressCount++; + buttonPressed = true; + ledStateChanged = true; // Signal LED toggle + } else { + // Button released (RISING edge) + buttonReleaseCount++; + buttonReleased = true; + } +}; + +attachInterrupt(BUTTON_PIN, changeModeLambda, CHANGE); ``` -- Captures pointer to object instance -- Enables calling object methods from interrupt -- Useful for object-oriented interrupt handling -### 4. State-Based Debouncing Pattern (Anti-Hysteresis) -```cpp -bool currentState = digitalRead(pin); -if (currentState == lastKnownState) return; // No real change -lastKnownState = currentState; -``` -- Prevents counting multiple edges from electrical noise/hysteresis -- Ensures press/release counts remain synchronized -- Tracks actual state changes rather than just edge triggers -- Critical for reliable button input with mechanical switches - -### 5. Time-Based Debouncing Pattern -```cpp -unsigned long currentTime = millis(); -if (currentTime - lastInterruptTime < DEBOUNCE_DELAY) return; -lastInterruptTime = currentTime; -``` -- Prevents multiple triggers from mechanical switch bounce -- Uses `millis()` for timing (fast and ISR-safe) -- Typical debounce delay: 20-100ms depending on switch quality - -## Edge Detection Pattern in CHANGE Mode +## Key Concepts +### Edge Detection in CHANGE Mode ```cpp if (digitalRead(pin) == LOW) { // FALLING edge detected (button pressed) @@ -113,97 +91,55 @@ if (digitalRead(pin) == LOW) { } ``` -## Best Practices for Lambda Interrupts - -✅ **Keep lambda functions short and simple** -✅ **Use volatile for variables modified in ISR** -✅ **Avoid complex operations** (delays, Serial.print, etc.) -✅ **NEVER call digitalWrite() or other GPIO functions directly in ISR** -✅ **Use flags and handle GPIO operations in main loop** -✅ **Use pointer captures for static/global variables** -✅ **Use std::function for type clarity** -✅ **Implement state-based debouncing to prevent hysteresis issues** -✅ **Track last known pin state to avoid counting noise as real edges** -✅ **Test thoroughly with real hardware** -✅ **Consider using flags and processing in loop()** - -## Lambda Capture Syntax Guide - -| Syntax | Description | -|-----------------|--------------------------------------| -| `[]` | Capture nothing | -| `[var]` | Capture var by value (copy) | -| `[&var]` | Capture var by reference (avoid!) | -| `[ptr]` | Capture pointer (recommended) | -| `[=]` | Capture all by value | -| `[&]` | Capture all by reference (avoid!) | -| `[var1, ptr2]` | Mixed captures | - -## Why Use Pointer Captures? - -• **Avoids "non-automatic storage duration" compiler warnings** -• **More explicit about what's being accessed** -• **Better performance than reference captures** -• **Safer for static and global variables** -• **Compatible with all ESP32 compiler versions** - -## Debouncing Strategy - -This example implements a **dual-layer debouncing approach**: - -1. **Time-based debouncing**: Ignores interrupts within 50ms of the previous one -2. **State-based debouncing**: Only processes interrupts that represent actual state changes - -This combination effectively eliminates: -- Mechanical switch bounce -- Electrical noise and hysteresis -- Multiple false triggers -- Synchronized press/release count mismatches +### Debouncing Strategy +This example implements dual-layer debouncing: +1. **Time-based**: Ignores interrupts within 50ms of previous one +2. **State-based**: Only processes actual state changes + +### Main Loop Processing +```cpp +void loop() { + // Handle LED changes safely outside ISR + if (ledStateChanged) { + ledStateChanged = false; + ledState = !ledState; + digitalWrite(LED_PIN, ledState); + } + + // Report button events + if (buttonPressed) { + // Handle press event + } + if (buttonReleased) { + // Handle release event + } +} +``` ## Expected Output ``` ESP32 Lambda FunctionalInterrupt Example ======================================== -Setting up Example 1: CHANGE mode lambda for both edges -Setting up Example 2: Lambda with pointer captures - -Lambda interrupts configured: -- Button 1 (Pin 4): CHANGE mode lambda (handles both press and release) -- Button 2 (Pin 0): FALLING mode lambda with LED control -- Debounce delay: 50 ms for both buttons - -Press and release the buttons to see lambda interrupts in action! -Button 1 will detect both press (FALLING) and release (RISING) events. -Button 2 (FALLING only) will toggle the LED. -Both buttons include debouncing to prevent mechanical bounce issues. - -==> Button 1 PRESSED! Count: 1 (FALLING edge detected) -Handler 'ButtonHandler': Press count = 1 -==> Button 1 RELEASED! Count: 1 (RISING edge detected) -Handler 'ButtonHandler': Press count = 2 -==> Button 2 pressed! Count: 1, LED: ON (Capture lambda) - -============================ -Lambda Interrupt Statistics: -============================ -Button 1 presses: 2 -Button 1 releases: 2 -Button 2 presses: 1 -Object handler calls: 2 -Total interrupts: 1 -LED state: ON -Last interrupt: 1234 ms ago -``` +Setting up CHANGE mode lambda for LED toggle -## Pin Configuration +Lambda interrupt configured on Pin 0 (CHANGE mode) +Debounce delay: 50 ms -The example uses these default pins (configurable in the source): +Press the button to toggle the LED! +Button press (FALLING edge) will toggle the LED. +Button release (RISING edge) will be detected and reported. +Button includes debouncing to prevent mechanical bounce issues. -- `BUTTON_PIN`: GPIO 4 (change as needed) -- `BUTTON2_PIN`: BOOT_PIN (GPIO 0 on most ESP32 boards) -- `LED_PIN`: LED_BUILTIN (GPIO 2 on most ESP32 boards) +==> Button PRESSED! Count: 1, LED: ON (FALLING edge) +==> Button RELEASED! Count: 1 (RISING edge) +==> Button PRESSED! Count: 2, LED: OFF (FALLING edge) +==> Button RELEASED! Count: 2 (RISING edge) +``` + +## Pin Configuration -## Compilation Notes +The example uses these default pins: -This example uses proper pointer captures for static/global variables to avoid compiler warnings about non-automatic storage duration. +- `BUTTON_PIN`: BOOT_PIN (automatically assigned by the Arduino Core) +- `LED_PIN`: LED_BUILTIN (may not be available for your board - please verify it) From 2e50aa5e6f687f237de7916c9d97144e485ef58c Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Mon, 14 Jul 2025 13:10:26 -0300 Subject: [PATCH 09/10] feat(gpio): uses IRAM lambda and fixes volatile operation --- .../FunctionalInterruptLambda.ino | 69 ++++++++++--------- .../GPIO/FunctionalInterruptLambda/README.md | 6 +- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino index bfae3077ebe..885d6f004d6 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino @@ -67,6 +67,42 @@ const unsigned long DEBOUNCE_DELAY_MS = 50; // 50ms debounce delay // State-based debouncing to prevent hysteresis issues volatile bool lastButtonState = HIGH; // Track last stable state (HIGH = released) +// Global lambda function (declared at file scope) - ISR in IRAM +IRAM_ATTR std::function changeModeLambda = []() { + // Simple debouncing: check if enough time has passed since last interrupt + unsigned long currentTime = millis(); + if (currentTime - lastButtonInterruptTime < DEBOUNCE_DELAY_MS) { + return; // Ignore this interrupt due to bouncing + } + + // Read current pin state to determine edge type + bool currentState = digitalRead(BUTTON_PIN); + + // State-based debouncing: only process if state actually changed + if (currentState == lastButtonState) { + return; // No real state change, ignore (hysteresis/noise) + } + + // Update timing and state + lastButtonInterruptTime = currentTime; + lastButtonState = currentState; + + if (currentState == LOW) { + // FALLING edge detected (button pressed) - set flag for main loop + // volatile variables require use of temporary value transfer + uint32_t temp = buttonPressCount + 1; + buttonPressCount = temp; + buttonPressed = true; + ledStateChanged = true; // Signal main loop to toggle LED + } else { + // RISING edge detected (button released) - set flag for main loop + // volatile variables require use of temporary value transfer + uint32_t temp = buttonReleaseCount + 1; + buttonReleaseCount = temp; + buttonReleased = true; + } +}; + void setup() { Serial.begin(115200); delay(1000); // Allow serial monitor to connect @@ -83,38 +119,7 @@ void setup() { // This toggles the LED on button press (FALLING edge) Serial.println("Setting up CHANGE mode lambda for LED toggle"); - // Simplified lambda with minimal captures - std::function changeModeLambda = []() { - // Simple debouncing: check if enough time has passed since last interrupt - unsigned long currentTime = millis(); - if (currentTime - lastButtonInterruptTime < DEBOUNCE_DELAY_MS) { - return; // Ignore this interrupt due to bouncing - } - - // Read current pin state to determine edge type - bool currentState = digitalRead(BUTTON_PIN); - - // State-based debouncing: only process if state actually changed - if (currentState == lastButtonState) { - return; // No real state change, ignore (hysteresis/noise) - } - - // Update timing and state - lastButtonInterruptTime = currentTime; - lastButtonState = currentState; - - if (currentState == LOW) { - // FALLING edge detected (button pressed) - set flag for main loop - buttonPressCount++; - buttonPressed = true; - ledStateChanged = true; // Signal main loop to toggle LED - } else { - // RISING edge detected (button released) - set flag for main loop - buttonReleaseCount++; - buttonReleased = true; - } - }; - + // Use the global lambda function attachInterrupt(BUTTON_PIN, changeModeLambda, CHANGE); Serial.println(); diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md index d7d73197b5c..555cc5ccaa7 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md @@ -8,6 +8,7 @@ This example demonstrates how to use lambda functions with FunctionalInterrupt f 2. **LED toggle on button press** (FALLING edge) 3. **Edge type detection** using digitalRead() within ISR 4. **Hardware debouncing** with configurable timeout +5. **IRAM_ATTR lambda declaration** for optimal ISR performance in RAM ## Hardware Setup @@ -46,9 +47,10 @@ This example demonstrates a simple CHANGE mode lambda interrupt that: ## Lambda Function Pattern -### CHANGE Mode Lambda (No Captures) +### CHANGE Mode Lambda with IRAM Declaration ```cpp -std::function changeModeLambda = []() { +// Global lambda declared with IRAM_ATTR for optimal ISR performance +IRAM_ATTR std::function changeModeLambda = []() { // Debouncing check unsigned long currentTime = millis(); if (currentTime - lastButtonInterruptTime < DEBOUNCE_DELAY_MS) { From 6da476523bd003de9baf05f1f469ecec2e5c5a68 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Mon, 14 Jul 2025 13:14:17 -0300 Subject: [PATCH 10/10] fix(doc): fixing documentation apresentation --- .../GPIO/FunctionalInterruptLambda/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md index 555cc5ccaa7..afbbb2272e9 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md @@ -17,14 +17,14 @@ This example demonstrates how to use lambda functions with FunctionalInterrupt f Some boards have an RGB LED that needs no special hardware setup to work as a simple white on/off LED. ``` -ESP32 Board Button LED ------------ ------ --- +ESP32 Board Button/LED +----------- --------- BOOT_PIN ------------ [BUTTON] ---- GND -LED_PIN ----------- [LED] ------- GND - ¦ - [330O] (*) Only needed when using an external LED attached to the GPIO. - ¦ - 3V3 +LED_PIN --------------- [LED] ----- GND + ¦ + [330O] (*) Only needed when using an external LED attached to the GPIO. + ¦ + 3V3 ``` ## Important ESP32 Interrupt Behavior