Главная / Без рубрики / Создание многозадачных систем на базе RTOS

Создание многозадачных систем на базе RTOS

1. Введение: зачем нужна многозадачность в встраиваемых системах

Современные микроконтроллерные приложения всё чаще требуют одновременного выполнения нескольких независимых функций:

  • сбор данных с датчиков;
  • обработка и фильтрация сигналов;
  • управление исполнительными устройствами;
  • обмен данными по беспроводным/проводным интерфейсам;
  • отображение информации на экране;
  • ведение журнала и диагностика.

Без RTOS программист вынужден:

  • реализовывать «псевдомногозадачность» через циклический опрос (polling);
  • вручную управлять приоритетами и временем выполнения;
  • бороться с гонками и взаимными блокировками.

RTOS (Real‑Time Operating System) решает эти проблемы, предоставляя:

  • планировщик задач (scheduler) — автоматически переключает контексты;
  • механизмы синхронизации — семафоры, мьютексы, очереди;
  • таймеры — точное выполнение действий по времени;
  • управление памятью — безопасное выделение ресурсов.

2. Базовые понятия многозадачности в RTOS

2.1. Задача (Task)

  • Независимый поток выполнения с собственным стеком и приоритетом.
  • Состояния:
    • Running — исполняется на CPU;
    • Ready — готова к исполнению, ждёт CPU;
    • Blocked — ждёт события (семафор, очередь, таймер);
    • Suspended — временно отключена.

2.2. Приоритет задачи

  • Числовое значение (например, от 0 до 255).
  • Чем выше число — тем выше приоритет.
  • Планировщик всегда выбирает готовую задачу с наивысшим приоритетом.

2.3. Переключение контекстов (Context Switch)

  • Сохранение регистров и стека текущей задачи.
  • Загрузка регистров и стека следующей задачи.
  • Время переключения — десятки микросекунд.

2.4. Типы планировщиков

  • Вытесняющий (Preemptive) — высокоприоритетная задача немедленно прерывает низкоприоритетную.
  • Кооперативный (Cooperative) — задача сама отдаёт управление (редко в современных RTOS).
  • Циклический (Round‑Robin) — задачи одного приоритета исполняются по очереди.

3. Проектирование многозадачной системы: шаги и рекомендации

3.1. Анализ требований

  1. Перечислить все функции системы (например: «считывать датчик каждые 100 мс», «отправлять данные по UART каждые 1 с»).
  2. Определить временные ограничения (дедлайны, периоды).
  3. Выделить критические секции (где недопустимы задержки).
  4. Оценить объём данных (размер очередей, буферов).

3.2. Разбиение на задачи

  • Каждая задача — логически завершённый модуль.
  • Примеры:
    • SensorTask — опрос датчиков;
    • ControlTask — алгоритм управления;
    • CommTask — обмен данными с ПК;
    • DisplayTask — обновление экрана;
    • LoggerTask — запись в энергонезависимую память.

Рекомендации:

  • Не делать слишком много задач (накладные расходы на переключение).
  • Задачи с жёсткими дедлайнами — высокий приоритет.
  • Длительные операции — выносить в низкоприоритетные задачи.

3.3. Назначение приоритетов

  • Метод Rate Monotonic Scheduling (RMS):
    • Чем короче период задачи — тем выше приоритет.
    • Пример: задача с периодом 10 мс > задача с периодом 100 мс.
  • Учёт зависимостей: если задача A ждёт данные от B, то B должна иметь приоритет ≥ A.

3.4. Распределение ресурсов

  • Стек задачи:
    • Минимальный размер — 128–512 байт (зависит от вложенных вызовов).
    • Тестировать на переполнение (в RTOS есть механизмы контроля).
  • Куча (heap):
    • Выделять память статически, если возможно.
    • Динамическое выделение — только для буферов данных.
  • Периферия:
    • Один ресурс (например, UART) — одна задача‑владелец.
    • Доступ через очереди/семафоры.

