diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino new file mode 100644 index 00000000000..885d6f004d6 --- /dev/null +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino @@ -0,0 +1,159 @@ +/* + 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 CHANGE mode detection + with LED toggle functionality and proper debouncing. + + Hardware Setup: + - 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 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 +*/ + +#include +#include + +// Pin definitions +#define BUTTON_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 handling (volatile for ISR safety) +volatile uint32_t buttonPressCount = 0; +volatile uint32_t buttonReleaseCount = 0; +volatile bool buttonPressed = false; +volatile bool buttonReleased = false; +volatile bool ledState = false; +volatile bool ledStateChanged = false; // Flag to indicate LED needs updating + +// Debouncing variables (volatile for ISR safety) +volatile unsigned long lastButtonInterruptTime = 0; +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 + + Serial.println("ESP32 Lambda FunctionalInterrupt Example"); + Serial.println("========================================"); + + // Configure pins + pinMode(BUTTON_PIN, INPUT_PULLUP); + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + // 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"); + + // Use the global lambda function + attachInterrupt(BUTTON_PIN, changeModeLambda, CHANGE); + + Serial.println(); + 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 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() { + // Handle LED state changes (ISR-safe approach) + if (ledStateChanged) { + ledStateChanged = false; + ledState = !ledState; // Toggle LED state in main loop + digitalWrite(LED_PIN, ledState); + } + + // Check for button presses + if (buttonPressed) { + buttonPressed = false; + 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 RELEASED! Count: %lu (RISING edge)\r\n", + buttonReleaseCount); + } + + delay(10); +} diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md new file mode 100644 index 00000000000..afbbb2272e9 --- /dev/null +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md @@ -0,0 +1,147 @@ +# ESP32 Lambda FunctionalInterrupt Example + +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 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 +5. **IRAM_ATTR lambda declaration** for optimal ISR performance in RAM + +## Hardware Setup + +- 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 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 + +**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 Overview + +This example demonstrates a simple CHANGE mode lambda interrupt that: + +- **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 + +## Lambda Function Pattern + +### CHANGE Mode Lambda with IRAM Declaration +```cpp +// 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) { + 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); +``` + +## Key Concepts + +### Edge Detection in CHANGE Mode +```cpp +if (digitalRead(pin) == LOW) { + // FALLING edge detected (button pressed) +} else { + // RISING edge detected (button released) +} +``` + +### 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 CHANGE mode lambda for LED toggle + +Lambda interrupt configured on Pin 0 (CHANGE mode) +Debounce delay: 50 ms + +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 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 + +The example uses these default pins: + +- `BUTTON_PIN`: BOOT_PIN (automatically assigned by the Arduino Core) +- `LED_PIN`: LED_BUILTIN (may not be available for your board - please verify it)