High Resolution Timer (ESP Timer) with ESP-IDF

In this ESP-IDF series of tutorials, we will introduce you to ESP timer API by going through an example (system/esp_timer) provided by ESP-IDF. This high resolution timer APIs feature the creation of multiple timers though a a single hardware timer. Several timers can easily be managed through callback functions thus offering a lesser number of complexities while running multiple timers. ESP-IDF provides a driver esp_timer that contains APIs to create both periodic and one-shot timers with a time resolution of microsecond and 64 bit range for the hardware timers.

The esp_timer example is found in the ESP-IDF extension examples under system. Through this example, we will show you how to create two different types of timers, one-shot and periodic and manage them both by using the ESP Timer APIs. So let us begin!

ESP32 High Resolution Timer with ESP-IDF

In this section we will look at an example sketch provided by ESP-IDF to learn how to setup timers using esp_timer APIs.

Create Example Project

Open your VS Code and head over to View > Command Palette. Type ESP-IDF: New Project in the search bar and press enter.

Specify the project name and directory. We have named our project ‘ESP_IDF_ESP_TIMER.’ For the ESP-IDF board, we have chosen the custom board option. For ESP-IDF target, we have chosen ESP32 module.

Click ‘Choose Template’ button to proceed forward.

ESP32 ESP Timer using ESP-IDF Project 1

In the Extension, select ESP-IDF option:

ESP-IDF in VS Code New Project 2

We will click the ‘esp_timer’ under the System tab. Now click ‘Create project using template esp_timer.’

ESP32 ESP Timer using ESP-IDF Project 2

You will get a notification that the project has been created. To open the project in a new window, click ‘Yes.’

This opens our ESP_IDF_ESP_TIMER project that we created inside the EXPLORER tab. There are several folders inside our project folder. This is the same for every project which you will create through ESP-IDF Explorer. The main folder contains the source code meaning the main.c file will be found here.

Now go to main > esp_timer_example_main.c and open it.

The following code opens up. This example code creates two timers. A periodic and a one-shot timer. The periodic timer will be called after every 0.5 seconds to print a message in the terminal. The one-shot timer will run and re-start the periodic timer with a period of 1 second.

ESP-IDF ESP Timer Code

/* esp_timer (high resolution timer) example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "esp_timer.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include "sdkconfig.h"

static void periodic_timer_callback(void* arg);
static void oneshot_timer_callback(void* arg);

static const char* TAG = "example";

void app_main(void)
{
    /* Create two timers:
     * 1. a periodic timer which will run every 0.5s, and print a message
     * 2. a one-shot timer which will fire after 5s, and re-start periodic
     *    timer with period of 1s.
     */

    const esp_timer_create_args_t periodic_timer_args = {
            .callback = &periodic_timer_callback,
            /* name is optional, but may help identify the timer when debugging */
            .name = "periodic"
    };

    esp_timer_handle_t periodic_timer;
    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
    /* The timer has been created but is not running yet */

    const esp_timer_create_args_t oneshot_timer_args = {
            .callback = &oneshot_timer_callback,
            /* argument specified here will be passed to timer callback function */
            .arg = (void*) periodic_timer,
            .name = "one-shot"
    };
    esp_timer_handle_t oneshot_timer;
    ESP_ERROR_CHECK(esp_timer_create(&oneshot_timer_args, &oneshot_timer));

    /* Start the timers */
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, 500000));
    ESP_ERROR_CHECK(esp_timer_start_once(oneshot_timer, 5000000));
    ESP_LOGI(TAG, "Started timers, time since boot: %lld us", esp_timer_get_time());

    /* Print debugging information about timers to console every 2 seconds */
    for (int i = 0; i < 5; ++i) {
        ESP_ERROR_CHECK(esp_timer_dump(stdout));
        usleep(2000000);
    }

    /* Timekeeping continues in light sleep, and timers are scheduled
     * correctly after light sleep.
     */
    ESP_LOGI(TAG, "Entering light sleep for 0.5s, time since boot: %lld us",
            esp_timer_get_time());

    ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(500000));
    esp_light_sleep_start();

    ESP_LOGI(TAG, "Woke up from light sleep, time since boot: %lld us",
                esp_timer_get_time());

    /* Let the timer run for a little bit more */
    usleep(2000000);

    /* Clean up and finish the example */
    ESP_ERROR_CHECK(esp_timer_stop(periodic_timer));
    ESP_ERROR_CHECK(esp_timer_delete(periodic_timer));
    ESP_ERROR_CHECK(esp_timer_delete(oneshot_timer));
    ESP_LOGI(TAG, "Stopped and deleted timers");
}

