Главная / Без рубрики / Паттерны проектирования для встраиваемых систем: State Machine, Publisher‑Subscriber, Hardware Abstraction Layer

Паттерны проектирования для встраиваемых систем: State Machine, Publisher‑Subscriber, Hardware Abstraction Layer

Введение

Встраиваемые системы (embedded systems) работают в условиях жёстких ограничений:

  • ограниченный объём памяти (RAM/ROM);
  • низкое быстродействие процессора;
  • жёсткие требования к реальному времени;
  • прямая работа с аппаратурой (периферией).

В таких условиях паттерны проектирования (design patterns) помогают:

  • структурировать код;
  • снизить связность компонентов;
  • обеспечить переносимость между платформами;
  • упростить тестирование и сопровождение.

В статье рассмотрены три ключевых паттерна для embedded‑разработки:

  1. State Machine (машина состояний) — управление поведением по событиям;
  2. Publisher‑Subscriber (издатель‑подписчик) — асинхронная коммуникация;
  3. Hardware Abstraction Layer (HAL) — изоляция от аппаратной платформы.

1. State Machine (машина состояний)

1.1. Суть паттерна

Машина состояний моделирует объект, который:

  • находится в одном из конечного набора состояний (states);
  • переходит между состояниями по событиям (events);
  • выполняет действия (actions) при входе/выходе из состояния или на переходе.

Цели:

  • чётко описать логику с множеством условий;
  • избежать «спагетти‑кода» с вложенными if‑else;
  • гарантировать детерминированное поведение.

1.2. Компоненты

  • Состояния (States) — атомарные режимы работы (например, IDLE, RUNNING, ERROR).
  • События (Events) — внешние или внутренние стимулы (нажатие кнопки, таймер, ошибка).
  • Переходы (Transitions) — правила смены состояния по событию.
  • Действия (Actions) — код, выполняемый:
    • при входе в состояние (entry action);
    • при выходе из состояния (exit action);
    • на переходе (transition action).

1.3. Реализация на C

Вариант 1: Таблица переходов

typedef enum {
    STATE_IDLE,
    STATE_RUNNING,
    STATE_ERROR
} state_t;

typedef enum {
    EVT_BUTTON,
    EVT_TIMER,
    EVT_FAULT
} event_t;

// Таблица: текущее состояние + событие → новое состояние + действие
typedef struct {
    state_t current_state;
    event_t event;
    state_t next_state;
    void (*action)(void);
} transition_t;

transition_t transitions[] = {
    {STATE_IDLE,   EVT_BUTTON, STATE_RUNNING, start_motor},
    {STATE_RUNNING, EVT_FAULT,  STATE_ERROR,   stop_motor},
    {STATE_ERROR,  EVT_BUTTON, STATE_IDLE,    reset_system}
};

void state_machine(event_t evt) {
    for (int i = 0; i < ARRAY_SIZE(transitions); i++) {
        if (transitions[i].current_state == current_state &&
            transitions[i].event == evt) {
            transitions[i].action();
            current_state = transitions[i].next_state;
            break;
        }
    }
}

Вариант 2: Переключатель состояний (switch‑case)

void state_handler(event_t evt) {
    switch (current_state) {
        case STATE_IDLE:
            if (evt == EVT_BUTTON) {
                start_motor();
                current_state = STATE_RUNNING;
            }
            break;
        case STATE_RUNNING:
            if (evt == EVT_FAULT) {
                stop_motor();
                current_state = STATE_ERROR;
            }
            break;
        // ...
    }
}

1.4. Плюсы и минусы

Плюсы:

  • наглядность логики;
  • лёгкость добавления новых состояний/событий;
  • тестируемость (каждое состояние изолировано).

Минусы:

  • расход памяти на таблицу переходов;
  • накладные расходы на поиск перехода (в таблице).

1.5. Примеры применения

  • управление режимом работы прибора (включение/выключение/ошибка);
  • протоколы связи (состояние соединения);
  • пользовательские интерфейсы (меню, диалоговые окна).

2. Publisher‑Subscriber (издатель‑подписчик)

2.1. Суть паттерна

Паттерн разделяет:

  • Издателей (Publishers) — компоненты, генерирующие события;
  • Подписчиков (Subscribers) — компоненты, реагирующие на события.

Ключевая идея: издатели не знают о подписчиках; подписчики не знают об издателях. Связь через шину событий (event bus).

Цели:

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

2.2. Компоненты

  • Шина событий (Event Bus) — центральный реестр подписок.
  • Событие (Event) — структура с типом и данными.
  • Издатель — вызывает publish(event).
  • Подписчик — регистрирует callback‑функцию для типа события.
  • Диспетчер (Dispatcher) — доставляет события подписчикам.

2.3. Реализация на C

// Тип события
typedef enum {
    EVT_TEMPERATURE,
    EVT_BUTTON_PRESS,
    EVT_UART_DATA
} event_type_t;

// Структура события
typedef struct {
    event_type_t type;
    void *data;
    uint16_t size;
} event_t;

// Callback подписчика
typedef void (*event_callback_t)(const event_t *evt);

// Реестр подписок (упрощённо)
#define MAX_SUBSCRIBERS 10
static struct {
    event_type_t type;
    event_callback_t cb;
} subscriptions[MAX_SUBSCRIBERS];
static int sub_count = 0;

// Регистрация подписчика
void subscribe(event_type_t type, event_callback_t cb) {
    subscriptions[sub_count].type = type;
    subscriptions[sub_count].cb = cb;
    sub_count++;
}

// Публикация события
void publish(const event_t *evt) {
    for (int i = 0; i < sub_count; i++) {
        if (subscriptions[i].type == evt->type) {
            subscriptions[i].cb(evt);
        }
    }
}

// Пример подписчика
void temp_handler(const event_t *evt) {
    float temp = *(float*)evt->data;
    if (temp > 80.0f) {
        set_fan_speed(HIGH);
    }
}

// Использование
int main(void) {
    subscribe(EVT_TEMPERATURE, temp_handler);
    
    while (1) {
        float t = read_temperature();
        event_t evt = {EVT_TEMPERATURE, &t, sizeof(t)};
        publish(&evt);
    }
}

2.4. Плюсы и минусы

Плюсы:

  • слабая связность (модули независимы);
  • масштабируемость (легко добавить новых подписчиков);
  • асинхронность (издатель не ждёт ответа).

Минусы:

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

2.5. Примеры применения

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

3. Hardware Abstraction Layer (HAL)

3.1. Суть паттерна

HAL — слой кода, скрывающий детали аппаратной платформы:

  • регистры периферии;
  • векторы прерываний;
  • тактирование и питание;
  • пины ввода‑вывода.

Цели:

  • написать код, переносимый между МК одного семейства (например, STM32F1 → STM32F4);
  • изолировать бизнес‑логику от «железа»;
  • упростить рефакторинг и тестирование (можно заменить HAL на «заглушку»).

3.2. Принципы проектирования

  1. Инкапсуляция регистров
    • вместо GPIOA->ODR |= (1<<5)gpio_set(PIN_LED).
  2. Единые интерфейсы
    *

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

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