ESP32 one-shot timers: how to?

Hi, I'm trying to define a one-time, re-triggerable timer on ESP32 using the Arduino-ESP32 Timer library from Espressif (Timer — Arduino-ESP32 2.0.6 documentation) but I don't get the behaviour that I'm expecting.

The main idea is to start a timer for a given amount of time (say 5 seconds) whenever an external button is pushed. After this time, the interrupt callback is executed. See the full code below.

According to the docs, the third parameter of timerAlarmWrite(timer, DELAY * 1000 * 1000, false) is a boolean that specifies whether the timer will auto-reload or not, and should be set to true for a periodic alarm, and false otherwise.

Now, if you see the code below, the alarm is enabled when the button is pressed, and disabled when pressed again.

The problem is that the interrupt callback function onTimeout is called immediately after pressing the button, instead of waiting the 5 second delay. Why is this the case? How do you make it trigger the alarm only after the delay has passed?

Thanks

Full code:

#include <Arduino.h>
#include <time.h>

#define LED 21
#define DELAY 3
#define BUTTON 5
 
hw_timer_t *timer = NULL;
unsigned int count = 0;

String getTimeStr() {
  char time_of_day_sec[20];
  char microseconds[8];
  char full_timestamp[30];
  struct tm timeinfo;
	struct timeval tv;
	gettimeofday(&tv, NULL);
  time_t now_sec = tv.tv_sec;
  suseconds_t now_usec = tv.tv_usec;
  localtime_r(&now_sec, &timeinfo);
  strftime(time_of_day_sec, sizeof(time_of_day_sec), "%Y-%m-%dT%H:%M:%S", &timeinfo);
  sprintf(microseconds, "%06d", now_usec);
  sprintf(full_timestamp, "%s.%sZ", time_of_day_sec, microseconds);
  return String(full_timestamp);
}

// void IRAM_ATTR onTimeout()
void ARDUINO_ISR_ATTR onTimeout()
{
  Serial.println("timeout alt");
  digitalWrite(LED, !digitalRead(LED));
}
void setup()
{
  Serial.begin(115200);
  Serial.println("setup");
  pinMode(LED, OUTPUT);
  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimeout, true);
  timerAlarmWrite(timer, DELAY * 1000 * 1000, false);
}

void loop()
{
  Serial.printf("%s - loop iteration #%u\n", getTimeStr().c_str(), ++count);
  static bool active = false;
  static bool timerEnabled = false;
  static int lastButtonState = 1;
  int buttonState = digitalRead(BUTTON);
  if (!buttonState && lastButtonState) {
    Serial.println("button pressed");
    active = !active;
  }
  if (active) {
    Serial.println("active");
    if (!timerEnabled) {
      Serial.println("enabling timer");
      timerAlarmEnable(timer);
      timerEnabled = true;
    }
  }
  else {
    Serial.println("inactive");
    if (timerEnabled) {
      Serial.println("disabling timer");
      timerAlarmDisable(timer);
      timerEnabled = false;
    }
  }
  delay(1000);
}