static void periodic_timer_callback(void* arg)
{
    int64_t time_since_boot = esp_timer_get_time();
    ESP_LOGI(TAG, "Periodic timer called, time since boot: %lld us", time_since_boot);
}

static void oneshot_timer_callback(void* arg)
{
    int64_t time_since_boot = esp_timer_get_time();
    ESP_LOGI(TAG, "One-shot timer called, time since boot: %lld us", time_since_boot);
    esp_timer_handle_t periodic_timer_handle = (esp_timer_handle_t) arg;
    /* To start the timer which is running, need to stop it first */
    ESP_ERROR_CHECK(esp_timer_stop(periodic_timer_handle));
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer_handle, 1000000));
    time_since_boot = esp_timer_get_time();
    ESP_LOGI(TAG, "Restarted periodic timer with 1s period, time since boot: %lld us",
            time_since_boot);
}

How the Code Works?

The High Resolution Timer APIs are defined in components/esp_timer/include/esp_timer.h. Therefore, we will include this driver at the start along with other libraries which are needed.

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "esp_timer.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include "sdkconfig.h"

In esp_timer set of APIs, a single timer is depicted by esp_timer_handle_t type. As this example creates two timers, hence two esp timer handles will be used, one for the periodic timer and the other for the one-shot timer.

esp_timer_handle_t periodic_timer;
esp_timer_handle_t oneshot_timer;

Callbacks

Each timer has a callback function which is called every time the timer progresses. It is called from the esp_timer task.

static void periodic_timer_callback(void* arg);
static void oneshot_timer_callback(void* arg);

The periodic timer will be called after every 0.5 seconds (period of 500ms) and it will print the time since boot in the terminal, using the function, esp_timer_get_time() which returns the number of microseconds since boot. This will be achieved through the callback function for the periodic timer which is given below.

static void periodic_timer_callback(void* arg)
{
    int64_t time_since_boot = esp_timer_get_time();
    ESP_LOGI(TAG, "Periodic timer called, time since boot: %lld us", time_since_boot);
}

The one-shot timer will run and re-start the periodic timer with a period of 1 second, hence changing the period of the periodic timer. Below you can view the callback function for the one-shot timer. It first prints the time since boot when the one-shot timer gets started. Then, it stops the periodic timer and restarts it with a different time period (1 second instead of 0.5 second). Finally, it prints that the periodic timer has been restarted with 1s period and also specifies the time since boot.

static void oneshot_timer_callback(void* arg)
{
    int64_t time_since_boot = esp_timer_get_time();
    ESP_LOGI(TAG, "One-shot timer called, time since boot: %lld us", time_since_boot);
    esp_timer_handle_t periodic_timer_handle = (esp_timer_handle_t) arg;
    /* To start the timer which is running, need to stop it first */
    ESP_ERROR_CHECK(esp_timer_stop(periodic_timer_handle));
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer_handle, 1000000));
    time_since_boot = esp_timer_get_time();
    ESP_LOGI(TAG, "Restarted periodic timer with 1s period, time since boot: %lld us",
            time_since_boot);
}

Create Timer

esp_timer_create() function is used to create an esp_timer instance. It takes in two parameters. The first parameter is the pointer to a structure that contains the timer creation arguments. The second parameter is the pointer to the esp_timer_handle_t that contains the timer handle that was previously created.

To create the periodic timer, the following parameters are used.

ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));

To create the one-shot timer, the following parameters are used.

ESP_ERROR_CHECK(esp_timer_create(&oneshot_timer_args, &oneshot_timer));

Before calling the esp_timer_create() function for a specific timer, we first define a structure that contains the timer creation arguments and the esp timer handle.

This is the timer configuration and the handle that will be passed to esp_timer_create() for the periodic timer. Note that the timer configuration consists of the callback function which is called when the timer elapses and the name of the timer.

const esp_timer_create_args_t periodic_timer_args = {
            .callback = &periodic_timer_callback,
            /* name is optional, but may help identify the timer when debugging */
            .name = "periodic"
    };

    esp_timer_handle_t periodic_timer;

