Programming timer interruption

I'm posting here a simple project to create an interrupt timer on an ESP32 board for version 3.1.1 by Esspressif Systems. I had difficulties to find updated information to make this code, I hope it can be useful to someone !
This code creates an interrupt every 100ms and counts the number of interrupts.

There is the code :

#include "esp32-hal-timer.h"

const int ledPin = 2;  // pin of the LED
volatile bool flag = false;  // State of the LED

hw_timer_t *timer = NULL;  // declaration of timer
int c=0; 

// function call by the timer interruption
void IRAM_ATTR onTimer() {
    flag = true;  // Inverser l'état de la LED
  
}

void setup() {
    Serial.begin(115200);
    
    Serial.println("Start...");

    // Timer initialisation at a frequency of 1 MHz (1 µs per tick)
    timer = timerBegin(1000000);   

    if (timer == NULL) {
        Serial.println("Error with the start of the timer");
        while (1);
    }

    // Attaches the interrupt function to the timer
    timerAttachInterrupt(timer, &onTimer);

    // Configure an alarm to trigger the interrupt every 100 ms (100000 µs)    timerAlarm(timer, 100000, true, 0);  // 1000000 µs = 100ms = 0.1s

  
    // Start of the timer
    timerStart(timer);
}

void loop() {
  if (flag){
    c+=1;
    Serial.println(c);
    flag=false;
  }

  if (timer == NULL) {
    Serial.println("Erreur : timerBegin() a échoué !");
    while (1){
      Serial.println("Erreur !!");
    }
}

    //  Nothing in the loop because everything is managed by the interruption
}

1 Like

1,000,000us is 1 second, not a 100ms

see this and this

Thank you, the comment was not on the right line.
And thank you for the documentation, I couldn't find the documentation about the new version of eps32 by expressif

I would have at least a delay(2000) in the loop.

An infinite loop() without an explicit yield(), delay() etc. may cause a watch dog timer reset which could make troubleshooting more complex.

This comment is a bit misleading because the parameter 2 is not primarily a time period, it is a number of ticks of the timer. :

If you are using the timer for only one purpose, as in your example, then you could instead have set the frequency of timer to 10Hz and set the alarm to trigger on every tick.

if I run your code on an ESP32 I get an exception - serial monitor output

Start...
E (20) gptimer: gptimer_start(396): timer is not ready for a new start

if I replace your statement

    timerStart(timer);

with

      timerAlarm(timer, 100000, true, 0);

timer interrupt goes off every 100mSec - serial monitor displays

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

Interesting. The OP's example looks close enough to the one here for the ESP32 Arduino 3.x core if that is indeed what you are running. Timer - - — Arduino ESP32 latest documentation

I am using ESP32 core 3.1.1
think the problem is timerbegin() initializes the timer and starts it
the following timerstart() attempts to start the timer again and throws the exception

E (20) gptimer: gptimer_start(396): timer is not ready for a new start

the ESP32 documentation timer example uses

// Set alarm to call onTimer function every second (value in microseconds).
  // Repeat the alarm (third parameter) with unlimited count = 0 (fourth parameter).
  timerAlarm(timer, 1000000, true, 0);
1 Like

Ah yes. On looking more closely at the quoted official example, the statement timerStart(timer); does not appear. Actually, the error message you got is quite explicit and the behaviour of timerBegin() is also clear and documented to read "This function is used to configure the timer. After successful setup the timer will automatically start."

Back to the OP to tidy up their code. The timerAlarm() function is also erroneously in a comment.

FWIW, this code (originally written for Core 3.0.7) compiles and works in Core 3.1.1. So, any differences are not due to changes in the IDF APIs, but the Arduino APIs that wrap them.

#include "Arduino.h"
#include "driver/gptimer.h"

void BlinkingTask(void *params);
bool timerCallback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data);

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println("Starting");
  xTaskCreatePinnedToCore(BlinkingTask, "Blinking", 1000, NULL, 5, NULL, ARDUINO_RUNNING_CORE);
}

void loop() {
  vTaskDelete(NULL);
}

