Системное время и планировщик задач … ↩
mod. Четверг, 15 октября 2026 22:55 . См. 7
BeeFC · Урок S6 из 33 · Уровень «Старт»
Как отсчитывать время в микросекундах без ошибок при переполнении счётчика, как устроен кооперативный планировщик задач с приоритетами и что происходит, когда задача выполняется дольше, чем ей отведено.
Планировщик — основа BeeFC. Все следующие модули подключаются к нему как задачи, а его поведение проверяется тестами на модельном времени, без платы.
Содержание
1. Цели урока
Продолжительность урока — три часа: один час теории и два часа практики. После урока слушатель:
- получает время в микросекундах от аппаратного таймера и правильно вычисляет интервалы при переполнении;
- объясняет разницу между кооперативной и вытесняющей многозадачностью;
- реализует планировщик задач с приоритетами, статистикой опозданий и замером загрузки процессора;
- проверяет планировщик тестами на модельном времени;
- объясняет, почему длительная задача с низким приоритетом нарушает частоту задачи с высоким приоритетом.
2. Термины и сокращения
| Термин | Определение |
|---|---|
| системное время | число микросекунд от запуска прошивки |
| кооперативная многозадачность | задачи выполняются по очереди, и каждая сама возвращает управление планировщику |
| вытесняющая многозадачность | операционная система может прервать задачу в любой момент и передать управление другой |
| операционная система реального времени (ОСРВ) | операционная система, гарантирующая время реакции на события |
| период задачи | заданный интервал между запусками задачи |
| джиттер | разброс фактических интервалов между запусками относительно периода |
| загрузка процессора | доля времени, которую процессор тратит на выполнение задач |
| модельное время | время, которое в тесте задаёт сам тест, а не аппаратный таймер |
3. Системное время
В STM32F405 есть три источника для отсчёта времени. Для BeeFC выбран таймер TIM5: он 32-битный и отсчитывает микросекунды без участия процессора.
| Источник | Разрядность | Переполнение при отсчёте 1 мкс | Особенность |
|---|---|---|---|
| системный таймер SysTick | 24 бита | 16,8 с | требует обработчика прерывания для продолжения счёта |
| счётчик тактов DWT | 32 бита | 25,6 с при отсчёте в тактах | считает такты ядра, а не микросекунды |
| таймер TIM5 | 32 бита | около 71,6 минуты | работает без прерываний |
Файл hal/stm32f4/time_stm32.c
Интервалы при переполнении
Через 71,6 минуты счётчик переходит через ноль. Интервал между двумя показаниями вычисляется вычитанием беззнаковых 32-битных чисел, и результат остаётся верным при переходе через ноль, потому что вычитание выполняется по модулю \( 2^{32} \).
\( (5 - 4294967291) \bmod 2^{32} = 10 \)
Поэтому в BeeFC время всегда сравнивается через разность: now - start >= period. Сравнение моментов времени напрямую, например now >= deadline, при переходе через ноль даёт ошибку.
4. Многозадачность без операционной системы
Цикл управления должен выполняться строго периодически, а остальные задачи — когда найдётся время. Задачу распределения времени решает либо операционная система реального времени, либо собственный планировщик прошивки.
| Прошивка | Подход |
|---|---|
| BeeFC, Betaflight, INAV | собственный кооперативный планировщик без операционной системы |
| ArduPilot | операционная система ChibiOS с потоками, а основной цикл выполняет кооперативный планировщик AP_Scheduler |
| PX4 | операционная система NuttX и очереди работ, каждая очередь работает в своём потоке |
Кооперативный планировщик прост и полностью предсказуем: задача никогда не прерывается другой задачей, поэтому общие данные задач не нуждаются в защите. Цена простоты — задача должна сама быстро возвращать управление.
↑ К оглавлению5. Устройство планировщика BeeFC
Задача описывается структурой с указателем на функцию, периодом и приоритетом. Статистику заполняет сам планировщик.
Файл core/sched.h
За один вызов sched_run_once планировщик выбирает среди задач, у которых наступил срок, задачу с наибольшим приоритетом и выполняет её. Время следующего запуска отсчитывается от предыдущего срока, а не от фактического момента запуска. Благодаря этому небольшие задержки не накапливаются, и средняя частота задачи остаётся точной.
Файл core/sched.c
Тест на модельном времени
Тест подставляет вместо аппаратного таймера функцию, которая возвращает значение переменной fake_now. Задачи в тесте увеличивают эту переменную на время своего выполнения. Так за доли секунды на компьютере моделируется секунда работы прошивки, включая переход счётчика через ноль.
Файл tests/unit/test_level1.c
Задача ПИД выполняется 1000 раз в секунду по 50 мкс, то есть занимает 5 % времени. Медленная задача выполняется 100 раз в секунду по 200 мкс и занимает 2 %. Общая загрузка процессора составляет 7 %.
↑ К оглавлению6. Опоздание задач
Что произойдёт, если медленная задача будет выполняться 2,5 мс при периоде задачи ПИД 1 мс? Кооперативный планировщик не может прервать задачу, поэтому задача ПИД будет ждать окончания медленной задачи, какой бы высокий приоритет у неё ни был.
Файл tests/unit/test_level1.c

