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

Программный таймер (software timer)

Погасить светодиод через 200 мс, раз в секунду опросить датчик, отменить ожидание ответа по таймауту — заводить под каждое аппаратный таймер расточительно. FreeRTOS предлагает программные таймеры: все на одном системном тике и без кода в прерывании.

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

Программный таймер (software timer) — механизм FreeRTOS, который вызывает заданную функцию (callback) через заданное число тиков после запуска. Время от запуска до вызова называется периодом таймера. Таймер нужно явно создать; после создания он находится в состоянии dormant («спящий»), и активируют его xTimerStart(), xTimerReset(), xTimerChangePeriod() или их FromISR-версии. Отдельный аппаратный таймер под каждый не нужен: все они работают от одного источника времени — системного тика.

Задача-демон и очередь команд

Таймеры — не часть ядра FreeRTOS: их обслуживает отдельная задача таймеров, она же демон (timer service/daemon task), которую планировщик создаёт при старте, если configUSE_TIMERS = 1 (в сборку должен входить timers.c). Функции API не трогают таймеры напрямую, а кладут команды в закрытую очередь команд; демон вынимает их и обрабатывает. Отсюда свойства реализации, которые подчёркивает документация: callback никогда не выполняется в контексте прерывания, таймеры не тратят процессорное время, пока не истекли, не нагружают прерывание тика и не обходят списки при запрещённых прерываниях.

ПараметрЧто задаётВ шаблоне FreeRTOSConfig.h
configUSE_TIMERSвключить таймеры и создать демона1 (по умолчанию в FreeRTOS.h — 0)
configTIMER_TASK_PRIORITYприоритет демона, от 0 до configMAX_PRIORITIES − 1configMAX_PRIORITIES − 1, то есть 4 при 5 приоритетах
configTIMER_QUEUE_LENGTHсколько команд может ждать обработки10
configTIMER_TASK_STACK_DEPTHстек демона в словахconfigMINIMAL_STACK_SIZE = 128

Очередь команд переполняется, если много команд отправлено до старта планировщика, из прерывания или из задачи с приоритетом выше демона. Стек демона должен вмещать самый прожорливый callback. Если configUSE_DAEMON_TASK_STARTUP_HOOK = 1, при первом запуске демон вызывает vApplicationDaemonTaskStartupHook() — удобное место для инициализации, которой нужен работающий планировщик.

Создание и callback

xTimerCreate(pcTimerName, xTimerPeriod, uxAutoReload, pvTimerID, pxCallbackFunction) берёт память из кучи FreeRTOS и возвращает NULL, если её не хватило; xTimerCreateStatic() использует память приложения. Период задаётся в тиках и должен быть больше 0; pdMS_TO_TICKS() переводит миллисекунды, но только при configTICK_RATE_HZ ≤ 1000. uxAutoReload = pdTRUE даёт периодический таймер, pdFALSE — однократный: после срабатывания он возвращается в dormant и ждёт ручного перезапуска. pvTimerID помогает одному callback различать несколько таймеров.

Callback имеет прототип void vCallbackFunction(TimerHandle_t xTimer) и выполняется в контексте демона, поэтому блокироваться ему нельзя: ни vTaskDelay(), ни ожидание очереди или семафора с ненулевым таймаутом. Иначе встанут все таймеры разом. Длинную работу из callback передают другой задаче — например, установкой бита в группе событий.

Где встречается

Периодический опрос датчика проще сделать таймером, чем отдельной задачей с vTaskDelayUntil(), — сравнение подходов в ответе «Периодическая задача во FreeRTOS». Таймером удобно сбрасывать сторожевой таймер только при живом демоне, то есть при работающем планировщике. С бестактовым режимом таймеры связаны обратной зависимостью: пока есть активный программный таймер, ядро не может уснуть на неопределённый срок — ему известен ближайший момент пробуждения.

Частые вопросы

Почему callback программного таймера нельзя блокировать?

Все callback-функции выполняются в контексте одной задачи — демона таймеров. Если callback вызовет vTaskDelay() или будет ждать очередь с ненулевым таймаутом, остановится обработка всех остальных таймеров и команд. Поэтому в callback допустимы только неблокирующие вызовы — с временем ожидания 0; длинную работу передают другой задаче.

Какой приоритет дать задаче таймеров?