void BlinkingTask(void *params) {
  TaskHandle_t myTaskHandle { xTaskGetCurrentTaskHandle() };
  pinMode(4, OUTPUT);
  digitalWrite(4, LOW);

  gptimer_handle_t gptimer = NULL;
  const gptimer_config_t timer_config = {
      .clk_src = GPTIMER_CLK_SRC_DEFAULT,
      .direction = GPTIMER_COUNT_UP,
      .resolution_hz = 1000000, // 1MHz, 1 tick=1us
      };
  assert((gptimer_new_timer(&timer_config, &gptimer)==ESP_OK) && "Failed to Create Timer");

  gptimer_event_callbacks_t cbs = {
      .on_alarm = timerCallback,
  };
  assert((gptimer_register_event_callbacks(gptimer, &cbs, static_cast<void*>(myTaskHandle))==ESP_OK) && "Failed to Register Callback");
  assert((gptimer_enable(gptimer)==ESP_OK) && "Failed to Enable Timer");

  gptimer_alarm_config_t alarm_config ={1000000, 0, true};
  assert((gptimer_set_alarm_action(gptimer, &alarm_config)==ESP_OK) && "Failed to Set Alarm Action");

  assert((gptimer_start(gptimer)==ESP_OK) && "Failed to Start Timer");

  for (;;) {
    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
    digitalWrite(4, HIGH);
    vTaskDelay(5);
    digitalWrite(4, LOW);
  }
}

bool IRAM_ATTR timerCallback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) {
  TaskHandle_t handle { static_cast<TaskHandle_t>(user_data) };
  BaseType_t pxHigherPriorityTaskWoken { pdFALSE };
  vTaskNotifyGiveFromISR(handle, &pxHigherPriorityTaskWoken);
  return pxHigherPriorityTaskWoken == pdTRUE;
}

the ESP32 timer is very useful
by calling timerAlarm() in the timer ISR one can have a sequence of events of different periods, e.g. events at 100Usec 350uSec 750uSec

// ESP32 timer interrupts -   three pulses 100Usec 350uSec 750uSec

#define pin 19

hw_timer_t *timer = NULL;

// timer ISR invert pin level
volatile int counter = 0;
void ARDUINO_ISR_ATTR onTimer() {
  static int timer1 = 350;
  counter++;
  // digitalWrite(pin, !digitalRead(pin));
  gpio_set_level((gpio_num_t)pin, HIGH);  // HIGH pulse
  // set next interrupt time
  timerAlarm(timer, timer1, true, 0);
  if (timer1 == 100) timer1 = 350;
  else if (timer1 == 350) timer1 = 750;
  else timer1 = 100;
  gpio_set_level((gpio_num_t)pin, LOW);  // LOW
}

void setup() {
  Serial.begin(115200);
  pinMode(pin, OUTPUT);
  timer = timerBegin(1000000);            // Set timer frequency Mhz
  timerAttachInterrupt(timer, &onTimer);  //  Attach onTimer function to our timer.
  // Set alarm to call onTimer function(value in microseconds).
  // Repeat the alarm (third parameter) with unlimited count = 0 (fourth parameter).
  timerAlarm(timer, 150, true, 0);  // start with 150uSec pulse
}

void loop() {
  // display counter every second
  static long timer1 = millis();
  if (millis() - timer1 > 1000) {
    timer1 = millis();
    Serial.println(counter);
    counter = 0;
  }
}

gives a pulse sequence

although for pulse sequences I prefer using the Remote Control Transceiver (RMT)

Thank you, I will correct that. Just a dalay(100); in the while{} ?

I understand that, but will there be a real difference in the program? Is it just easier to understand the code?

I don't understand why you have this error, my code works perfectly on my esp32 and I'm also using version 3.1.1...
However, I tried commenting out the timerStart(timer); and that works too...

That would be an improvement.

Maybe just add a comment so it is clear that that parameter is timer ticks.

Generally:

  1. Correct the code in #1 so that the function timerAlarm() is active. Currently it is effectively commented out.

  2. Yes, remove the unwanted timerStart(timer); . It apparently causes a problem and is not in the official example.

  3. Test the corrected code to check it behaves as you originally intended.