This is the timer configuration and the handle that will be passed to esp_timer_create() for the one-shot timer.

    const esp_timer_create_args_t oneshot_timer_args = {
            .callback = &oneshot_timer_callback,
            /* argument specified here will be passed to timer callback function */
            .arg = (void*) periodic_timer,
            .name = "one-shot"
    };
    esp_timer_handle_t oneshot_timer;

Start Timer

After creating the timer then it is started. To start a periodic timer, esp_timer_start_periodic() function is used. This function call will start the periodic timer that will trigger after every time period in microseconds that is specified. It takes in two parameters. The first parameter is the timer handle and the second parameter is the time period in microseconds with which the callback function is called. The timer stops when esp_timer_stop() is called.

  ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, 500000));

To start a one-shot timer, esp_timer_start_once() function is used. This function call will start the one-shot timer. It takes in two parameters. The first parameter is the timer handle and the second parameter is the timer timeout in microseconds after which the callback function is called. This timer stops when its callback function is called.

ESP_ERROR_CHECK(esp_timer_start_once(oneshot_timer, 5000000));

At this moment, the terminal will display that the timers have been started along with the time since boot which is accessed though the esp_timer_get_time() function.

ESP_LOGI(TAG, "Started timers, time since boot: %lld us", esp_timer_get_time());

Note: When starting the periodic or one-shot timer, make sure the timer is not already running. If you want to restart a running timer, then you first need to stop it by using esp_timer_stop() function and then start it by using esp_timer_start_periodic() or esp_timer_start_once().

The following for loop, prints the timer information on the terminal after every 2 seconds. esp_timer_dump() function is used to dump the list of timers to a stream which is ‘stdout’ in this case.

   for (int i = 0; i < 5; ++i) {
        ESP_ERROR_CHECK(esp_timer_dump(stdout));
        usleep(2000000);
    }

Enabling Light Sleep

Next, the ESP32 board goes into light sleep mode for 0.5s. This is achieved by calling the function esp_sleep_enable_timer_wakeup(500000) and esp_light_sleep_start(). The terminal prints the time since boot when the board goes to light sleep and after it wakes up.

    ESP_LOGI(TAG, "Entering light sleep for 0.5s, time since boot: %lld us",
            esp_timer_get_time());

    ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(500000));
    esp_light_sleep_start();

    ESP_LOGI(TAG, "Woke up from light sleep, time since boot: %lld us",
                esp_timer_get_time());

    /* Let the timer run for a little bit more */
    usleep(2000000);

Delete Timers

Finally, we will delete our periodic and one-shot timers. To do that, we will first have to stop the timers. If the one-shot timer has already finished its run then it need not be stopped. Hence, we will only be stopping the periodic timer. This is done by calling the function esp_timer_stop() and passing the time handle as a parameter inside it.

 ESP_ERROR_CHECK(esp_timer_stop(periodic_timer));

To delete a timer, we will call the function esp_timer_delete() and pass the timer handle as a parameter inside it. At this stage, both the timers will be deleted and the terminals prints the message that the timers have been stopped and are deleted.

   
    ESP_ERROR_CHECK(esp_timer_delete(periodic_timer));
    ESP_ERROR_CHECK(esp_timer_delete(oneshot_timer));
    ESP_LOGI(TAG, "Stopped and deleted timers");

Compiling the Sketch

To flash your chip, type the following command in the ESP-IDF serial terminal. Remember to replace the COM port with the one through which your board is connected.

idf.py -p COMX flash monitor
ESP32 ESP Timer using ESP-IDF Project Flash Chip

After the code flashes successfully, you can view the messages in the ESP-IDF terminal.

ESP32 ESP Timer using ESP-IDF Project Terminal

Lets dissect the messages one by one to understand them.

Firstly, the periodic and the one-shot timers get created.

ESP32 ESP Timer using ESP-IDF Project Terminal 2

Next, the initial timer dump is printed.

ESP32 ESP Timer using ESP-IDF Project Terminal 3

The Periodic Timer runs with a period of 500000us (500ms/0.5s)

ESP32 ESP Timer using ESP-IDF Project Terminal 4

The one-shot timer runs after 5s and restarts the periodic timer with a period of 1s.

ESP32 ESP Timer using ESP-IDF Project Terminal 5

ESP32 enters light sleep and the time keeping stays intact after the board exits the this mode.

ESP32 ESP Timer using ESP-IDF Project Terminal 6

The periodic timers is stopped then both of the timers are deleted.

ESP32 ESP Timer using ESP-IDF Project Terminal 7

You may also like to read:

Leave a Comment