ESP32 Tutorial – Using xTimer


Introduction

ESP32 is a low-power Wi-Fi and Bluetooth dual-mode SoC developed by Espressif Systems, while FreeRTOS is an open source real-time operating system kernel for microcontrollers. When the two are used together, ESP32 can leverage FreeRTOS to achieve multi-task scheduling and improve the real-time performance of applications.

xTimer is a powerful timer tool in FreeRTOS for implementing precise delays and timing operations on ESP32.

xTimer Function

xTimer is a flexible and efficient way to handle software timers. Software timers allow you to set a timer that executes a callback function after a specified amount of time. This is useful for applications that require periodic tasks or delayed operations.

How To Use xTimer

1. Create and initialize timers.
2. Set timer parameters.
3. Start the timer.
4. Stop the timer.

xTimer Functions

1. xTimerCreate: Create a software timer.
2. xTimerStart: Start the timer.
3. xTimerStop: Stop the timer.
4. xTimerChangePeriod: Change the period of the timer.
5. xTimerReset: Reset the timer and restart the timer.
6. xTimerDelete: Delete the timer.
7. xTimerGetPeriod: Get the period of the timer.
8. xTimerGetExpiryTime: Get the next expiration time of the timer.
9. xTimerGetTimerDaemonTaskHandle: Get the handle of the timer daemon task.
10. pvTimerGetTimerID: Get the ID of the timer.
11. vTimerSetTimerID: Set the ID of the timer.
12. xTimerPendFunctionCall: Execute a function that will run in the context of the timer daemon task.

Install VSCode & ESP-IDF VSCode Extension

Make sure you have installed and configured the ESP-IDF development environment. You can also refer to the article the ESP32 Tutorial – ESP-IDF With VSCode.

Create A New ESP32 Project

We can use VSCode's IDF plug-in to create a new ESP32 project. Please refer to ESP32 Tutorial – How To Create An ESP32 Project With VSCode.

xTimer Functions Example

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_log.h"

#define TAG "xtimer_example"

// Counter to track the number of timer callbacks
static int callback_count = 0;
static const int stop_after_count = 4;

// Callback function
void timer_callback(TimerHandle_t xTimer) {
    callback_count++;
    ESP_LOGI(TAG, "Timer expired! Callback count: %d", callback_count);

    // Stop the timer after 4 callbacks
    if (callback_count >= stop_after_count) {
        if (xTimerStop(xTimer, 0) != pdPASS) {
            ESP_LOGE(TAG, "Timer stop failed!");
        } else {
            ESP_LOGI(TAG, "Timer stopped after %d callbacks", stop_after_count);
        }
    }
}

// Function to be executed in the timer daemon task context
void pendingFunction(void *pvParameter1, uint32_t ulParameter2) {
    ESP_LOGI(TAG, "Pending function called with param1: %p, param2: %d", pvParameter1, ulParameter2);
}

void app_main() {
    // Create a software timer
    TimerHandle_t xTimer = xTimerCreate(
        "Timer",                  // Timer name
        pdMS_TO_TICKS(1000),      // Timer period in ticks (milliseconds to ticks)
        pdTRUE,                   // Auto-reload flag
        (void *) 0,               // Timer ID
        timer_callback            // Callback function
    );

    if (xTimer == NULL) {
        ESP_LOGE(TAG, "Timer creation failed!");
        return;
    }

    // Start the timer
    if (xTimerStart(xTimer, 0) != pdPASS) {
        ESP_LOGE(TAG, "Timer start failed!");
    }

    // Change the timer period to 2000 ticks (assuming 1 tick = 1 millisecond)
    if (xTimerChangePeriod(xTimer, pdMS_TO_TICKS(2000), 0) != pdPASS) {
        ESP_LOGE(TAG, "Timer change period failed!");
    }

    // Get the timer period
    TickType_t period = xTimerGetPeriod(xTimer);
    ESP_LOGI(TAG, "Timer period: %d ticks", period);

    // Get the timer expiry time
    TickType_t expiryTime = xTimerGetExpiryTime(xTimer);
    ESP_LOGI(TAG, "Timer expiry time: %d ticks", expiryTime);

    // Set and get the timer ID
    void *newTimerID = (void *)111;
    vTimerSetTimerID(xTimer, newTimerID);
    void *timerID = pvTimerGetTimerID(xTimer);
    ESP_LOGI(TAG, "Timer ID: %p", timerID);

    // Call a function in the timer daemon task context
    if (xTimerPendFunctionCall(pendingFunction, (void *)0xFFF, 111, 0) != pdPASS) {
        ESP_LOGE(TAG, "Pending function call failed!");
    }

    // Get the timer daemon task handle
    TaskHandle_t timerDaemonTaskHandle = xTimerGetTimerDaemonTaskHandle();
    ESP_LOGI(TAG, "Timer daemon task handle: %p", timerDaemonTaskHandle);
}

Compile, Flash & Monitor

Find the "ESP32-IDF: Build, Flash and Monitor" ICON in VSCode to execute and burn.

Results

We can view the printing results on the terminal, as follows...
I (306) xtimer_example: Timer period: 100 ticks
I (316) xtimer_example: Timer expiry time: 1073407732 ticks
I (316) xtimer_example: Timer ID: 0x6f
I (326) xtimer_example: Timer daemon task handle: 0x3ffb6b48
I (326) xtimer_example: Pending function called with param1: 0xfff, param2: 111
I (2326) xtimer_example: Timer expired! Callback count: 1
I (4326) xtimer_example: Timer expired! Callback count: 2
I (6326) xtimer_example: Timer expired! Callback count: 3
I (8326) xtimer_example: Timer expired! Callback count: 4
I (8326) xtimer_example: Timer stopped after 4 callbacks

Conclusion

The accuracy of the ESP32's FreeRTOS timer depends on the system's tick frequency, which is typically 1ms. Make sure you manage memory properly when creating and deleting timers to avoid memory leaks. The callback function should perform fast and non-blocking operations and avoid running for a long time, so as not to affect the scheduling of other tasks in the system.