Существует четыре способа устранить опоздания.
- Разделить длительную задачу на короткие шаги и выполнять по одному шагу за запуск.
- Перенести ожидание на аппаратную часть: прямой доступ к памяти и прерывания (урок S7).
- Запускать задачу, только если она успеет завершиться до следующего запуска задачи с высоким приоритетом.
- Перенести цикл управления в обработчик прерывания, а планировщику оставить остальные задачи.
7. Практика
Задание S6.1. Планировщик
Продолжительность — 40 минут, выполняется на компьютере.
Реализовать функции sched_init и sched_run_once. Планировщик должен правильно работать при переходе системного времени через ноль.
Критерий автоматической проверки: тест test_1khz_task_runs_1000_times_per_second пройден: задача ПИД вызвана 1000 ± 1 раз за секунду модельного времени, загрузка равна 7 ± 1 %. Тест test_long_task_makes_fast_task_late пройден.
Задание S6.2. Задачи на плате
Продолжительность — 40 минут.
Создать таблицу из трёх задач: светодиод с частотой 2 Гц, заготовка консоли с частотой 100 Гц и заготовка регулятора с частотой 1 кГц, которая переключает отладочный вывод. Логическим анализатором измерить период и джиттер задачи 1 кГц, отладчиком прочитать значение load_percent. Записать результаты в отчёт docs/s6_report.md.
Критерий автоматической проверки: в модели Renode период переключения светодиода равен 250 ± 1 мс.
Задание S6.3. Бюджет времени
Продолжительность — 40 минут, выполняется на компьютере.
Добавить в структуру task_t поле max_delay_us — наибольшую задержку запуска относительно срока. Доработать планировщик третьим способом: задача с низким приоритетом запускается, только если её наибольшее время выполнения max_exec_us меньше времени до ближайшего срока задачи с более высоким приоритетом.
Критерий автоматической проверки: скрытый тест с задачей ПИД 1 кГц длительностью 50 мкс и задачей 100 Гц длительностью 600 мкс; наибольшая задержка задачи ПИД не превышает 100 мкс, медленная задача выполняется не менее 90 раз в секунду.
8. Как это сделано в больших прошивках
| Прошивка | Версия | Файл в исходном коде |
|---|---|---|
| Betaflight | 2026.6.2 | src/main/scheduler/scheduler.c, src/main/fc/tasks.c, src/test/unit/scheduler_unittest.cc |
| INAV | 9.1.0 | src/main/scheduler/scheduler.c, src/main/fc/fc_tasks.c |
| ArduPilot | Copter-4.7.2 | libraries/AP_Scheduler/AP_Scheduler.cpp, ArduCopter/Copter.cpp |
| PX4 | v1.17.0 | platforms/common/px4_work_queue/WorkQueueManager.cpp, src/drivers/drv_hrt.h |
- В Betaflight таблица задач в файле
tasks.cзадаётся макросомDEFINE_TASKс именем, функцией, периодом и приоритетом. ЗадачиGYRO,FILTERиPIDимеют приоритетTASK_PRIORITY_REALTIME, задача последовательных портов выполняется с частотой 100 Гц. - Планировщик Betaflight в файле
scheduler.cподстраивает свой такт под фактическую частоту прерываний гироскопа. Для каждой задачи он оценивает время выполнения в полеanticipatedExecutionTimeи запускает задачу, только если она успевает до следующего цикла гироскопа. Это третий способ из списка выше. - Если задача всё же опоздала настолько, что пропущен целый цикл гироскопа, Betaflight сдвигает расписание на целое число циклов. Комментарий в коде называет самую частую причину: обмен с конфигуратором по USB, длительность которого заранее неизвестна.
- В ArduPilot каждая строка таблицы задач содержит, кроме частоты, наибольшее допустимое время выполнения. Метод
AP_Scheduler::runзапускает задачу, только если оставшегося времени цикла хватает, и считает задачи, пропустившие два и более периода. - В PX4 задачи распределены по очередям работ в файле
WorkQueueManager.cpp, а время берётся из таймера высокого разрешения, интерфейс которого объявлен вdrv_hrt.h.
9. Проверьте себя
- Почему для системного времени выбран таймер TIM5, а не SysTick?
- Почему интервал вычисляется как
now - start, а не сравнениемnow >= deadline? - Почему следующий срок задачи отсчитывается от предыдущего срока, а не от момента фактического запуска?
- Почему задача с высоким приоритетом опаздывает, если задача с низким приоритетом выполняется дольше её периода?
- Как Betaflight избегает запуска задачи, которая не успеет до следующего цикла гироскопа?
10. Чек-лист самопроверки
11. Источники
- Справочное руководство RM0090, раздел 18, таймеры общего назначения TIM2–TIM5.
- Планировщик Betaflight.
- Планировщик ArduPilot.
- Руководство разработчика PX4: архитектура.
12. Что дальше
- Следующий урок: S7. Последовательный порт, прямой доступ к памяти и консоль.
- Полный перечень уроков приведён в статье «Курс BeeFC: содержание уровня „Старт“».
Проверено: 13.10.2026
↑ К оглавлению- мод. : Четверг, 15 октября 2026