Аннотация
В статье рассматривается библиотека на C++, которая предназначена для реализации технологии параллельного автоматного программирования (АП), отвечающей концепции среды ВКПа(подробнее о ней см. [1]). Для полного понимания материала рекомендуется ознакомиться с основами теории АП, представленной в статьях [2, 3, 4], Взаимосвязь машины Тьюринга с конечными автоматами (КА) подробно рассмотрена в [5]. Вопросы применения корутин в контексте автоматного программирования анализируются в статьях [6-9]. Но в минимальном варианте достаточно даже общего представления о модели конечного автомата и принципах объектного программирования.

Цели работы: 

1. Ознакомление разработчиков с универсальной технологией проектирования программного обеспечения. 

2. Реализация технологии АП в виде библиотеки на C++ для микроконтроллеров ESP32, что позволяет применять передовые методы разработки, характерные для крупных платформ, в ресурсоограниченных средах. 

Ключевые преимущества: 

Объединение модульного, объектно-ориентированного и параллельного программирования в единую концепцию на основе строгой математической теории конечных автоматов кардинально меняет подход к проектированию, отладке и документированию ПО. Это устраняет зависимость от интуитивных и зачастую кустарных методик, характерных для традиционного программирования. 

Без параллелизма не обойтись, но и с ним жизнь не предполагается безмятежной. Но тут, «как чертик из табакерки», выскакивают пышущие оптимизмом корутины. Но связь между верой в лучшее и знанием, как это произойдет, часто довольно путанная или, как в случае корутин,  буквально никакая. Тем не менее, в них есть нечто, что нам точно пригодится.

Корутины, как минимум, имитируют параллельную работу. Для этого нужно вручную расставить точки прерывания процессов. Достигнув такой точки, процесс приостанавливает работу и передает управление соседу. Затем ему остается надеяться, что когда-то он продолжит работу. Если все происходит достаточно быстро, то создается впечатление параллельной работы. Так организована работа в асинхронной программе.

Можно ли сделать корутинную модель программирования удобнее, проще и, главное, истинно параллельной?  Это может быть ситуация, когда алгоритм содержит обязательные компоненты, которые можно считать точками прерывания. Как минимум, одна такая алгоритмическая модель существует.  

Но это не широко известная модель блок-схем (БС). С другой стороны, можно представить множество БС, где  каждая представляет корутину, а прерывания выполняются после каждой команды. Реализовать подобное не сложно, но будут большие потери, связанные с переключением процессов. Оптимизация заключается в уменьшении числа прерываний. Так поступают, настраивая режим многопоточности. Но поскольку при этом прерывания совершаются хаотично и не тогда, когда это нужно, то возникает клубок проблем.

Расставляя точки прерывания, программист в чем-то микширует определенные проблемы. Может, в этом и есть один из смыслов корутин? Но проблем в любом случае не становится меньше даже с ними. А поскольку процесс разрешения проблем параллелизма интуитивен, то это не способствует созданию качественного и надежного программного кода.

Конечно-автоматные корутины и процессы

Однако, как мы уже сказали, есть модель, которая перекрывает возможности корутин и потоков. Это сетевая конечно-автоматная модель. Сама модель конечного автомата (КА) программистам известна давно. На ней основан, например, метод введения переменной состояния, предложенный Ашкрофтом и Манной еще в 70-х годах прошлого века[12]. Заметим, что он входит в число наиболее эффективных методов проектирования программ.

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

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

Например, на любом языке программирования довольно просто запрограммировать фразу типа: «если случилось что-то, то сделать то-то». Выражению таких мыслей служит оператор IF-ELSE. Подобное мышление поощряют и другие управляющие операторы типа FOR, WHILE, SWITH и т.п. Но в таком разнообразии есть свой минус. Особенно, если вспомнить, что в категорию управляющих операторов попадает злополучный оператор GO TO.

Но, чем сложнее алгоритм, тем выигрышнее его автоматное представление. Современный вектор движения, когда программирование усложняется, а параллелизм все популярнее, не в пользу блок-схем, а потому в их «королевстве» все более и более тревожно. В этом убеждает уже упомянутый подход Ашкрофта-Манны. Параллелизм еще более изменяет отношение к модели блок-схем.

