RTOS — Операционные системы реального времени
RTOS — Операционные системы реального времени
RTOS (Real-Time Operating System) обеспечивает детерминированное выполнение задач с гарантированным временем отклика. В отличие от Linux, где задержки могут составлять миллисекунды, RTOS гарантирует микросекундную предсказуемость.
Зачем нужна RTOS
Проблема суперцикла (bare-metal)
void loop() {
readSensors(); // 5 мс
calculatePID(); // 1 мс
updateMotors(); // 0.1 мс
sendTelemetry(); // 50 мс (блокирует!)
checkButtons(); // пропущено на 50 мс
}
Проблемы:
- Блокирующие операции останавливают всё
- Невозможно гарантировать периодичность PID
- Сложно масштабировать
Решение RTOS
// Задачи выполняются "параллельно"
xTaskCreate(taskPID, "PID", 1024, NULL, 3, NULL); // Высокий приоритет
xTaskCreate(taskSensors, "Sensors", 2048, NULL, 2, NULL);
xTaskCreate(taskTelemetry,"Telemetry",4096, NULL, 1, NULL); // Низкий приоритет
Теория планирования
Модель периодических задач
Задача $\tau_i$ характеризуется:
- $C_i$ — время выполнения (WCET)
- $T_i$ — период
- $D_i$ — дедлайн (обычно $D_i = T_i$)
Rate Monotonic Scheduling (RMS)
Статический приоритет: чем короче период, тем выше приоритет.
$$U \leq n(2^{1/n} - 1)$$| n | Предел U |
|---|---|
| 1 | 1.000 |
| 2 | 0.828 |
| 3 | 0.780 |
| ∞ | 0.693 |
Пример: 3 задачи с $U = 0.1 + 0.2 + 0.3 = 0.6 < 0.780$ — планируемы.
Earliest Deadline First (EDF)
Динамический приоритет: выполняется задача с ближайшим дедлайном.
$$U \leq 1$$EDF оптимален, но сложнее в реализации.
FreeRTOS
Архитектура
┌─────────────────────────────────────┐
│ Application Tasks │
├─────────────────────────────────────┤
│ Queues │ Semaphores │ Timers │ ... │
├─────────────────────────────────────┤
│ FreeRTOS Kernel │
├─────────────────────────────────────┤
│ Hardware Abstraction │
└─────────────────────────────────────┘
API задач
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// Создание задачи
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, // Функция задачи
const char *pcName, // Имя (для отладки)
uint32_t usStackDepth, // Размер стека (слова)
void *pvParameters, // Параметры
UBaseType_t uxPriority, // Приоритет (0 = idle)
TaskHandle_t *pxCreatedTask // Handle (опционально)
);
// Пример задачи
void vTaskPID(void *pvParameters) {
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xPeriod = pdMS_TO_TICKS(10); // 10 мс = 100 Гц
for (;;) {
float error = setpoint - readEncoder();
float output = computePID(error);
setMotorPWM(output);
// Точная периодичность (не vTaskDelay!)
vTaskDelayUntil(&xLastWakeTime, xPeriod);
}
}
Очереди (Queues)
Потокобезопасная передача данных между задачами:
#include "freertos/queue.h"
// Создание очереди на 10 элементов типа SensorData
QueueHandle_t xSensorQueue = xQueueCreate(10, sizeof(SensorData));
// Отправка (из ISR используйте xQueueSendFromISR)
void vTaskSensor(void *pvParameters) {
SensorData data;
for (;;) {
data.distance = readUltrasonic();
data.timestamp = xTaskGetTickCount();
// Блокировка до 100 мс если очередь полна
xQueueSend(xSensorQueue, &data, pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(50));
}
}
// Приём
void vTaskProcess(void *pvParameters) {
SensorData data;
for (;;) {
if (xQueueReceive(xSensorQueue, &data, portMAX_DELAY)) {
processData(&data);
}
}
}
Семафоры и мьютексы
#include "freertos/semphr.h"
// Бинарный семафор (сигнализация)
SemaphoreHandle_t xBinarySem = xSemaphoreCreateBinary();
// ISR сигнализирует о событии
void IRAM_ATTR encoderISR() {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xBinarySem, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// Задача ждёт события
void vTaskEncoder(void *pvParameters) {
for (;;) {
if (xSemaphoreTake(xBinarySem, portMAX_DELAY)) {
encoderCount++;
}
}
}
// Мьютекс (защита ресурса)
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
void safeSerialPrint(const char *msg) {
if (xSemaphoreTake(xMutex, pdMS_TO_TICKS(100))) {
Serial.println(msg);
xSemaphoreGive(xMutex);
}
}
Программные таймеры
#include "freertos/timers.h"
TimerHandle_t xHeartbeatTimer;
void vHeartbeatCallback(TimerHandle_t xTimer) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}
void setup() {
xHeartbeatTimer = xTimerCreate(
"Heartbeat",
pdMS_TO_TICKS(500), // Период
pdTRUE, // Auto-reload
NULL,
vHeartbeatCallback
);
xTimerStart(xHeartbeatTimer, 0);
}
Zephyr RTOS
Современная RTOS с поддержкой 500+ плат, сетевых стеков, Bluetooth, USB.
Конфигурация (prj.conf)
CONFIG_MULTITHREADING=y
CONFIG_TIMESLICING=y
CONFIG_NUM_PREEMPT_PRIORITIES=16
CONFIG_MAIN_STACK_SIZE=2048
CONFIG_HEAP_MEM_POOL_SIZE=16384
Потоки (Threads)
#include <zephyr/kernel.h>
#define STACK_SIZE 1024
#define PRIORITY 5
K_THREAD_STACK_DEFINE(pid_stack, STACK_SIZE);
struct k_thread pid_thread;
void pid_entry(void *p1, void *p2, void *p3) {
while (1) {
// PID вычисления
k_sleep(K_MSEC(10));
}
}
void main(void) {
k_thread_create(&pid_thread, pid_stack, STACK_SIZE,
pid_entry, NULL, NULL, NULL,
PRIORITY, 0, K_NO_WAIT);
}
Сравнение FreeRTOS vs Zephyr
| Параметр | FreeRTOS | Zephyr |
|---|---|---|
| Размер ядра | 5-10 KB | 20-50 KB |
| Плат | ~40 | 500+ |
| Сетевой стек | Отдельно (FreeRTOS+TCP) | Встроенный |
| Bluetooth | Нет | Да (BLE 5.x) |
| Драйверы | Минимум | Обширные |
| Сертификация | IEC 61508 SIL 4 | Нет |
| Лицензия | MIT | Apache 2.0 |
Инверсия приоритетов
Проблема
Задача H (высокий) ждёт мьютекс
Задача M (средний) выполняется
Задача L (низкий) держит мьютекс
→ H ждёт M, хотя H важнее!
Решение: Priority Inheritance
// FreeRTOS: мьютекс с наследованием приоритета
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
// Автоматически поднимает приоритет L до H
Отладка RTOS
Stack Overflow Detection
// FreeRTOSConfig.h
#define configCHECK_FOR_STACK_OVERFLOW 2
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) {
Serial.printf("Stack overflow in %s!\n", pcTaskName);
while(1);
}
Runtime Stats
char buffer[512];
vTaskGetRunTimeStats(buffer);
Serial.println(buffer);
// Вывод:
// Task Abs Time % Time
// PID 12345 45%
// Sensors 5432 20%
// IDLE 9500 35%
Трассировка (Segger SystemView)
#include "SEGGER_SYSVIEW.h"
SEGGER_SYSVIEW_Conf();
SEGGER_SYSVIEW_Start();
// Визуализация в реальном времени
Паттерны проектирования
Producer-Consumer
QueueHandle_t xDataQueue;
void vProducer(void *pv) {
Data d;
for (;;) {
d = acquireData();
xQueueSend(xDataQueue, &d, portMAX_DELAY);
}
}
void vConsumer(void *pv) {
Data d;
for (;;) {
xQueueReceive(xDataQueue, &d, portMAX_DELAY);
processData(&d);
}
}
Event Groups
#include "freertos/event_groups.h"
#define BIT_SENSOR_READY (1 << 0)
#define BIT_WIFI_CONNECTED (1 << 1)
EventGroupHandle_t xEvents = xEventGroupCreate();
// Ожидание нескольких событий
EventBits_t bits = xEventGroupWaitBits(
xEvents,
BIT_SENSOR_READY | BIT_WIFI_CONNECTED,
pdTRUE, // Clear on exit
pdTRUE, // Wait for ALL bits
portMAX_DELAY
);
Рекомендации
- Размер стека — начните с 2048 слов, уменьшайте по uxTaskGetStackHighWaterMark()
- Приоритеты — не более 5-7 уровней, избегайте равных приоритетов
- ISR — минимум кода, только xSemaphoreGiveFromISR / xQueueSendFromISR
- Мьютексы — только для защиты данных, не для сигнализации
- Watchdog — обязательно в продакшене
