Почему vTaskDelay(1) на ESP32 ждёт 10 мс, а не 1 мс?
Обновлено Редакция GAW
В FreeRTOS время измеряется в тиках — периодах системного прерывания планировщика. Функция vTaskDelay принимает именно тики, и сколько это в миллисекундах, зависит от настройки configTICK_RATE_HZ. В ESP-IDF её задаёт опция CONFIG_FREERTOS_HZ, и по умолчанию она равна 100 Гц.
Как пересчитать
Макрос pdMS_TO_TICKS из FreeRTOS переводит миллисекунды в тики целочисленным делением:
| Вызов | Тик 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 периодами тика (наш вывод из того, что разрешение задержки — один тик). Нужен гарантированный минимум — добавьте один тик.
Источники
- Espressif Systems. ESP-IDF v6.1, components/freertos/Kconfig — config FREERTOS_HZ — configTICK_RATE_HZ, диапазон 1–1000, по умолчанию 100; частота прерывания тика FreeRTOS в герцах
- 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
- Espressif Systems. ESP-IDF v6.1 Programming Guide, FreeRTOS (IDF) (docs/en/api-reference/system/freertos_idf.rst) — прерывание тика есть на каждом ядре, счёт тиков и разблокировку задач по тайм-ауту ведёт ядро 0; размеры стека задач в IDF FreeRTOS задаются в байтах, а не в словах
Нашли ошибку?
Смотрите также
- ESP32: выводы и GPIO34 GPIO: функции, свободные выводы, только входы, флеш-память, strapping, JTAG, токи
- Прерывания и общие данныеISR на AVR и Cortex-M, атомарность, критические секции, кольцевой буфер
- Как сделать точный период задачи во FreeRTOS?xTaskDelayUntil(&last, period): пробуждение по абсолютному времени, без накопления сдвига
- SysTick24-битный таймер ядра Cortex-M для системного тика
- Очередь RTOSПотокобезопасный FIFO для обмена данными между задачами и прерываниями в RTOS