Но одна из причин засилья блок-схем в том, что фактически не рассматриваются конкурентные  алгоритмические модели. И, заполняя этот пробел, мы далее этим и займемся на примере рассмотрения конечно-автоматной модели параллельных вычислений. Точнее, уже сразу на примере ее реализации и теперь уже на микроконтроллерах. Подробнее же с ее реализацией на ПК можно познакомиться по упомянутым в начале статьи ссылкам.

Сопрограммы

Термин «корутины» ввел в оборот Мелвин Конвей в 1958 г. в статье "A Multiprocessor System Design". Первая реализация появилась на языке COBOL спустя пять лет – в 1963 г. В целом же активная жизнь корутин связана с 1960-1970-ми годами и языками Simula и Modula-2.  Таким о бразом, процесс их использования имеет давнюю историю. Сейчас мы присутствуем при их в своем роде ренессансе.

Итак, краткое введение в тему. Что же собой представляют корутины, которые советские инженеры называли сопрограммами? На рис. 1. приведена картинка из книжки 80-х годов - Зелковиц М., Шоу А., Геннон Дж. «Принципы разработки программного обеспечения» [14]. Она более чем наглядно поясняет работу сопрограмм.

Рис.1. Сопрограммы
Рис.1. Сопрограммы
Рис.1. Сопрограммы

А если мы откроем «Толковый словарь по вычислительным системам» (под ред. Иллингоута и др.), переведенный на русский язык в 90-х годах, то узнаем, что «сопрограммы, как правило, не встречаются в высокоуровневых языках» и «представляют особый интерес, как средство моделирования параллельной работы в последовательных машинах» [15]. Это отражение ситуации 90-х годов. Если бы авторы словаря только могли предположить, что ожидает высокоуровневые языки в будущем? Но с моделированием они явно угадали.

Сопрограммы привлекают детским примитивизмом, но напрягают ручной расстановкой точек прерывания. А можно ли это как-то исправить? Ведь, сама идея сопрограмм удачна для реализации параллелизма. Пусть даже в режиме имитации. А когда-то, ведь, и выхода другого не было. Когда о многоядерности еще не знали, а потоки только-только зарождались! Т.е. крутись, как хочешь.  Вот и докрутились! До корутин.

Сторонний взгляд на модель конечного автомата подсказывает, что его состояния могут быть точками прерывания. А тут недалеко уже до идеи автоматных потоков. Напрашивается программно-аппаратное ядро, которое интерпретирует или даже аппаратно реализует автоматную форму программного потока, перехватывая на себя управление при смене состояний.

В идеале язык описания автоматов должен соответствовать формальному определению модели. Он должен быть эффективно реализуемым и иметь иерархическую (вложенную) модель для создания аналога программных функций. Важно, что в теории КА с подобной иерархией есть проблемы, но в нашем случае они решены (см., например, [2]). Отдельного рассмотрения заслуживает также проблема инерционности программных процессов, решенная в рамках автоматной модели, но никак не представленная и более того игнорируемая (!) современной концепцией программирования.

Кандидат, обладающий требуемыми качествами,   на момент создания сопрограмм имелся. Это таблицы решений (TP). В книге Э.Хамби «Программирование таблиц решений» они описаны весьма подробно [13]. Изучив их, можно сделать заключение, что таблицы решений – это автоматы, но без состояний. Или, другими словами, любая ТР – это автомат с одним состоянием, по которое просто забыли.

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

Внедряя автоматы, удобно изменить представление о структуре программы, разделив ее монолитную конструкцию на три составные части – память, управление и операторы. А операторы разделить на предикаты и действия, где первые – это функции, предназначенные делать любой анализ и возвращающие булевское значение. Вторые – выпоняют конкретную работу, не возвращая значений. Кстати, так или примерно так поступают при реализации ТР.