Why this, and not the usual

  if (buttonState != lastButtonState) {
    if (buttonState == LOW) { // or HIGH, depends on switch wiring

I think re-triggerable means that if another trigger event occurs while timing, then the delay period is re-started. So, if a 5 second period is triggered again after 4 seconds, the total timing period will be 9 seconds.

Well, yes, I can do that, and in fact I just tried it, but it doesn't have anything to do with the main problem, which still happens: the interrupt callback is executed immediately after pushing the button.

Right, my wording was not accurate. But the main question remains: why is the callback executed right away instead of waiting after the delay specified for the timer?

No pinmode to setup the GPIO pins?

Is the timer set for auto retrigger?

Are you looking for a retriggerable one shot timer?

I've figured out a way to make it work by simplifying my code. The critical thing is that I have to call timerRestart, timerAlarmWrite and timerAlarmEnable and timerStart every time I want the timer to restart. While the code works, it's still unclear to me why I need to make those four calls. I would have expected that timerRestart or timerAlarmEnable would be enough. Unfortunately the library is not well documented.

#include <Arduino.h>
#include <time.h>

#define LED 21
#define DELAY 3
#define BUTTON 5
 
hw_timer_t *timer = NULL;
unsigned int count = 0;
bool timerEnabled = false;

// String getTimeStr() {
//...
// }

// void IRAM_ATTR onTimeout()
void ARDUINO_ISR_ATTR onTimeout()
{
  Serial.println("timeout alt");
  digitalWrite(LED, LOW);
  timerStop(timer);
  timerEnabled = false;
}
void setup()
{
  Serial.begin(115200);
  Serial.println("setup");
  pinMode(LED, OUTPUT);
  timer = timerBegin(0, 80, true);
  timerStop(timer);
  timerAttachInterrupt(timer, &onTimeout, true);
  timerAlarmWrite(timer, DELAY * 1000 * 1000, false);
  timerAlarmEnable(timer);
}

void loop()
{
  Serial.printf("%s - loop iteration #%u\n", getTimeStr().c_str(), ++count);
  static bool active = false;
  static int lastButtonState = 1;
  int buttonState = digitalRead(BUTTON);
  if (buttonState != lastButtonState) {
    Serial.println("button pressed");
    digitalWrite(LED, HIGH);
    timerRestart(timer);
    timerAlarmWrite(timer, DELAY * 1000 * 1000, false);
    timerAlarmEnable(timer);
    timerStart(timer);
    timerEnabled = true;
  }
  delay(1000);
}
setup()
{
  // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_timer.html?highlight=hardware%20timer High Resoultion Timer API
  esp_timer_create_args_t oneshot_timer_args = {}; // initialize High Resoulition Timer (HRT) configuration structure
  oneshot_timer_args.callback = &oneshot_timer_callback; // configure for callback, name of callback function
  esp_timer_create( &oneshot_timer_args, &oneshot_timer ); // assign configuration to the HRT, receive timer handle
}


void fDoParticleDetector( void * parameter )
{
  /*
    ug/m3     AQI                 Lvl AQ (Air Quality)
    (air Quality Index)
    0-35     0-50                1   Excellent
    35-75    51-100              2   Average
    75-115   101-150             3   Light pollution
    115-150  151-200             4   moderate
    150-250  201-300             5   heavy
    250-500  >=300               6   serious
  */
  float ADbits = 4095.0f;
  float uPvolts = 3.3f;
  float adcValue = 0.0f;
  float dustDensity = 0.0f;
  float Voc = 0.6f; // Set the typical output voltage, when there is zero dust.
  const float K = 0.5f; // Use the typical sensitivity in units of V per 100ug/m3.
  xEventGroupWaitBits (eg, evtWaitForBME, pdTRUE, pdTRUE, portMAX_DELAY );
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 100; //delay for mS
  for (;;)
  {
    //enable sensor led
    gpio_set_level( GPIO_NUM_4, HIGH ); // set gpio 4 to high to turn on sensor internal led for measurement
    esp_timer_start_once( oneshot_timer, 280 ); // trigger one shot timer for a 280uS timeout, warm up time.
    xEventGroupWaitBits (eg, evtDoParticleRead, pdTRUE, pdTRUE, portMAX_DELAY ); // event will be triggered by the timer expiring, wait here for the 280uS
    adcValue = float( adc1_get_raw(ADC1_CHANNEL_0) ); //take a raw ADC reading from the dust sensor
    gpio_set_level( GPIO_NUM_4, LOW );//Shut off the sensor LED
    adcValue = ( adcValue * uPvolts ) / ADbits; //calculate voltage
    dustDensity = (adcValue / K) * 100.0; //convert volts to dust density
    if ( dustDensity < 0.0f )
    {
      dustDensity = 0.00f; // make negative values a 0
    }
    if ( xSemaphoreTake( sema_PublishPM, 0 ) == pdTRUE )  // don't wait for semaphore to be available
    {
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
      //log_i( "ADC volts %f Dust Density = %ug / m3 ", adcValue, dustDensity ); // print the calculated voltage and dustdensity
      MQTTclient.publish( topicInsidePM, String(dustDensity).c_str() );
      xSemaphoreGive( sema_MQTT_KeepAlive );
      x_eData.PM2 = dustDensity;
    }
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
    //log_i( " high watermark % d",  uxTaskGetStackHighWaterMark( NULL ) );
  }
  vTaskDelete( NULL );
}// end fDoParticleDetector()

My use of the timer in one shot mode.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.