4. Механизмы синхронизации и коммуникации

4.1. Семафоры (Semaphores)

  • Binary Semaphore — сигнализация «событие произошло».
  • Counting Semaphore — учёт доступных ресурсов (например, буферов).

Пример (FreeRTOS):

SemaphoreHandle_t xSemaphore;


void ProducerTask(void *pvParameters) {
    for(;;) {
        // Производим данные
        xSemaphoreGive(xSemaphore);  // Сигнализируем
        vTaskDelay(100);
    }
}

void ConsumerTask(void *pvParameters) {
    for(;;) {
        if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdPASS) {
            // Обрабатываем данные
        }
    }
}

4.2. Мьютексы (Mutexes)

  • Защита критических секций от одновременного доступа.
  • В отличие от семафора — только владелец может освободить.

Пример:

MutexHandle_t xMutex;

void Task1(void *pvParameters) {
    xMutexTake(xMutex, portMAX_DELAY);
    // Работаем с общим ресурсом
    xMutexGive(xMutex);
}

void Task2(void *pvParameters) {
    xMutexTake(xMutex, portMAX_DELAY);
    // Работаем с тем же ресурсом
    xMutexGive(xMutex);
}

4.3. Очереди (Queues)

  • Передача данных между задачами.
  • Буферизация сообщений.

Пример:

QueueHandle_t xQueue;

typedef struct {
    uint32_t sensor_id;
    float value;
} SensorData_t;

void SensorTask(void *pvParameters) {
    SensorData_t data = {.sensor_id = 1, .value = 25.5};
    xQueueSend(xQueue, &data, portMAX_DELAY);
}

void ProcessingTask(void *pvParameters) {
    SensorData_t received;
    if (xQueueReceive(xQueue, &received, portMAX_DELAY) == pdPASS) {
        // Обрабатываем полученные данные
    }
}

4.4. События (Event Flags)

  • Многобитовая сигнализация (каждому биту — своё событие).
  • Эффективнее, чем множество семафоров.

5. Управление временем: таймеры и задержки

5.1. Системный тик (Tick)

  • Базовый квант времени RTOS (обычно 1–10 мс).
  • Определяет точность планировщика и таймеров.

5.2. Задержки выполнения

  • vTaskDelay() (FreeRTOS) / k_sleep() (Zephyr) — блокировка задачи на N тиков.
  • vTaskDelayUntil() — точная периодичность (учитывает время исполнения).

5.3. Программные таймеры

  • Однократные или периодические вызовы функций.
  • Отдельный поток выполнения (не блокирует вызывающую задачу).

Пример (FreeRTOS):

TimerHandle_t xTimer;

void TimerCallback(TimerHandle_t xTimer) {
    printf("Timer expired!\n");
}

xTimer = xTimerCreate("MyTimer", pdMS_TO_TICKS(1000),
                   pdTRUE, 0, TimerCallback);
xTimerStart(xTimer, 0);

6. Практические аспекты разработки

6.1. Отладка многозадачности

  • Мониторинг задач:
    • список задач, их приоритеты, состояние, использование стека;
    • в FreeRTOS: uxTaskGetSystemState().
  • Трассировка:
    • логирование переключений задач, событий;
    • инструменты типа Tracealyzer.
  • Проверка на взаимные блокировки (deadlocks):
    • анализ графов зависимостей;
    • использование тайм‑аутов в xSemaphoreTake().

6.2. Оптимизация производительности

  • Минимизировать переключение контекстов (избегать частых коротких задач).
  • Использовать статическое выделение памяти (без malloc/free).
  • Для высокочастотных событий — обработчики прерываний (ISR), а не задачи.

6.3. Энергосбережение

  • Задачи, не требующие немедленного исполнения, переводить в Blocked/Suspended.
  • Использовать режимы сна CPU между тиками (в RTOS есть

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *