Хабрахабр

[Из песочницы] Проектирование архитектуры embedded-приложения

Хотелось бы поговорить на тему архитектуры embedded приложений. Добрый день! В этой статье, я бы хотел описать один из возможных вариантов того, как можно проектировать такие приложения.
Вопрос этот дискуссионный! К сожалению, книг по этой теме очень мало, а в связи с тем, что, в последнее время, интерес к embedded и IoT растет, хочется уделить внимание этому вопросу. STM32) на языке C / Asm.
Проекты для систем на базе МК условно можно разделить на не требующие и требующие многозадачности. Поэтому предлагают поделиться своим виденьем в комментариях!
Для начала определимся с областью: в рамках данной статьи, под embedded разработкой будем понимать разработку ПО под микроконтроллеры (далее МК, напр. Например, простой проект, в рамках которого необходимо считывать данные с датчика и показывать их на экране, не требует многозадачности, здесь достаточно реализовать последовательное выполнение перечисленных операций. Что касается решений первого типа, они, как правило, не очень сложные (со структурной точки зрения).

Существует два способа решения вопроса многозадачности: реализовывать ее самому, либо воспользоваться какой-то операционной системой (далее ОС). Если же приложение более сложное: в рамках которого необходимо считывать данные как с цифровых датчиков, так и с аналоговых, сохранять полученные значения в память (например, на sd-карту), обслуживать пользовательский интерфейс (дисплей + клавиатура), предоставлять доступ к данным через цифровой интерфейс (например, RS-485 / Modbus или Ethernet / TCP/IP) и максимально быстро реагировать на определенные события в системе (нажатие аварийных кнопок и т.п.), то в этом случае будет тяжело обойтись без многозадачности. На сегодняшний день, одной из самых популярных ОС реального времени для встраиваемых систем является FreeRTOS.

Я допускаю, что можно предложить еще более сложный вариант, который предполагает решение вопросов обработки звука, криптографию и т.п., но остановимся на варианте, который был описан чуть выше. Попробуем представить, как должна выглядеть архитектура “сложного” embedded приложения, выполняющего достаточно большое количество разнородных операций.

Поставим задачу более четко, пусть в рамках нашего приложения необходимо:

  • Считывать данные с датчиков на шине RS-485/Modbus.
  • Считывать данные с датчиков на шине I2C.
  • Считывать данные с дискретных входов.
  • Управлять релейным выходом.
  • Обслуживать пользовательский интерфейс (дисплей + клавиатура).
  • Предоставлять доступ к данным по шине RS-485/Modbus.
  • Сохранять данные на внешний носитель.

Т.к. нам необходимо реализовать достаточно большое количество различных подзадач, в качестве базы будем использовать операционную систему реального времени (например, уже указанный выше FreeRTOS). Потоки в ОС иногда будем называть задачами — по аналогии с FreeRTOS. Сразу хочу предупредить: исходного кода в статье не будет, интересен именно архитектурный аспект данного вопроса.

Например: данные с датчиков необходимо получить, отобразить на экране, записать на носитель и предоставить внешним системам для считывания. Если проанализировать задачу, то можно увидеть, что разные компоненты системы используют одни и те же данные. Это наводит на мысль, что нужна какая-то база данных реального времени (RTDB) для хранения и для предоставления самых актуальных данных различным подсистемам.

Нет смысла обновлять данные на дисплее с частотой 1 раз в 100 мс, т.к. Задачи, выполняющиеся в системе (считывание данных, запись, отображение и т.п.), могут иметь различные требования к частоте их вызова. Еще один важный момент связан с решением задачи доступа к одним и тем же данным на чтение и запись. для человека это не критично, а вот считывать данные с датчиков (особенно, если необходимо выдавать по ним управляющие воздействия) нужно часто (хотя в зависимости от ТЗ может и нет). Здесь нам помогут механизмы синхронизации, которые предоставляет операционная система. Например: поток, опрашивающий датчики записывает полученные значения в RTDB, а в этот момент поток, отвечающий за обновление информации на дисплее, их считывает.

Начнем проектировать архитектуру нашего приложения!

База данных реального времени

Для доступа к “RTDB” будем использовать API, который позволит записывать и считывать данные из базы. В качестве такой базы может выступать обычная структура, содержащая необходимый набор полей или массив. Синхронизацию доступа к данным внутри функций API можно построить на мьютексах, предоставляемых ОС (либо использовать какой-то другой механизм).

Работа с датчиками на шинах

Работа с датчиками предполагает следующее:

  • считывание данных;
  • обработка данных (если это необходимо), которая включает:
    • проверку на достоверность;
    • масштабирование;
    • фильтрацию;
    • проверку на допустимые значения;

  • запись полученных данных в RTDB.

Всю эту работу можно сделать в рамках одной задачи.

К такому драйверу желательно сделать свой интерфейс и работать через него. “Port” — реальный порт МК;
“Protocol driver” — драйвер протокола (например, Modbus). Некоторые разработчики предлагают это делать на уровне порта, чтобы быть уверенным в том, что никто другой в этот порт записывать ничего не будет, пока мы через него передаем свои Modbus пакеты.
“Sensor reader” — задача (task), которая опрашивает датчики, приводит в порядок полученную информацию и записывает ее в ”RTDB”. В рамках такого интерфейса можно реализовать управление доступом к ресурсу через мьютексы, так как это было сделано для “RTDB”.

Если у задач приоритет одинаковый, то запустится та, у которой дольше время ожидания. “RTDB” — база данных реального времени, описанная выше, в соответствующем разделе.
Надпись “Pr: 1” над задачей означает приоритет, суть в том, что у каждой задачи может быть приоритет, если у двух задач, ожидающих процессорное время, разный приоритет, ресурс получит та, у которой приоритет выше.

Работа с дискретными входами

В общем случае работу с дискретными входами можно организовать точно также как и с цифровыми датчиками. Но может возникнуть необходимость в быстром реагировании на изменение состояния входов. Например, по нажатию кнопки как можно быстрее замыкать релейный выход. В таком случае, лучше применить следующий подход: для обработки релейного выхода мы создаем специальную отдельную задачу с более высоким приоритетом, чем у остальных. Внутри этой задачи находится семафор, который она пытается захватить. На срабатывание конкретного дискретного входа заводится прерывание, в котором сбрасывается упомянутый выше семафор. Т.к. приоритет прерывания максимальный, то связанная с ним функция выполнится почти мгновенно, в нашем случае, она сбросит семафор, и, после этого, следующей задачей в очереди на выполнение будет как раз та, в рамках которой осуществляется управление реле (т.к. у нее приоритет выше, чем у остальных задач и блокировка по ожиданию семафора снята).
Вот так может выглядеть схема данной подсистемы.

Эта задача может быть как самостоятельной, так и вызываться по таймеру. Помимо быстрого срабатывания на изменение состояния конкретного входа, дополнительно можно поставить задачу “DI reader” для считывания состояния дискретных входов.

Работа “Interrupt handler’а” и “Relay controller’а” в виде диаграмм представлена ниже.

Запись данных на внешний носитель

Запись данных на внешний носитель идеологически очень похожа на чтение данных с цифровых датчиков, только движение данных осуществляется в обратную сторону.

Опять-таки, не забываем в функции интерфейса помещать мьютекс-обертки (или какие-либо другие инструменты для организации доступа к ресурсу)! Мы читаем из “RTDB” и записываем через “Store driver” во внешний носитель — это может быть SD карта, USB-флешка или что-нибудь ещё.

Предоставление доступа к данным реального времени

Важным является момент предоставления данных из “RTDB” для внешних систем. Это могут быть практически любые интерфейсы и протоколы. В отличии от ряда рассмотренных подсистем, ключевым отличием этой является то, что некоторые из протоколов, широко применяемых в системах автоматизации, предъявляют особые требования ко времени ответа на запрос, если ответ не приходит в течении определенного времени, то считается, что с данным устройством нет связи, даже если он (ответ) придет через некоторое время. А т.к. доступ к “RTDB” в нашем примере может быть временно заблокирован (по мьютексу) необходимо предусмотреть защиту внешнего master-устройства (master — это устройство, которое пытается прочитать данные из нашего) от такой блокировки. Также стоит предусмотреть защиту самого устройства от того, что master будет опрашивать его с большой частотой, тем самым тормозя работу системы постоянным чтением из ”RTDB”. Один из вариантов решения — это использовать промежуточный буфер.

В данном случае возникает проблема блокировки на уровне протокольного кэша, для ее решения можно завести ещё один кэш, в котором “Protocol handler” будет хранить данные на случай, если не смог прочитать из заблокированного “Protocol cache”, дополнительно можно:
— сделать для “Protocol handler” более высокий приоритет;
— увеличить период чтения из “RTDB” для “Data updater” (что так себе решение). “Data updater” читает данные из “RTDB” с заданной периодичностью и складывает, то, что прочитал в “Protocol cache”, из которого “Protocol handler” будет данные забирать.

Работа с пользовательским интерфейсом

Работа с пользовательским интерфейсом предполагает обновление данных на экране и работу с клавиатурой. Архитектура этой подсистемы может выглядеть так.

UI worker занимается тем, что считывает нажатия клавиш, забирает данные из “RTDB” и обновляет дисплей, который видит пользователь.

Общая структура системы

Теперь взглянем на то, что получилось в итоге.

Часть задач по перебросу данных можно решить с помощью очередей, благо они, как правило, поддерживаются операционными системами реального времени (во FreeRTOS точно).
На этом все, надеюсь было интересно. Для того чтобы балансировать нагрузку можно ставить дополнительные кэши, так, как мы это сделали в подсистеме, отвечающей за предоставление доступа к данным внешним системам.

S.
В качестве литературы я бы посоветовал “Making Embedded Systems: Design Patterns for Great Software” Elecia White и статьи Андрея Курница “FreeRTOS — операционная система для микроконтроллеров” P.

Показать больше

Похожие публикации

Добавить комментарий

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

Кнопка «Наверх»