ESP32 Timer reloading

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.

Hi @bdrmachine ,

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;
}

To be tested on Wokwi: https://wokwi.com/projects/425693519758915585

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 ...

Good luck!
ec2021

post 1 specified a delay 6seconds but post 3 mentions 5minutes
anyway this code delays for 6 seconds

// ESP32 external interrupt test pin 5 rising edge set timer for 6seconds

hw_timer_t *timer = NULL;

// timer intrrupt routine
volatile bool timerInterrupt = false;
void ARDUINO_ISR_ATTR onTimer() {
  timerInterrupt = true;
}

#define RXPIN 5

volatile bool interrupt = false;  // true on interrupt
// pin rising edge interrupt handler
void IRAM_ATTR handleInterrupt() {
  interrupt = true;
  // start timer for 6 seconds - Attach onTimer function to our timer.
  timer = timerBegin(1000000);            // timer frequency 1MHz
  timerAttachInterrupt(timer, &onTimer);
  timerAlarm(timer, 6000000, false, 0);   // interrupt once
}

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() {
  static unsigned long timer1;
  // if had inperrupt on pin 5 display message
  if (interrupt) {
    interrupt = false;
    Serial.println("interrupt on pin 5");
    timer1 = millis();
  }
  // had timer interrupt display time
  if (timerInterrupt) {
    timerInterrupt = false;
    Serial.printf("timer Interrupt %lumSec\n", millis() - timer1);
  }
}

serial monitor output

ESP32 external interrupt test on pin 5
interrupt on pin 5
timer Interrupt 6000mSec
interrupt on pin 5
timer Interrupt 6000mSec
interrupt on pin 5
timer Interrupt 6000mSe

after pressing button which takes pin 5 low the timer messages appears
using ESP32 core 3.1.3

Hi @horace !

Looks nice!

Just a question: Does timerBegin() create a new timer instance? If yes the previous timer should be freed calling timerEnd(timer)...

Could be done just before the new timer is created

if (timer) {timerEnd(timer);};

In the meantime I found the functions

timerStart(timer)
timerStop(timer) and
timer restart(timer)

which might be interesting... Too late for me today...:wink:

Regards
ec2021

might want to take a peek at High Resolution Timer (ESP Timer)..

experimented with them a while back..
An example, One shot that reloads itself, just blinks onboard led once per second..

good luck.. ~q

Thanks for the reply but The compiler flags numerous error in your code and won't build.

as @ec2021 mentioned in post 5 you are running ESP32 core 2.x the code I posted used core V3..1.3 - see Migration from ESP32 core 2.x to 3.0

With all these moving parts My hats off to you. Thanks Much for pointing me in the right direction!!

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.

#include <Arduino.h>
byte SW  {35};
byte LED {38};
volatile byte _ledState = LOW;

hw_timer_t * timer = NULL;
boolean detectLowSignal = false;
boolean Triggered = false;

void ARDUINO_ISR_ATTR onTimer() 
{
  digitalWrite(LED, LOW) ;
  Triggered = true;
}

void setup() 
{
  Serial.begin(115200);
  pinMode(SW, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
}

void loop() 
{
  if ( !digitalRead(SW) && timer == nullptr)  // replaced !digitalRead(LED)
  {
    digitalWrite(LED, HIGH);
    detectLowSignal = true;
    Serial.println("Led OFF");

    {// start timer }
      timer = timerBegin(1000000);
      timerAttachInterrupt(timer, &onTimer);
      timerAlarm(timer, 6000000, false, 0);
      Serial.println("Timer started");      
    }
  }

  if (Triggered) 
  {
    if (timer)
    { // timer stop
      timerEnd(timer);
      timer = nullptr; // Explicitly set the timer pointer to nullptr
      Serial.println("Timer stopped");
    }
    Serial.println("Led ON");
    detectLowSignal = false;
    Triggered = false;
  }
}