Перейти к содержанию

Почему vTaskDelay(1) на ESP32 ждёт 10 мс, а не 1 мс?

Обновлено Редакция GAW

В FreeRTOS время измеряется в тиках — периодах системного прерывания планировщика. Функция vTaskDelay принимает именно тики, и сколько это в миллисекундах, зависит от настройки configTICK_RATE_HZ. В ESP-IDF её задаёт опция CONFIG_FREERTOS_HZ, и по умолчанию она равна 100 Гц.

Как пересчитать

Макрос pdMS_TO_TICKS из FreeRTOS переводит миллисекунды в тики целочисленным делением:

N=⌊tмс⋅fтик1000⌋N = \left\lfloor \frac{t_{мс} \cdot f_{тик}}{1000} \right\rfloor
ВызовТик 100 Гц (ESP-IDF по умолчанию)Тик 1000 Гц
vTaskDelay(1)1 тик ≈ 10 мс1 тик ≈ 1 мс
vTaskDelay(pdMS_TO_TICKS(5))0 тиков — задержки нет5 тиков ≈ 5 мс
vTaskDelay(pdMS_TO_TICKS(25))2 тика ≈ 20 мс25 тиков ≈ 25 мс
vTaskDelay(pdMS_TO_TICKS(1000))100 тиков = 1 с1000 тиков = 1 с

Значения в таблице — наш расчёт по формуле макроса. Задержка 0 тиков задачу не блокирует (наш вывод из формулы), поэтому цикл с vTaskDelay(pdMS_TO_TICKS(5)) при тике 100 Гц не отдаёт процессор задачам с меньшим приоритетом, как рассчитывал автор, и может их полностью вытеснить.

Что делать

  • Писать задержки через pdMS_TO_TICKS, а не числом тиков — код не сломается при смене частоты тика.
  • Для периодических задач использовать xTaskDelayUntil: она держит постоянный период, в отличие от vTaskDelay (ответ «Как сделать точный период задачи FreeRTOS»).
  • Для пауз короче тика не использовать планировщик вовсе.

Тик планировщика на Cortex-M обычно даёт таймер SysTick (термин «SysTick»), на ESP32 у каждого ядра своё прерывание тика, а счёт времени ведёт ядро 0. О взаимодействии задач и прерываний — статья «Прерывания и параллельность», о передаче данных между задачами — термин «Очередь RTOS». Модули ESP32 — на странице ESP32.

Похожие вопросы

Можно ли поставить CONFIG_FREERTOS_HZ = 1000?

Да, Kconfig ESP-IDF допускает частоту тика от 1 до 1000 Гц. Тик 1 мс даёт более мелкие задержки, но прерывание тика происходит в 10 раз чаще — это лишняя нагрузка и меньше пользы от энергосбережения. Обычно хватает 100 Гц, а короткие паузы делают другими средствами.

Чем сделать задержку в несколько микросекунд?

Не планировщиком: по описанию vTaskDelay в task.h её разрешение — один период тика. Короткие паузы делают активным ожиданием или аппаратным таймером; активное ожидание занимает ядро, поэтому его используют только для пауз в единицы и десятки микросекунд (наш вывод).

Почему vTaskDelay(pdMS_TO_TICKS(10)) иногда длится меньше 10 мс?

Задержка в тиках отсчитывается от текущего тика, а вызов может прийтись на любой момент внутри него. Поэтому реальная пауза для N тиков лежит примерно между N − 1 и N периодами тика (наш вывод из того, что разрешение задержки — один тик). Нужен гарантированный минимум — добавьте один тик.

Источники

  1. Espressif Systems. ESP-IDF v6.1, components/freertos/Kconfig — config FREERTOS_HZ — configTICK_RATE_HZ, диапазон 1–1000, по умолчанию 100; частота прерывания тика FreeRTOS в герцах
  2. FreeRTOS. FreeRTOS-Kernel V11.3.3, include/projdefs.h и include/task.h — projdefs.h: pdMS_TO_TICKS(x) = x · configTICK_RATE_HZ / 1000 в целых числах (64-битное умножение), pdTICKS_TO_MS — обратный перевод; task.h: vTaskDelay задерживает задачу на число тиков, реальное время зависит от частоты тика, разрешение — один период тика; задержка отсчитывается от момента вызова, для постоянного периода предназначена xTaskDelayUntil
  3. Espressif Systems. ESP-IDF v6.1 Programming Guide, FreeRTOS (IDF) (docs/en/api-reference/system/freertos_idf.rst) — прерывание тика есть на каждом ядре, счёт тиков и разблокировку задач по тайм-ауту ведёт ядро 0; размеры стека задач в IDF FreeRTOS задаются в байтах, а не в словах

Нашли ошибку?