Довести идею корутин на базе автоматов до рабочего состояние поможет объектно-ориентированное программирование (ООП). В этом случае каждый поток представляется [автоматным] объектом, у которого предикаты и действия – суть его методы, а свойства объекта – память автоматной программы.

Остается только внедрить в тело такого объекта таблицу переходов (ТП) автомата, что совсем уж дело техники. За это будет отвечать базовый автоматный объект (ниже это LFsaAppl), от которого будут порождены уже прикладные объекты. Такая таблица будет заданием, которое управление должно реализовать, если это делается аппаратно, а если же программно,  – интерпретировать. Речь здесь идет об упомянутом выше  ядре.

Есть от автоматного программирования польза и объектам: у них появляется поведение. Подобные объекты называют акторами. Только теперь в основе их поведения лежит строгая математическая модель – конечный автомат. Такие объекты мы будем назвать программными автоматами, автоматными процессами  или, когда это понятно из контекста,  просто автоматами. Понимая при этом, что такой объект - это программа, представленная активным программным объектом, который имеет управление в форме КА.

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

Процесс внедрения какой-либо концепции в иную для нее область представляет, порой, долгую, а для кого-то даже мучительную процедуру.  Особенно, когда нужно не просто переучиться, а изменить свое мышление. Подобная «ломка» происходит, например, при переходе от обычного программирования к объектному. И пусть это не самый простой и приятный процесс, но он стоит того. Сами же детали подобной процедуры мы уточним по ходу освоения технологии автоматного программирования (АП) на конкретных примерах.

Переходим к практической стороне реализации представленных выше идей.

Автоматные корутины на микроконтроллере

Чем привлекает микроконтроллер? Своей доступностью, а в последнее время и возможностями. С появлением серии ESP32 у микроконтроллеров появился полноценный С++ (даже с библиотекой STL). Стало легко заимствовать созданный для «больших» ПК код. На переносе ядра среды  ВКПа все это успешно было проверено.

В результате была создана библиотека для микроконтроллера ESP32. Она позволяет без проблем переносить автоматы, созданные ранее на С++ для «больших ПК». Более того, можно саму разработку и отладку вести на ПК в тех же средах типа Qt Creator и/или Microsoft Visual Studio, где уже есть реализация ВКПа (подробнее см. [1]). Это многократно эффективнее, чем программирование в среде для микроконтроллера (в том же редакторе VS Code). При этом финишную доводку кода следует все же доверять микроконтроллеру.

Итак, предположим, есть датчики, регистрирующие три уровня жидкости, подключенные к входам (пинам) микроконтроллера с номерами 18, 19, 21. Процедуру чтения входов и отображения их значений отражает листинг 1. На листинге 2 показана установка режима входов микроконтроллера – в функции setup() и организация периодического опрос датчика – в функции loop(). Функция setup() выполняется только один раз при запуске микроконтроллера, а потом постоянно «крутится» функция loop(),которая в нашем случае содержит периодический опрос датчиков (один раз в секунду).Это пример типичного проектирования программ для микроконтроллера.

Листинг 1. Опрос и отображения состояния входов датчиков уровня жидкости
#include <Arduino.h>
// Номера входных пинов датчика
int gpioLevel1{18}; int gpioLevel2{19}; int gpioLevel3{21};
// текущее состояние входов
bool level1{0x0}; bool level2{0x0}; bool level3{0x0}; 
float fLevel{0}; float fSavLevel{-1};
// Отображение состояния датчика уровня
void LevelView()
{
  level1 = digitalRead(gpioLevel1);
  level2 = digitalRead(gpioLevel2);
  level3 = digitalRead(gpioLevel3);
  if (!level1&&level2&&level3) fLevel=30;
  else if (level1&&!level2) fLevel=60;
  else if (!level3) fLevel=90;
  else if (level1&&level2&&level3) fLevel=0;
  else fLevel=-1;
  if (fSavLevel!=fLevel) {
    Serial.printf("Level has changed its state: %.f\n", fLevel);
    fSavLevel = fLevel;
  }
}
Листинг 2. Установка режимов входов и цикл опрос датчиков уровня жидкости
#include <Arduino.h>

