I have been struggling with a piece of code that will trigger an interrupt X seconds after a input is received, 6 seconds in my code example. The timer needs to be re-triggered every time the input arrives. There is no way the input will still be present at the end of the timer delay and it will not reappear until sometime after. The only way I have been able to get it working is with a guard variable (detectLowSignal). I do think there is a easier way by disabling or not reloading the timer but I haven't succeed in that technique. I would appreciate any input from members familiar with the workings of ESP32 timers.
#include <Arduino.h>
#define LED_PIN 38 // Pin connected to the LED
#define INPUT_PIN 35 // Pin to detect the LOW signal
hw_timer_t * timer = NULL; // Declare a pointer for Timer 3
bool toggleLED = false; // Flag to toggle the LED
bool detectLowSignal = false; // Flag to indicate LOW signal detection
void IRAM_ATTR onTimer() {
// This function is called by Timer 3 interrupt every 6 seconds
if (detectLowSignal)
{ // Only toggle LED after LOW signal is detected
toggleLED = true; // Flag to toggle LED after 6 seconds
}
}
void setup() {
pinMode(LED_PIN, OUTPUT); // Set LED pin as output
pinMode(INPUT_PIN, INPUT_PULLUP); // Set input pin for detecting LOW signal
Serial.begin(115200); // Start serial communication at 115200 baud rate
// Initialize Timer 3 to trigger every 6 seconds (6,000,000 microseconds)
timer = timerBegin(3, 80, true); // Timer 3, prescaler of 80 for 1-second intervals
timerAttachInterrupt(timer, &onTimer, true); // Attach onTimer function as interrupt handler
timerAlarmWrite(timer, 6000000, true); // Set the timer to trigger every 6 seconds (6,000,000 microseconds)
}
void loop()
{
// Check for a LOW signal on INPUT_PIN (GPIO 35)
int inputState = digitalRead(INPUT_PIN);
if (inputState == LOW && !detectLowSignal)
{ // Detect LOW signal and not already in process
detectLowSignal = true; // Start the 6-second timer
timerAlarmEnable(timer); // Enable the timer after LOW signal detection
Serial.println("LOW signal detected, starting 6-second delay...");
}
if (toggleLED)
{
digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // Toggle the LED state
Serial.println("LED Toggled!");
toggleLED = false; // Reset the flag after toggling LED
detectLowSignal = false; // Reset LOW signal detection flag
}
delay(100); // Small delay to avoid flooding the serial monitor too quickly
}
what is the "input" which triggers the six second delay?
if a rising edge on a digital pin
// ESP32 external interrupt test pin 5 rising edge
#define RXPIN 5
volatile bool interrupt = false; // true on interrupt
// interrupt handler
void IRAM_ATTR handleInterrupt() {
interrupt = true;
}
void setup() {
Serial.begin(115200);
delay(3000);
Serial.println("\n\nESP32 external interrupt test on pin 5");
pinMode(RXPIN, INPUT_PULLUP);
attachInterrupt(digitalPinToGPIONumber(RXPIN), handleInterrupt, RISING);
}
void loop() {
// if had inperrupt on pin 5 display message
if (interrupt) {
interrupt = false;
Serial.println("interrupt on pin 5");
}
}
on pin 5 rising edge a message is displayed - serial monitor output
ESP32 external interrupt test on pin 5
interrupt on pin 5
interrupt on pin 5
interrupt on pin 5
interrupt on pin 5
interrupt on pin 5
interrupt on pin 5
your interrupt routine could start the timer which could interrupt six seconds later
In the long view of things, I want the delay to be triggered every time a DS3231 RTC interrupts. The RTC interrupt happens every 30 minutes which will turn on a motor. I want the motor on for 5 minutes. I need to figure out how to use a ESP32 timer to start when it sees the RTR interrupt and send its own interrupt trigger after 5 minutes is up so the code knows when to turn the motor off again. The code I enclosed was a test that used a switch as the trigger and a LED that lights for 6 seconds. This was just a test to get the delay feature debugged. Thanks for the reply but your code is not using the timer function I need.
You could more easily do the entire thing without any interrupts. Every time through loop(), if the motor is not running, check if it is time to start the motor. If it is time to start the motor, turn it on and note the time.
If the motor is running, check how long it has been running (millis() - startTime) and if it has been running long enough, turn it off.
It is more of a state machine approach. Just another way to accomplish your goal.
I second @blh64 's post that it's much easier to realize the required functionality inside loop(). However if using the timer is the main goal ("if the journey is the destination") feel free to check this out:
/*
Forum: https://forum.arduino.cc/t/esp32-timer-reloading/1364408
Wokwi: https://wokwi.com/projects/425693519758915585
References: [1] https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/timer.html
[2] https://docs.espressif.com/projects/arduino-esp32/en/latest/migration_guides/2.x_to_3.0.html#timer
2025/03/17
ec2021
Due to an example in reference [1] it is allowed to use digitalRead/write in the ISR outside of the critical part
As the timer did not stop (as expected from the documentation) it is freed and newly created in the routines
*/
#include <Arduino.h>
constexpr byte BTN_START_ALARM {12};
constexpr byte LED_PIN {14};
volatile byte _ledState = LOW;
hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
boolean detectLowSignal = false;
void ARDUINO_ISR_ATTR onTimer() {
setLed(LOW);
}
void setup() {
Serial.begin(115200);
pinMode(BTN_START_ALARM, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
byte inputState = digitalRead(BTN_START_ALARM);
if (inputState == LOW && !ledState()) {
setLed(HIGH);
detectLowSignal = true;
Serial.println("Led ON");
startTimer();
}
if (detectLowSignal && !ledState()) {
stopTimer();
Serial.println("Led OFF");
detectLowSignal = false;
}
}
void startTimer(){
timer = timerBegin(1000000);
timerAttachInterrupt(timer, &onTimer);
timerAlarm(timer, 6000000, false, 0);
Serial.println("Timer started");
}
void stopTimer(){
if (timer){
timerEnd(timer);
Serial.println("Timer stopped");
}
}
void setLed(byte state){
digitalWrite(LED_PIN, state);
portENTER_CRITICAL_ISR(&timerMux);
_ledState = state;
portEXIT_CRITICAL_ISR(&timerMux);
}
byte ledState(){
portENTER_CRITICAL_ISR(&timerMux);
byte state = _ledState;
portEXIT_CRITICAL_ISR(&timerMux);
return state;
}
Please be aware that the sketch is based on the ESP32 Arduino API 3.0 not on 2.x as your sketch and other pins are used for the led and a button.
As the timer (at least on Wokwi) did not stop after one shot, I finally created and deleted the timer inside loop(). The ISR does not toggle but switch the led off. The variable shared between loop() and ISR is handled using the ESP32 typical portENTER/port_EXIT_CRITICAL_ISR() macros. The recent ledState() is used as an additional criteria to enter the if clauses to start and to stop the timer only when the led is off.
In total I think it's quite likely less effort to use millis() functions in loop() for the timing ... But just for the fun of it ...
I have had some time to play around with the code that this forum has so graciously provided. I tried to boil it down to the shortest and most understandable for me, while I'm sure a lot of you find this stuff trivial. One gotcha I fought with was the testing to see if (timer) was defined. It seems that timerEnd(timer) doesn't set timer back to nullptr as expected so I had to manually do so. In my view this is a error in version 3.0 of the api. The test code is enclosed in hopes it helps someone out there. Here is a brief explanation of the code: If a switch is pressed turn a LED off and start a timer. The timer will create a interrupt and turn the LED back on.