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

Как сделать точный период задачи во 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 оно оставлено макросом.

Источники

  1. FreeRTOS. FreeRTOS-Kernel V11.3.3, include/task.h — vTaskDelay: блокировка на заданное число тиков от момента вызова, поэтому плохо подходит для постоянной частоты — путь по коду, прерывания и вытеснение меняют момент вызова; xTaskDelayUntil(pxPreviousWakeTime, xTimeIncrement): пробуждение в абсолютный момент *pxPreviousWakeTime + xTimeIncrement, переменную инициализируют текущим временем до первого вызова, далее функция обновляет её сама; возвращает pdTRUE, если задача была задержана, и pdFALSE, если следующее время пробуждения уже в прошлом — так периодические задачи не накапливают задержку; нужен INCLUDE_xTaskDelayUntil = 1; vTaskDelayUntil — старый вариант без возвращаемого значения; pdMS_TO_TICKS переводит миллисекунды в тики с разрешением в один тик
  2. FreeRTOS. FreeRTOS-Kernel V11.3.3, include/projdefs.h — pdMS_TO_TICKS(x) = x · configTICK_RATE_HZ / 1000, целочисленное деление

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