void setup() {
  // Инициализация последовательного порта
  Serial.begin(115200); 
// конфигурирование пинов датчика уровня
  pinMode(18, INPUT); pinMode(19, INPUT); pinMode(21, INPUT);
}	

unsigned long lastUpdateTimeLevel = 0;
const long updateIntervalLevel = 1000;
void loop() {
  if (millis() - lastUpdateTimeLevel >= updateIntervalLevel) {
    lastUpdateTimeLevel = millis();
    // читаем входы датчика уровня  
    void LevelView();
    LevelView();
  }
}

Поскольку мы используем редактор VSCode и платформу PlatformIO, то нужен еще файл инициализации проекта под стандартным именем platformio.ini, который определяет 1) текущую платформу, 2) тип платы, 3) параметры порта для подключения микроконтроллера 4) подключаемые библиотеки и другие параметры проекта. Его вид приведен на листинге 3.

Листинг 3. Файл инициализации проекта platform.ini
 [platformio]
default_envs = esp32dev

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200	

Вот фактически и весь проект, который далее мы будем считать исходным.

Преобразуем созданный классический проект к автоматному виду. Для этого нам необходимо: 1) подключить библиотеку, интерпретирующую автоматы, 2) создать и подключить к ядру автомат, реализующий работу с датчиками.  Подключение библиотеки к проекту демонстрирует листинг 4, где курсивом выделено дополнение к исходному варианту (ср. с листингом 3).

Листинг 4. Подключение библиотеки к проекту
 [platformio]
