В этой серии уроков по ESP-IDF мы познакомим вас с API таймеров ESP на примере (system/esp_timer), предоставленном ESP-IDF. Этот API для таймеров с высоким разрешением позволяет создавать несколько таймеров с помощью одного аппаратного таймера. Управление несколькими таймерами легко осуществляется с помощью функций обратного вызова, что снижает сложность при работе с несколькими таймерами. ESP-IDF предоставляет драйвер esp_timer, содержащий API для создания как периодических, так и однократных таймеров с разрешением по времени в микросекунды и 64-битным диапазоном для аппаратных таймеров.

Пример использования esp_timer находится в примерах расширения ESP-IDF в разделе system. На этом примере мы покажем, как создать два разных типа таймеров: одноразовый и периодический, и управлять ими обоими с помощью API ESP Timer. Итак, начнём!
Более кратко работу с таймерами в ESP32 мы рассматривали в статье таймеры и задержки в FreeRTOS на ESP32 с помощью ESP-IDF. Также хороший обзор про данные таймеры можно прочитать в статье использование таймеров и прерываний от них в модуле ESP32, но в ней работа с таймерами рассматривается с помощью Arduino IDE.
Высокоточный таймер ESP32 с модулем ESP-IDF
В этом разделе мы рассмотрим пример скетча, предоставленный ESP-IDF, чтобы научиться настраивать таймеры с помощью API esp_timer.
Создание примера проекта
Откройте VS Code и перейдите в меню View > Command Palette («Вид» > «Палитра команд»). Введите в строке поиска ESP-IDF: New Project и нажмите Enter.
Укажите название проекта и каталог. Мы назвали наш проект «ESP_IDF_ESP_TIMER». Для платы ESP-IDF мы выбрали опцию custom board («пользовательская плата»). В качестве целевой платформы для ESP-IDF мы выбрали модуль ESP32.
Нажмите кнопку ‘Choose Template’ («Выбрать шаблон»), чтобы продолжить.
В разделе Extension («Расширение») выберите опцию ESP-IDF:
На вкладке System («Система») мы щелкнем по пункту «esp_timer». Затем нажмем ‘Create project using template esp_timer’ («Создать проект, используя шаблон esp_timer»).
Вы получите уведомление о создании проекта. Чтобы открыть проект в новом окне, нажмите «Да».
Это открывает наш проект ESP_IDF_ESP_TIMER, который мы создали на вкладке EXPLORER. Внутри папки проекта находится несколько папок. Это одинаково для всех проектов, которые вы создадите с помощью ESP-IDF Explorer. В основной папке находится исходный код, то есть файл main.c будет находиться именно здесь.
Теперь перейдите в папку main > esp_timer_example_main.c и откройте её.
Откроется следующий код. В этом примере кода создаются два таймера: периодический и одноразовый. Периодический таймер будет вызываться каждые 0,5 секунды для вывода сообщения в терминал. Одноразовый таймер будет запускаться и перезапускать периодический таймер с периодом в 1 секунду.
Код таймера для ESP32
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
/* 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); } |
Как работает код?
API для таймера высокого разрешения определены в файле components/esp_timer/include/esp_timer.h. Поэтому мы включим этот драйвер в самом начале вместе с другими необходимыми библиотеками.
|
1 2 3 4 5 6 7 |
#include <stdio.h> #include <string.h> #include <unistd.h> #include "esp_timer.h" #include "esp_log.h" #include "esp_sleep.h" #include "sdkconfig.h" |
В наборе API esp_timer один таймер обозначается типом esp_timer_handle_t. Поскольку в этом примере создаются два таймера, будут использоваться два дескриптора таймеров esp: один для периодического таймера, а другой для однократного.
|
1 2 |
esp_timer_handle_t periodic_timer; esp_timer_handle_t oneshot_timer; |
Функции обратного вызова
Каждый таймер имеет функцию обратного вызова, которая вызывается каждый раз, когда таймер продолжает отсчет. Она вызывается из задачи esp_timer.
|
1 2 |
static void periodic_timer_callback(void* arg); static void oneshot_timer_callback(void* arg); |
Периодический таймер будет вызываться каждые 0,5 секунды (период 500 мс) и будет выводить время с момента загрузки в терминал, используя функцию esp_timer_get_time() , которая возвращает количество микросекунд с момента загрузки. Это будет достигнуто с помощью функции обратного вызова периодического таймера, которая приведена ниже.
|
1 2 3 4 5 |
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); } |
Одноразовый таймер запустится и перезапустит периодический таймер с периодом в 1 секунду, тем самым изменив период периодического таймера. Ниже вы можете увидеть функцию обратного вызова для одноразового таймера. Сначала она выводит время с момента загрузки, когда запускается одноразовый таймер. Затем она останавливает периодический таймер и перезапускает его с другим периодом (1 секунда вместо 0,5 секунды). Наконец, она выводит сообщение о том, что периодический таймер перезапущен с периодом в 1 секунду, а также указывает время с момента загрузки.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
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); } |
Создание таймера
Функция esp_timer_create() используется для создания экземпляра esp_timer. Она принимает два параметра. Первый параметр — это указатель на структуру, содержащую аргументы для создания таймера. Второй параметр — это указатель на тип esp_timer_handle_t, содержащий ранее созданный дескриптор таймера.
Для создания периодического таймера используются следующие параметры.
|
1 |
ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer)); |
Для создания однократного таймера используются следующие параметры.
|
1 |
ESP_ERROR_CHECK(esp_timer_create(&oneshot_timer_args, &oneshot_timer)); |
Перед вызовом функции esp_timer_create() для конкретного таймера мы сначала определяем структуру, содержащую аргументы для создания таймера и дескриптор таймера esp.
Это конфигурация таймера и дескриптор, который будет передан функции esp_timer_create() для периодического таймера. Обратите внимание, что конфигурация таймера включает в себя функцию обратного вызова, которая вызывается по истечении времени таймера, и имя таймера.
|
1 2 3 4 5 6 7 |
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_timer_create() для одноразового таймера.
|
1 2 3 4 5 6 7 |
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_timer_start_periodic(). Этот вызов функции запускает периодический таймер, который будет срабатывать через каждый указанный период времени в микросекундах. Функция принимает два параметра. Первый параметр — это дескриптор таймера, а второй — период времени в микросекундах, через который вызывается функция обратного вызова. Таймер останавливается при вызове функции esp_timer_stop().
|
1 |
ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, 500000)); |
Для запуска одноразового таймера используется функция esp_timer_start_once(). Этот вызов функции запускает одноразовый таймер. Он принимает два параметра. Первый параметр — это дескриптор таймера, а второй — время ожидания таймера в микросекундах, по истечении которого вызывается функция обратного вызова. Этот таймер останавливается, когда вызывается его функция обратного вызова.
|
1 |
ESP_ERROR_CHECK(esp_timer_start_once(oneshot_timer, 5000000)); |
В этот момент в терминале отобразится сообщение о запуске таймеров, а также время с момента загрузки, которое можно получить с помощью функции esp_timer_get_time().
|
1 |
ESP_LOGI(TAG, "Started timers, time since boot: %lld us", esp_timer_get_time()); |
Примечание: при запуске периодического или одноразового таймера убедитесь, что он еще не запущен. Если вы хотите перезапустить работающий таймер, сначала необходимо остановить его с помощью функции esp_timer_stop(), а затем запустить с помощью функций esp_timer_start_periodic() или esp_timer_start_once().
Следующий цикл for выводит информацию о таймерах на терминал каждые 2 секунды. Функция esp_timer_dump() используется для вывода списка таймеров в поток, который в данном случае является стандартным выводом (stdout).
|
1 2 3 4 |
for (int i = 0; i < 5; ++i) { ESP_ERROR_CHECK(esp_timer_dump(stdout)); usleep(2000000); } |
Включение режима легкого сна
Далее плата ESP32 переходит в режим легкого сна на 0,5 секунды. Это достигается вызовом функций esp_sleep_enable_timer_wakeup(500000) и esp_light_sleep_start(). В терминале отображается время с момента загрузки, когда плата переходит в режим легкого сна, и время после пробуждения.
|
1 2 3 4 5 6 7 8 9 10 11 |
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); |
Удаление таймеров
Наконец, мы удалим наши периодические и одноразовые таймеры. Для этого нам сначала нужно остановить таймеры. Если одноразовый таймер уже завершил свою работу, его останавливать не нужно. Следовательно, мы остановим только периодический таймер. Это делается путем вызова функции esp_timer_stop() и передачи в качестве параметра дескриптора времени.
|
1 |
ESP_ERROR_CHECK(esp_timer_stop(periodic_timer)); |
Для удаления таймера вызовем функцию esp_timer_delete( ) и передадим в качестве параметра дескриптор таймера. На этом этапе оба таймера будут удалены, и в терминале отобразится сообщение о том, что таймеры остановлены и удалены.
|
1 2 3 |
ESP_ERROR_CHECK(esp_timer_delete(periodic_timer)); ESP_ERROR_CHECK(esp_timer_delete(oneshot_timer)); ESP_LOGI(TAG, "Stopped and deleted timers"); |
Компиляция скетча
Для прошивки микросхемы введите следующую команду в последовательном терминале ESP-IDF. Не забудьте заменить COM-порт на тот, через который подключена ваша плата.
|
1 |
idf.py -p COMX flash monitor |
После успешной прошивки кода вы сможете просмотреть сообщения в терминале ESP-IDF.
Давайте разберем сообщения по одному, чтобы понять их.
Сначала создаются периодические и одноразовые таймеры.
Далее выводится первоначальный дамп таймера.
Периодический таймер работает с периодом 500000 мкс (500 мс/0,5 с).
Однократный таймер срабатывает через 5 секунд и перезапускает периодический таймер с периодом в 1 секунду.
После выхода из этого режима ESP32 переходит в режим легкого сна, и отслеживание времени остается неизменным.
После остановки периодических таймеров оба таймера удаляются.
5 просмотров














