Введение
Встраиваемые системы (embedded systems) работают в условиях жёстких ограничений:
- ограниченный объём памяти (RAM/ROM);
- низкое быстродействие процессора;
- жёсткие требования к реальному времени;
- прямая работа с аппаратурой (периферией).
В таких условиях паттерны проектирования (design patterns) помогают:
- структурировать код;
- снизить связность компонентов;
- обеспечить переносимость между платформами;
- упростить тестирование и сопровождение.
В статье рассмотрены три ключевых паттерна для embedded‑разработки:
- State Machine (машина состояний) — управление поведением по событиям;
- Publisher‑Subscriber (издатель‑подписчик) — асинхронная коммуникация;
- 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. Принципы проектирования
- Инкапсуляция регистров
- вместо
GPIOA->ODR |= (1<<5)→gpio_set(PIN_LED).
- вместо
- Единые интерфейсы
*