default_envs = esp32dev

 [env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps = https://github.com/lvs628/VCPa
monitor_speed = 115200

Рассмотрим превращение обычной функции (см. выше LevelView) в программный автомат. Граф автомата для работы с датчиком уровня приведен на рис. 2. Создан он с помощью Microsoft Visio и служит примером документирования автоматов. И если в исходном проекте мы исходно создавали функцию обслуживания датчиков, то в рамках АП мы изначально проектируем процесс в форме автомата.

Программный автомат – это процесс с заданным поведением. В проекте он анализирует величину заданной задержки, исполняет функции опроса входов микроконтроллера и создает, если задано, задержку между опросами. И все это, включая задержку, которая в этом случае тоже автомат,  но только вложенный, реализуется автоматным ядром.

Рис.2. Автоматная модель управления датчиком уровня
Рис.2. Автоматная модель управления датчиком уровня
Рис.2. Автоматная модель управления датчиком уровня

Коды заголовка автоматного класса и его реализация приведены на листингах 5 и 6. Здесь курсивом выделено то, что отличает новую программу от ее исходного варианта, а дополнительно подчеркиванием то, что относится к специфике автоматной библиотеки. Данный объект инкапсулирует свойства, методы и поведение автоматного процесса. В этом - поведении и есть качественное отличие объектного автоматного подхода  от классического.

В программировании микроконтроллеров обычно избегают применения ООП. Возможно, потому, что программированием занимаются те, кто лучше разбирается в железе и им ближе простой Си? Другой причиной может быть желание ужать код.  И, конечно, отсутствие поведения у классических объектов не способствует их популярности у тех, кто много внимания уделяет «умным» алгоритмам, т.е. увлечен IoT. С автоматной библиотекой, как минимум, поведение у обычных объектов есть точно.

Листинг 5. Файл заголовка автомата датчика уровня
#ifndef __SENSORLEVEL_H
#define __SENSORLEVEL_H

#include <Arduino.h>
#include "lfsaappl.h"
class TAppProcesses;
class SensorLevel:public LFsaAppl 
{
public:
    void LevelView();
    SensorLevel(string strNam = "SENSORLEVEL");
    ~SensorLevel() {};
    // Номера выводов для цифровых входов сенсора
    const int gpioLevel1{18};
    const int gpioLevel2{19};
    const int gpioLevel3{21};
    int nDelay{0};
protected:    
    int x1(); int x2();
    void y1(); void y2(); void y3(); void y4();
    bool level1{false};
    bool level2{false};
    bool level3{false};
    bool bIfViewError{false};
    float fSavLevel{-1};
};
#endif // __SENSORLEVEL_H
Листинг 6. Файл реализации процесса контроля уровней жидкости
#include <Arduino.h>
#include "stdafx.h"
#include "SensorLevel.h"
LArc TBL_SENSORLEVEL[] = {
  LArc("ss",    "ss","^x1^x2",  "y1"), 
  LArc("ss",    "s1","x1",      "y1"), 
  LArc("ss",    "er","x2",      "y3"), 
  LArc("s1",    "ss","--",      "y2"),
  LArc("er",    "er","--",      "y4"),
  LArc()
};
SensorLevel::SensorLevel(string strNam):
  LFsaAppl(TBL_SENSORLEVEL, strNam)
{
// конфигурирование пинов датчика уровня
  pinMode(gpioLevel1, INPUT); pinMode(gpioLevel2, INPUT); 
  pinMode(gpioLevel3, INPUT); bIfViewError = false;
}
// ПРЕДИКАТЫ
int SensorLevel::x1() { return nDelay > 0; }
int SensorLevel::x2() { return nDelay < 0; }
// ДЕЙСТВИЯ
void SensorLevel::y1() { LevelView(); }
// создание задержки
void SensorLevel::y2() { FCreateDelay(nDelay); }
// ошибка в задании задержки
void SensorLevel::y3() { Serial.printf("%s(%s):error nDelay=%d\n", FGetNameVarFSA().c_str(), FGetState().c_str(), nDelay); }
void SensorLevel::y4() { 
  if (!bIfViewError) { Serial.printf("%s(%s)\n", FGetNameVarFSA().c_str(), FGetState().c_str()); bIfViewError = true; }
}
// Отображение состояния датчиков уровня жидкости
void SensorLevel::LevelView()
{
  level1 = digitalRead(gpioLevel1);
  level2 = digitalRead(gpioLevel2);
  level3 = digitalRead(gpioLevel3);
  if (!level1&&level2&&level3) fLevel=30;
  else if (level1&&!level2) fLevel=60;
  else if (!level3) fLevel=90;
  else if (level1&&level2&&level3) fLevel=0;
  else fLevel=-1;
  if (fSavLevel!=fLevel) {
    Serial.printf("%s:Level has changed its state: %.f\n", FGetNameVarFSA().c_str(), fLevel);
    fSavLevel = fLevel;
  }
}

Созданный автоматный код мало отличается от кода обычного классического объекта. Это видно по объему подчеркнутого текста. Здесь есть некий базовый класс – LFsaAppl, которому передается ссылка на таблицу переходов автомата - TBL_SENSORLEVEL. Присутствуют методы, которые ассоциируются с именами в данной таблице. Во всем этом чего-то необычного для ООП нет.

Как все это работает? Имеется ядро в форме библиотеки, которому  передается информация об автоматном объекте. За создание  ядра, ссылок к нему и реализацию дискретного времени, отвечает код, представленный на листинге 7.

Листинг 7. Код реализации ядра и дискретного времени автоматов на ESP32
#include <Arduino.h>
#include "TAppProcesses.h"        // ядро FSM
void setup() {
  // Инициализация последовательного порта
  Serial.begin(115200); Serial.println();
  Serial.println("Demo project - SensorLevelFSM");
// создаем среду для автоматов
  VCPaCore_init();                // создание ядра FSM
// загружаем автоматные процессы  
  LoadingFsaProsesses();
  string str = "Setting1";
  CVarSetting *pViewVar = pTAppProcesses->pSetVarSetting->GetAddressVar(str);
  if (pViewVar) { pViewVar->dDeltaTime = 2; }
}
void loop() {
// моделирование дискретного времени FSM  
  VCPaCore_TimerEvent(0);
}

За создание автоматных объектов отвечает фунгция LoadingFsaProcesses(). Ее код приведен в листинге 8. Здесь демонстрируется создание только одного объекта автоматного типа. По аналогии можно создать его копии или добавить другие.

Листинг 8. Код подключения автоматного объекта к ядру VCPa
#include "TAppProcesses.h"
#include "SensorLevel.h"
SensorLevel *pSensorLevel{nullptr};             // ссылка на процесс 1
SensorLevel *pSensorLevel2{nullptr};            // ссылка на процесс 2
void LoadingFsaProsesses() {
    pSensorLevel = new SensorLevel("sensor1");  // создание объекта процесса
    string vFSA1 = "sensor1;0;0;0;0;InitFsaWorld;1;1";
    static CVarFSA var1(pTAppProcesses,vFSA1);  // описатель процесса
    bool bRet1 = var1.LoadFsa(pSensorLevel); // загрузка процесса
    if (bRet1) Serial.println("Load sensor1");
// устанавливаем задержки датчикам уровня    
    pSensorLevel->nDelay = 500;      // задержка датчика 1
//    pSensorLevel->FSetSleep(100);  // установка длительности такта процесса
    pSensorLevel2 = new SensorLevel("sensor2"); // создание объекта процесса
    string vFSA2 = "sensor2;0;0;0;0;InitFsaWorld;1;1";
    static CVarFSA var2(pTAppProcesses,vFSA2);   // описатель процесса
    bool bRet2 = var2.LoadFsa(pSensorLevel2); // загрузка процесса
    if (bRet2) Serial.println("Load sensor2");
// устанавливаем задержки датчикам уровня    
    pSensorLevel2->nDelay = -1;   // задержка датчика 2
}

В заключении осталось озвучить размер библиотеки. Для микроконтроллера ESP32 с объемом памяти 4Mb он чуть более 24%. Совсем не запредельный объем, если сравнивать с наиболее объемными библиотеками ESP32. Тем не менее, как показывает практика, лучше использовать конфигурации ESP32 с большим объемом оперативной памяти.

Выводы

Выше мы описали, как по большому счету изменится само программирование, использующее конечно-автоматную форму вычислительных процессов. Причем любых – последовательных и параллельных. И, подчеркнем, не только на базе микроконтроллеров, а вообще везде. Цель, конечно, глобальная, но реализуемая. Тем более, когда она стала доступна микроконтроллерам!

Сама же реализация автоматного ядра вполне может быть другой. Хотелось бы видеть ее аппаратной, но на крайний случай это могут быть те же потоки. Однако параллелизм в одном потоке  удобнее, надежнее и если уж совсем прямо - эффективнее. Так нет проблем с отладкой, с синхронизацией, гонками, производительностью и т.д. и т.п.

Но обязательно найдется тот, кто-то возразит по поводу однопоточного параллелизма. Но в этом нет ничего необычного, т.к. вопрос параллелизма целиком лежит во власти модели вычислений, а не ее реализации. Если модель параллельна и корректно реализована, то параллельны будут и представленные ею процессы. Таков простой и краткий ответ, рассеивающий подобные сомнения.

Современные микроконтроллеры и ПК прекрасно дополняют друг друга. Создается ситуация, когда среду проектирования для микроконтроллеров можно использовать лишь на финишном этапе, а саму разработку вести на ПК. Именно так в среде Qt Creator была создана и проверена наша библиотека. Это позволило многократно ускорить процесс проектирования. Да и, если честно, вообще осуществить саму идею переноса.

Необходимо упомянуть характеристики реального проекта, из которого заимствован рассмотренный выше пример. Кроме датчиков уровня жидкости он содержит датчики температуры, влажности, освещенности, реле  и т.д. и т.п. Все процессы реализованы как автоматы, которые работают в жестком реальном времени (дискретность такта 10 мсек).   Проект поддерживает подключение по WiFi, MQTT, Home Assistant и имеет  свою страничку управления. При этом нет нужды во FreeRTOS фактически от слова совсем.

С учетом библиотек и прикладных процессов загрузка памяти на базе микроконтроллера ESP32-WROOM-32 приближается к 100%, где библиотеки занимают почти 85% из доступных 4Mb памяти (библиотека автоматов, напомним, занимает немногим более 24%) Поэтому в планах переход на микропроцессор  ESP-32-S3-WROOM-1, который имеет на борту  16Mb. А этого уже достаточно для большинства более масштабных проектов в рамках технологии АП.

Вариант проекта на базе ESP32 под именем HotbedAgroControl, спроектированный по канонам АП, уже доступен на маркетплейсе OZON (https://ozon.ru/t/CKMsv9P). На его базе технология АП прошла многостороннюю проверку и обкатку теперь уже на микроконтроллерах. Правда, из-за проблем с объемом памяти (см. замечание выше) в самом простом варианте (фактически в формате SWITCH-технологии). Но даже в этом случае она подтвердила свою эффективность при решении проблем распараллеливания в жестком реальном времени.

Осталось сказать, что исходные коды библиотеки VCPa доступны на GitHub по адресу - https://github.com/lvs628/VCPa, где в папке examples находится и рассмотренный нами пример. Поскольку библиотека написана на С++  лишь с небольшими вкраплениями STL, то напрямую или после небольшой адаптации она может  быть использована везде, где все это есть. А есть это везде или почти везде, если говорить о микроконтроллерах.

Литература

1.       ВКПа. Введение, ч.1. Визуальное проектирование автоматов. https://habr.com/ru/articles/794498/

2.       Автоматная модель управления программ. https://habr.com/ru/articles/484588/

3.       Модель параллельных вычислений. https://habr.com/ru/articles/486622/

4.       Автоматное программирование: определение, модель, реализация. https://habr.com/ru/articles/682422/

5.       Машина Тьюринга, как модель автоматных программ. https://habr.com/ru/articles/481998/

6.       Параллелизм, корутины, событийные автоматы,… живая математика. https://habr.com/ru/articles/499460/

7.       Параллелизм и эффективность: Python vs FSM. https://habr.com/ru/articles/506604/

8.       Мир без корутин. Итераторы-генераторы. https://habr.com/ru/articles/512348/

9.       Мир без корутин. Костыли для программиста — asyncio. https://habr.com/ru/articles/513512/

10.   Автоматное программирование в SimInTech и ВКПа. https://habr.com/ru/articles/717190/

11.   Практика применения автоматов в ПЛК. https://habr.com/ru/articles/694078/

12.   Йодан Э. Структурное проектирование и конструирование программ. М.: Мир,1979 - 415с

13.   Хамби Э. Программирование таблиц решений. М.: Мир, 1976. – 86 с.

14.   Зелковиц М., Шоу А., Геннон Дж. «Принципы разработки программного обеспечения». М.: Мир, 1982. –368 с.

15.   Толковый словарь по вычислительным системам/Под ред. В. Иллингоута и др.: Пер. с англ. А.К. Белоцкого и др.; Под ред. Масловского. – М.: Машиностроение, 1991. – 560 с.

Комментарии (1)


  1. ruomserg
    15.08.2025 09:45

    Конечные автоматы - это прекрасно. Я и сам их очень люблю. Но это ни разу не панацея. Во-первых, у вас существуют физически уникальные ресурсы (например, I2C шина). Исчезает независимость состояний - теперь придется думать и отлаживать целые кусты переходов с учетом общих ресурсов. Аналогично если появляется remove state - например, датчик в который надо сначала записать адрес, потом читать результаты... Во-вторых, сама форма для сложных процессов малопрактична. Сделать общение, например, с дисплеем 1604 через I2C регистр-расширитель - можно. Будет ли такой код красивее и понятнее чем через примитивы параллельного исполнения - ой, не знаю!

    Если строить систему на базе автоматов - то скорее всего придется делать очередь событий, и иерархические автоматы. Иначе для сколько-то сложной системы - таблица переходов становится необозримой.

    Я помню, был какой-то фреймворк под MCU который продвигал идею автоматного программирования (и имел GUI для описания автоматов, и потом генерировал код). Чем кончилось - за давностью лет не помню.

    В общем, проклятие сложности никуда не девается. С одной стороны, перейдя на автоматы мы избавляемся от головной боли с гонками - ибо теперь можем 100% контролировать потоки выполнения. Но получаем ту же сложность, вылезающую в описании автоматов и размере таблиц переходов...