Как сделать точный период задачи во FreeRTOS?
Обновлено Редакция GAW
Самая частая ошибка в периодических задачах FreeRTOS — цикл «поработать и подождать» через vTaskDelay. Пауза отсчитывается от момента вызова, поэтому реальный период равен паузе плюс времени работы, а оно меняется от прохода к проходу.
vTaskDelay и xTaskDelayUntil
| vTaskDelay(N) | xTaskDelayUntil(&last, N) | |
|---|---|---|
| От чего отсчёт | от момента вызова | от прошлого пробуждения |
| Период | N тиков плюс время работы | ровно N тиков |
| Накопление сдвига | да | нет |
| Пропуск срока | не виден | возвращает pdFALSE |
// Run every 20 ms with a fixed period (FreeRTOS task.h, V11.3.3)
static volatile uint32_t missed; // cycles that overran their period
void sensor_task(void *arg)
{
const TickType_t period = pdMS_TO_TICKS(20);
TickType_t last = xTaskGetTickCount(); // initialise once
for (;;) {
if (xTaskDelayUntil(&last, period) == pdFALSE) {
missed++; // previous cycle overran
}
read_sensor();
}
}
Точность
Период остаётся кратным тику: при тике 100 Гц, как в ESP-IDF по умолчанию, 20 мс — это 2 тика, а 25 мс округлятся вниз до 20 (ответ «Почему vTaskDelay(1) на ESP32 ждёт 10 мс»). Момент запуска внутри тика зависит от приоритета задачи и вытеснения, поэтому у отдельного запуска есть дрожание, но среднее время не уплывает — это и есть разница между джиттером и дрейфом. Сам тик на Cortex-M обычно отсчитывает SysTick.
Если работа должна идти точнее тика — например, опрос АЦП с постоянной частотой, — её запускают от аппаратного таймера, а задачу будят уведомлением из прерывания; общие правила такого разделения — в статье «Прерывания и параллельность». Модули, на которых FreeRTOS идёт в составе ESP-IDF, — на странице ESP32.
Похожие вопросы
Что значит, если xTaskDelayUntil вернула pdFALSE?
Что следующее время пробуждения уже прошло: задача работала дольше периода или долго не получала процессор. По описанию в task.h функция тогда не задерживает задачу, чтобы та не накапливала отставание и вернулась к своему расписанию. Это удобный признак пропущенного срока.
Можно ли так получить период 1 мс на ESP32?
Только если тик FreeRTOS равен 1 мс: период задаётся в тиках, а по умолчанию в ESP-IDF тик 10 мс. Для периода 1 мс либо поднимают CONFIG_FREERTOS_HZ до 1000, либо запускают работу от аппаратного таймера.
Почему компилятор не находит xTaskDelayUntil?
По task.h функция доступна, только если в FreeRTOSConfig.h определено INCLUDE_xTaskDelayUntil = 1. В старом коде встречается vTaskDelayUntil — это прежнее имя той же функции без возвращаемого значения, в V11 оно оставлено макросом.
Источники
- FreeRTOS. FreeRTOS-Kernel V11.3.3, include/task.h — vTaskDelay: блокировка на заданное число тиков от момента вызова, поэтому плохо подходит для постоянной частоты — путь по коду, прерывания и вытеснение меняют момент вызова; xTaskDelayUntil(pxPreviousWakeTime, xTimeIncrement): пробуждение в абсолютный момент *pxPreviousWakeTime + xTimeIncrement, переменную инициализируют текущим временем до первого вызова, далее функция обновляет её сама; возвращает pdTRUE, если задача была задержана, и pdFALSE, если следующее время пробуждения уже в прошлом — так периодические задачи не накапливают задержку; нужен INCLUDE_xTaskDelayUntil = 1; vTaskDelayUntil — старый вариант без возвращаемого значения; pdMS_TO_TICKS переводит миллисекунды в тики с разрешением в один тик
- FreeRTOS. FreeRTOS-Kernel V11.3.3, include/projdefs.h — pdMS_TO_TICKS(x) = x · configTICK_RATE_HZ / 1000, целочисленное деление
Нашли ошибку?
Смотрите также
- Почему vTaskDelay(1) на ESP32 ждёт 10 мс, а не 1 мс?Аргумент vTaskDelay — в тиках, а тик ESP-IDF по умолчанию 10 мс
- Прерывания и общие данныеISR на AVR и Cortex-M, атомарность, критические секции, кольцевой буфер
- SysTick24-битный таймер ядра Cortex-M для системного тика
- ДжиттерОтклонение фронтов сигнала от их идеального положения во времени
- ESP32: выводы и GPIO34 GPIO: функции, свободные выводы, только входы, флеш-память, strapping, JTAG, токи