Любой от 0 до configMAX_PRIORITIES − 1; шаблон конфигурации ставит максимальный, configMAX_PRIORITIES − 1. Чем выше приоритет демона, тем быстрее обрабатываются команды и истёкшие таймеры; при низком приоритете они ждут, пока демон станет самой приоритетной готовой задачей. Момент срабатывания при этом считается от отправки команды, а не от её обработки, так что задержка демона не сдвигает период, но сдвигает вызов callback.

Чем однократный таймер отличается от автоперезапускаемого?

Однократный (uxAutoReload = pdFALSE) вызывает callback один раз и переходит в состояние dormant; запустить его снова можно только вручную. Автоперезапускаемый (pdTRUE) после каждого вызова перезапускается сам и даёт периодический вызов. Период задаётся в тиках при создании, pdMS_TO_TICKS() переводит миллисекунды, но только при частоте тика не выше 1000 Гц.

Источники

  1. FreeRTOS. FreeRTOS Kernel documentation: Software timers (Kernel features) — Software timers, Timer service/daemon task, Timer daemon configuration, One-shot vs auto-reload, 2026 — разд. Software timers in a nutshell: таймер позволяет выполнить функцию (callback) в заданный момент в будущем; период — время от запуска до вызова; таймер нужно явно создать; разд. Efficiency: реализация FreeRTOS не вызывает callback из контекста прерывания, не тратит процессорное время, пока таймер не истёк, не нагружает прерывание тика и не обходит списки при запрещённых прерываниях; разд. Writing timer callback functions: callback выполняется в контексте задачи таймеров и не должен блокироваться — ни vTaskDelay(), ни vTaskDelayUntil(), ни ненулевое время ожидания очереди или семафора; стр. Timer service/daemon task: таймеры — не часть ядра, их обслуживает задача-демон; API отправляет команды через закрытую очередь команд (timer command queue); стр. Timer daemon configuration: configUSE_TIMERS = 1 — задача создаётся при запуске планировщика, нужен файл timers.c; configTIMER_TASK_PRIORITY — любой от 0 до configMAX_PRIORITIES − 1, моменты срабатывания считаются от отправки команды, а не от её обработки; configTIMER_QUEUE_LENGTH — число необработанных команд; очередь переполняется при вызовах до старта планировщика, из ISR или из задачи с приоритетом выше демона; configTIMER_TASK_STACK_DEPTH — в словах, зависит от стека callback-функций; стр. One-shot vs auto-reload: однократный таймер вызывает callback один раз и не перезапускается сам, автоперезапускаемый перезапускается после каждого вызова
  2. FreeRTOS. FreeRTOS Kernel API reference: xTimerCreate(), 2026 — xTimerCreate( pcTimerName, xTimerPeriod, uxAutoReload, pvTimerID, pxCallbackFunction ); нужны configUSE_TIMERS = 1 и configSUPPORT_DYNAMIC_ALLOCATION = 1 (по умолчанию 1); память под таймер берётся из кучи FreeRTOS, xTimerCreateStatic() — из памяти приложения; таймер создаётся в состоянии dormant, активируют xTimerStart(), xTimerReset(), xTimerChangePeriod() и их FromISR-версии; период в тиках, pdMS_TO_TICKS() переводит миллисекунды и работает только при configTICK_RATE_HZ ≤ 1000; период больше 0; uxAutoReload = pdTRUE — периодический, pdFALSE — однократный, после срабатывания переходит в dormant; pvTimerID различает таймеры с общим callback; прототип callback void vCallbackFunction( TimerHandle_t xTimer ); при нехватке кучи возвращается NULL
  3. FreeRTOS. FreeRTOS-Kernel V11.3.3: examples/template_configuration/FreeRTOSConfig.h и include/FreeRTOS.h (GitHub, MIT) — шаблон: configUSE_TIMERS 1, configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES − 1 ), configTIMER_TASK_STACK_DEPTH configMINIMAL_STACK_SIZE, configTIMER_QUEUE_LENGTH 10, configMAX_PRIORITIES 5, configMINIMAL_STACK_SIZE 128, configTICK_RATE_HZ 100; FreeRTOS.h: configUSE_TIMERS равно 0, если не определено; страница Hook functions: configUSE_DAEMON_TASK_STARTUP_HOOK = 1 вызывает vApplicationDaemonTaskStartupHook() при первом запуске демона
Страница помогла?

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