Skip to content

Commit 82d56bc

Browse files
SuGliderCopilotpre-commit-ci-lite[bot]me-no-dev
authored
feat(gpio): new functional interrupt lambda example (#11589)
* feat(gpio): new functional interrupt lambda example * fix(readme): schematic diagram allignment * fix(example): uses volatile for ISR variables Co-authored-by: Copilot <[email protected]> * fix(example): uses volatile for ISR variables Co-authored-by: Copilot <[email protected]> * fix(example): uses volatile data type also for the pointers * fix(readme): clear documentation * feat(example): improves ISR execution time Co-authored-by: Copilot <[email protected]> * feat(gpio): simplifies the example and documentation * feat(gpio): uses IRAM lambda and fixes volatile operation * fix(doc): fixing documentation apresentation * ci(pre-commit): Apply automatic fixes * fix(ci): Update README.md --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Me No Dev <[email protected]>
1 parent 1f0d4b5 commit 82d56bc

File tree

2 files changed

+304
-0
lines changed

2 files changed

+304
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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 CHANGE mode detection
23+
with LED toggle functionality and proper debouncing.
24+
25+
Hardware Setup:
26+
- Use BOOT Button or connect a button between BUTTON_PIN and GND (with internal pullup)
27+
- Use Builtin Board LED or connect an LED with resistor to GPIO 2 (LED_PIN)
28+
29+
Features Demonstrated:
30+
1. CHANGE mode lambda to detect both RISING and FALLING edges
31+
2. LED toggle on button press (FALLING edge)
32+
3. Edge type detection using digitalRead() within ISR
33+
4. Hardware debouncing with configurable timeout
34+
35+
IMPORTANT NOTE ABOUT ESP32 INTERRUPT BEHAVIOR:
36+
- Only ONE interrupt handler can be attached per GPIO pin at a time
37+
- Calling attachInterrupt() on a pin that already has an interrupt will override the previous one
38+
- This applies regardless of edge type (RISING, FALLING, CHANGE)
39+
- If you need both RISING and FALLING detection on the same pin, use CHANGE mode
40+
and determine the edge type within your handler by reading the pin state
41+
*/
42+
43+
#include <Arduino.h>
44+
#include <FunctionalInterrupt.h>
45+
46+
// Pin definitions
47+
#define BUTTON_PIN BOOT_PIN // BOOT BUTTON - change as needed
48+
#ifdef LED_BUILTIN
49+
#define LED_PIN LED_BUILTIN
50+
#else
51+
#warning Using LED_PIN = GPIO 2 as default - change as needed
52+
#define LED_PIN 2 // change as needed
53+
#endif
54+
55+
// Global variables for interrupt handling (volatile for ISR safety)
56+
volatile uint32_t buttonPressCount = 0;
57+
volatile uint32_t buttonReleaseCount = 0;
58+
volatile bool buttonPressed = false;
59+
volatile bool buttonReleased = false;
60+
volatile bool ledState = false;
61+
volatile bool ledStateChanged = false; // Flag to indicate LED needs updating
62+
63+
// Debouncing variables (volatile for ISR safety)
64+
volatile unsigned long lastButtonInterruptTime = 0;
65+
const unsigned long DEBOUNCE_DELAY_MS = 50; // 50ms debounce delay
66+
67+
// State-based debouncing to prevent hysteresis issues
68+
volatile bool lastButtonState = HIGH; // Track last stable state (HIGH = released)
69+
70+
// Global lambda function (declared at file scope) - ISR in IRAM
71+
IRAM_ATTR std::function<void()> changeModeLambda = []() {
72+
// Simple debouncing: check if enough time has passed since last interrupt
73+
unsigned long currentTime = millis();
74+
if (currentTime - lastButtonInterruptTime < DEBOUNCE_DELAY_MS) {
75+
return; // Ignore this interrupt due to bouncing
76+
}
77+
78+
// Read current pin state to determine edge type
79+
bool currentState = digitalRead(BUTTON_PIN);
80+
81+
// State-based debouncing: only process if state actually changed
82+
if (currentState == lastButtonState) {
83+
return; // No real state change, ignore (hysteresis/noise)
84+
}
85+
86+
// Update timing and state
87+
lastButtonInterruptTime = currentTime;
88+
lastButtonState = currentState;
89+
90+
if (currentState == LOW) {
91+
// FALLING edge detected (button pressed) - set flag for main loop
92+
// volatile variables require use of temporary value transfer
93+
uint32_t temp = buttonPressCount + 1;
94+
buttonPressCount = temp;
95+
buttonPressed = true;
96+
ledStateChanged = true; // Signal main loop to toggle LED
97+
} else {
98+
// RISING edge detected (button released) - set flag for main loop
99+
// volatile variables require use of temporary value transfer
100+
uint32_t temp = buttonReleaseCount + 1;
101+
buttonReleaseCount = temp;
102+
buttonReleased = true;
103+
}
104+
};
105+
106+
void setup() {
107+
Serial.begin(115200);
108+
delay(1000); // Allow serial monitor to connect
109+
110+
Serial.println("ESP32 Lambda FunctionalInterrupt Example");
111+
Serial.println("========================================");
112+
113+
// Configure pins
114+
pinMode(BUTTON_PIN, INPUT_PULLUP);
115+
pinMode(LED_PIN, OUTPUT);
116+
digitalWrite(LED_PIN, LOW);
117+
118+
// CHANGE mode lambda to handle both RISING and FALLING edges
119+
// This toggles the LED on button press (FALLING edge)
120+
Serial.println("Setting up CHANGE mode lambda for LED toggle");
121+
122+
// Use the global lambda function
123+
attachInterrupt(BUTTON_PIN, changeModeLambda, CHANGE);
124+
125+
Serial.println();
126+
Serial.printf("Lambda interrupt configured on Pin %d (CHANGE mode)\r\n", BUTTON_PIN);
127+
Serial.printf("Debounce delay: %lu ms\r\n", DEBOUNCE_DELAY_MS);
128+
Serial.println();
129+
Serial.println("Press the button to toggle the LED!");
130+
Serial.println("Button press (FALLING edge) will toggle the LED.");
131+
Serial.println("Button release (RISING edge) will be detected and reported.");
132+
Serial.println("Button includes debouncing to prevent mechanical bounce issues.");
133+
Serial.println();
134+
}
135+
136+
void loop() {
137+
// Handle LED state changes (ISR-safe approach)
138+
if (ledStateChanged) {
139+
ledStateChanged = false;
140+
ledState = !ledState; // Toggle LED state in main loop
141+
digitalWrite(LED_PIN, ledState);
142+
}
143+
144+
// Check for button presses
145+
if (buttonPressed) {
146+
buttonPressed = false;
147+
Serial.printf("==> Button PRESSED! Count: %lu, LED: %s (FALLING edge)\r\n", buttonPressCount, ledState ? "ON" : "OFF");
148+
}
149+
150+
// Check for button releases
151+
if (buttonReleased) {
152+
buttonReleased = false;
153+
Serial.printf("==> Button RELEASED! Count: %lu (RISING edge)\r\n", buttonReleaseCount);
154+
}
155+
156+
delay(10);
157+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# ESP32 Lambda FunctionalInterrupt Example
2+
3+
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.
4+
5+
## Features Demonstrated
6+
7+
1. **CHANGE mode lambda** to detect both RISING and FALLING edges
8+
2. **LED toggle on button press** (FALLING edge)
9+
3. **Edge type detection** using digitalRead() within ISR
10+
4. **Hardware debouncing** with configurable timeout
11+
5. **IRAM_ATTR lambda declaration** for optimal ISR performance in RAM
12+
13+
## Hardware Setup
14+
15+
- Use BOOT Button or connect a button between BUTTON_PIN and GND (with internal pullup)
16+
- Use Builtin Board LED (no special hardware setup) or connect an LED with resistor to GPIO assigned as LED_PIN.\
17+
Some boards have an RGB LED that needs no special hardware setup to work as a simple white on/off LED.
18+
19+
```
20+
ESP32 Board Button/LED
21+
----------- ---------
22+
BOOT_PIN ------------ [BUTTON] ---- GND
23+
LED_PIN --------------- [LED] ----- GND
24+
¦
25+
[330O] (*) Only needed when using an external LED attached to the GPIO.
26+
¦
27+
3V3
28+
```
29+
30+
## Important ESP32 Interrupt Behavior
31+
32+
**CRITICAL:** Only ONE interrupt handler can be attached per GPIO pin at a time on ESP32.
33+
34+
- Calling `attachInterrupt()` on a pin that already has an interrupt will **override** the previous one
35+
- This applies regardless of edge type (RISING, FALLING, CHANGE)
36+
- 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
37+
38+
## Code Overview
39+
40+
This example demonstrates a simple CHANGE mode lambda interrupt that:
41+
42+
- **Detects both button press and release** using a single interrupt handler
43+
- **Toggles LED only on button press** (FALLING edge)
44+
- **Reports both press and release events** to Serial output
45+
- **Uses proper debouncing** to prevent switch bounce issues
46+
- **Implements minimal lambda captures** for simplicity
47+
48+
## Lambda Function Pattern
49+
50+
### CHANGE Mode Lambda with IRAM Declaration
51+
```cpp
52+
// Global lambda declared with IRAM_ATTR for optimal ISR performance
53+
IRAM_ATTR std::function<void()> changeModeLambda = []() {
54+
// Debouncing check
55+
unsigned long currentTime = millis();
56+
if (currentTime - lastButtonInterruptTime < DEBOUNCE_DELAY_MS) {
57+
return; // Ignore bouncing
58+
}
59+
60+
// Determine edge type
61+
bool currentState = digitalRead(BUTTON_PIN);
62+
if (currentState == lastButtonState) {
63+
return; // No real state change
64+
}
65+
66+
// Update state and handle edges
67+
lastButtonInterruptTime = currentTime;
68+
lastButtonState = currentState;
69+
70+
if (currentState == LOW) {
71+
// Button pressed (FALLING edge)
72+
buttonPressCount++;
73+
buttonPressed = true;
74+
ledStateChanged = true; // Signal LED toggle
75+
} else {
76+
// Button released (RISING edge)
77+
buttonReleaseCount++;
78+
buttonReleased = true;
79+
}
80+
};
81+
82+
attachInterrupt(BUTTON_PIN, changeModeLambda, CHANGE);
83+
```
84+
85+
## Key Concepts
86+
87+
### Edge Detection in CHANGE Mode
88+
```cpp
89+
if (digitalRead(pin) == LOW) {
90+
// FALLING edge detected (button pressed)
91+
} else {
92+
// RISING edge detected (button released)
93+
}
94+
```
95+
96+
### Debouncing Strategy
97+
This example implements dual-layer debouncing:
98+
1. **Time-based**: Ignores interrupts within 50 ms of previous one
99+
2. **State-based**: Only processes actual state changes
100+
101+
### Main Loop Processing
102+
```cpp
103+
void loop() {
104+
// Handle LED changes safely outside ISR
105+
if (ledStateChanged) {
106+
ledStateChanged = false;
107+
ledState = !ledState;
108+
digitalWrite(LED_PIN, ledState);
109+
}
110+
111+
// Report button events
112+
if (buttonPressed) {
113+
// Handle press event
114+
}
115+
if (buttonReleased) {
116+
// Handle release event
117+
}
118+
}
119+
```
120+
121+
## Expected Output
122+
123+
```
124+
ESP32 Lambda FunctionalInterrupt Example
125+
========================================
126+
Setting up CHANGE mode lambda for LED toggle
127+
128+
Lambda interrupt configured on Pin 0 (CHANGE mode)
129+
Debounce delay: 50 ms
130+
131+
Press the button to toggle the LED!
132+
Button press (FALLING edge) will toggle the LED.
133+
Button release (RISING edge) will be detected and reported.
134+
Button includes debouncing to prevent mechanical bounce issues.
135+
136+
==> Button PRESSED! Count: 1, LED: ON (FALLING edge)
137+
==> Button RELEASED! Count: 1 (RISING edge)
138+
==> Button PRESSED! Count: 2, LED: OFF (FALLING edge)
139+
==> Button RELEASED! Count: 2 (RISING edge)
140+
```
141+
142+
## Pin Configuration
143+
144+
The example uses these default pins:
145+
146+
- `BUTTON_PIN`: BOOT_PIN (automatically assigned by the Arduino Core)
147+
- `LED_PIN`: LED_BUILTIN (may not be available for your board - please verify it)

0 commit comments

Comments
 (0)