
Проект называется AtsPLC. Это мой пет-проект: я начал его в 2024 году и делал неспешно. В этой статье я расскажу об идее и о том, как с контроллером работать. А во второй части - о том, как все устроено внутри.
Идея: ladder не нужен большой экран
Мне хотелось, чтобы логику можно было увидеть и поменять прямо на устройстве. И чтобы для этого хватило маленького и дешевого экрана.
Первопричина
Все началось с аквариума. Надоело каждый день включать свет, а днем еще и следить за солнцем: от избытка света вода в аквариуме зеленела. Компрессор тоже приходилось время от времени выключать. В ящике как раз лежало несколько модулей “WeMos ESP8266 board with OLED and 18650”, и один из них стал контроллером. Сначала программа была жестко зашита в код, без всяких настроек. Но скоро стали видны ее недостатки: в выходные хотелось, чтобы свет включался позже, а пороги фотодатчика приходилось то и дело подстраивать. И каждый раз ради этого нужно было править код и перепрошивать контроллер. Тогда вспомнилась Ladder Diagram, и появилась идея: пусть программу можно будет менять прямо на устройстве.
Для этой задачи хорошо подходит ladder. Программа на ladder - это просто список сетей. Каждая сеть читается как одна строка слева направо: от левой шины через контакты к катушке у правой шины. Такую строку легко показать даже на узком экране, а список строк удобно листать двумя кнопками.
Идея не новая: писать программу прямо на устройстве давно позволяют умные реле и даже конструкторы LEGO:
Устройство |
Экран |
Как пишется программа на устройстве |
|---|---|---|
Schneider Zelio Logic SR2 |
символьный |
ladder из символов, в строке 5 контактов и катушка |
Eaton easyE4 |
символьный |
ladder из символов, ввод курсорными кнопками |
Siemens LOGO! 8 |
символьный |
FBD, не ladder |
LEGO Mindstorms NXT/EV3 |
графический |
цепочка блоков-иконок |
В AtsPLC на графическом экране 128x64 пишется именно ladder. Каждый элемент нарисован как настоящий элемент схемы и сразу показывает свои параметры прямо в сети: адрес контакта, уставку компаратора, время таймера, значение индикатора.
Программа и есть экран оператора
В AtsPLC программа сама показывает, что происходит:
Активный участок цепи нарисован сплошной линией, а пассивный - пунктиром. Сразу видно, где проходит сигнал, а где цепь разорвана.
Сверху экрана есть строка состояния с 8 маленькими шкалами: AI, DI, O1, O2 и V1…V4. Так значения всех входов, выходов и переменных видны всегда.
Элемент Indicator показывает значение адреса крупным шрифтом. Его можно масштабировать и поставить десятичную точку. Например, сырое значение АЦП можно показать сразу в вольтах.
В режиме Stop правая шина каждой сети пунктирная, а у отключенной сети пунктирная левая шина.

Получается, что отдельный HMI делать не нужно - он уже есть в самой программе. Нужно показать температуру? Достаточно добавить в программу сеть с одним элементом Indicator.
Железо
Фото собранного устройства
Сразу предупрежу: это макет, собранный на скорую руку. Задача была проверить идею, а не сделать красиво, поэтому здесь много термоклея и проводов поверх платы.

Устройство состоит из двух частей: платы контроллера и периферии. Плата контроллера - это готовый модуль “WeMos ESP8266 board with OLED and 18650”, а периферию я собрал на макетной плате. Для моих задач этого вполне хватает. Схема лежит в репозитории, так что при желании ее можно перенести на свою печатную плату.
Что есть на плате контроллера:
ESP8266;
OLED 0.96" 128x64 на контроллере SSD1306, шина I2C;
джойстик на 5 положений: вверх, вниз и нажатие работают как кнопки Up, Down и Select;
на этом модуле джойстик вправо замыкает вход DI (GPIO0), поэтому DI удобно проверять без внешнего датчика, а влево сбрасывает устройство;
светодиод.
Что есть в периферии:
1 дискретный вход (DI);
1 аналоговый вход (AI), это АЦП ESP8266, 0…1 В;
2 выхода (O1, O2);
питание от сети 220 В через модуль AC/DC на 5 В.
Выходы я собирал в двух вариантах: с электромагнитными реле с катушкой на 12 В и переключающим контактом, и с твердотельными оптореле K293KP11BP. Входы защищены TVS-диодами и RC-фильтрами.
Схема. Деталей там немного, и все они легко находятся.
Так что если у вас в ящике лежит пара ESP8266 без дела, это хороший повод дать им работу.
Как писать программу тремя кнопками

На экране помещаются две сети, а кнопки Up и Down листают программу.
Менять программу можно только в режимах Stop и Debug, о них чуть ниже. В режиме Run программа защищена от случайных правок, и кнопки только листают ее.
Чтобы изменить сеть, нужно сделать так:
Select выбирает сеть.
Второй Select открывает ее.
Up и Down выбирают место в сети.
На выбранном месте Up и Down перебирают элементы. При этом контроллер предлагает только те элементы, которые помещаются на оставшееся место. Параметры переходят к новому элементу: например, если поменять нормально открытый контакт на нормально закрытый, адрес останется прежним.
Select подтверждает элемент, и дальше Up и Down меняют его параметры. Если держать Up или Down нажатой, значение меняется большим шагом.
А если долго держать Select на выбранной сети, откроются действия со всей сетью: переместить, копировать, удалить или отключить.
Как оказалось, тремя кнопками собирать программу на ladder довольно просто. Сначала я относился к этой идее скептически и собирался подключить больше кнопок. Но лень творит чудеса: новые кнопки я так и не подключил, а трех оказалось вполне достаточно.

Режимы работы
Режим |
Сети выполняются |
Выходы |
Редактирование |
|---|---|---|---|
Stop |
нет |
сброшены в 0 |
можно |
Run |
да |
работают по программе |
нельзя |
Debug |
да |
работают по программе |
можно |
Debug нужен при наладке: можно поменять уставку и сразу увидеть результат на живом объекте. Но это опасное сочетание, поэтому флаг Debug не переживает пропадание питания: после него устройство стартует в Run.
Сервисный режим
Если включить питание с нажатой кнопкой Up, откроется сервисное меню. В нем можно выбрать режим работы, настроить WiFi через приложение ESP-Touch, сохранить программу в один из 4 слотов, восстановить ее или сбросить настройки.
Какие элементы есть
Что может делать программа, зависит от набора элементов, поэтому перечислю их все. Но сначала про адреса. Доступны DI, AI, O1, O2 и переменные V1…V4, и все они хранят значения 0…255.
Входные элементы:
Элемент |
Что делает |
|---|---|
Нормально открытый контакт |
пропускает сигнал, когда значение адреса не 0 |
Нормально закрытый контакт |
пропускает сигнал, когда значение адреса 0 |
Таймер, секунды |
задержка 1…99999 с |
Таймер, миллисекунды |
задержка 50…99950 мс, шаг 50 мс |
Генератор |
импульсы, длительность паузы и импульса настраивается |
Компараторы |
==, <>, >, >=, <=, < со значением уставки |
Indicator |
показывает значение адреса с масштабом и десятичной точкой |
Выходные элементы:
Элемент |
Что делает |
|---|---|
Прямой выход |
пишет 255, пока цепь активна, и 0, когда пассивна |
Set |
пишет 255, когда цепь стала активной. Если цепь потом стала пассивной, значение остается 255, пока его не сбросит Reset |
Reset |
пишет 0, когда цепь стала активной |
Inc |
прибавляет 1, когда цепь стала активной, максимум 255 |
Dec |
вычитает 1, когда цепь стала активной, минимум 0 |
Связь между сетями:
Элемент |
Что делает |
|---|---|
ContinuationIn |
ставится в конце сети и передает ее состояние дальше |
ContinuationOut |
ставится в начале сети и берет это состояние |
В одну сеть помещается до 5 элементов. Если этого мало, выручает пара ContinuationIn и ContinuationOut: цепь просто продолжается в следующих сетях. Причем одна ContinuationIn может питать сразу несколько сетей ниже.
Привязки к WiFi, времени и настройкам:
Элемент |
Что делает |
|---|---|
WiFiBinding |
ищет сеть WiFi с заданным именем и пишет в переменную уровень ее сигнала |
WiFiStaBinding |
подключается к роутеру и пишет в переменную уровень сигнала |
WiFiApBinding |
поднимает свою точку доступа и пишет в переменную число подключенных клиентов. Маска по MAC-адресу задает, какие клиенты считаются: так можно учитывать только свой телефон |
DateTimeBinding |
пишет в переменную секунды, минуты, часы, день, день недели, месяц или год |
Settings |
показывает и правит одну настройку устройства прямо в сети |
Как элементы выглядят на экране

Пример: сигнализация, где телефон - это ключ
Чтобы было понятнее, как это выглядит в деле, разберу одну задачу.
Контроллер поднимает свою точку доступа WiFi. Если к ней подключен телефон хозяина, то значит, хозяин дома. Посторонний телефон к точке доступа не подключится: для этого нужно знать пароль. Но пароль могут знать и другие устройства, которые когда-то подключались: старый телефон, планшет или телефон гостя. Чтобы их не считать, в маске MAC-адреса указывается MAC телефона хозяина, и тогда хозяином считается только он. А если телефона нет и при этом открылась дверь, контроллер ждет 30 секунд и включает сирену на 10 минут. Красный светодиод показывает, в каком состоянии охрана. Линия датчика двери тоже проверяется: обрыв или короткое замыкание видно на светодиоде, а если дома никого нет, то неисправность сразу включает тревогу.
Подключение:
Вход или выход |
Что подключено |
|---|---|
AI |
датчик двери через делитель |
O1 |
сирена |
O2 |
красный светодиод |
Датчик двери включен в делитель на аналоговом входе. Благодаря этому по одному входу можно различить сразу четыре состояния:
AI |
Состояние |
|---|---|
0…29 |
неисправность, например линия замкнута |
30…129 |
дверь закрыта |
130…229 |
дверь открыта |
230…255 |
неисправность, например оборван резистор |
Переменные:
V1 - сколько телефонов подключено к точке доступа;
V2 - состояние охраны: 0 - тишина, 1…30 - ожидание 30 секунд, 31…180 - тревога, 181 - тревога закончилась.
V2 тут работает сразу и как состояние, и как счетчик времени. Генератор перед элементом Inc прибавляет 1 на каждом импульсе, и поэтому значение V2 растет вместе со временем.
Начнем с начала программы. Первая сеть поднимает точку доступа, а следующие три выводят значения на экран:
1 ||--[WiFiAp V1 "AtsPLC" "ats-PLC0" "************"]----|| 2 ||--[Indicator AI 0..255]-----------------------------|| 3 ||--[Indicator V1 0..255]-----------------------------|| 4 ||--[Indicator V2 0..255]-----------------------------||
Теперь запуск тревоги. Если телефона нет, дверь открыта, а охрана в покое, то V2 становится равной 1:
12 ||--[V1 == 0]--[AI >= 130]--[V2 == 0]----------------->> V2 13 >>-----------------------------------------------(+)--||
Дальше ожидание 30 секунд. Пока V2 находится между 1 и 30, она растет на 1 каждую секунду:
16 ||--[V2 > 0]--[V2 <= 30]------------------------------>> V2 17 >>--[SQW off 500ms on 500ms]---------------------(+)--||
И наконец, хозяин вернулся. Как только телефон подключился, все сбрасывается. Здесь одна ContinuationIn питает сразу три сети:
21 ||--[V1 > 0]------------------------------------------>> V2 22 >>-----------------------------------------------(R)--|| O1 23 >>-----------------------------------------------(R)--|| O2 24 >>-----------------------------------------------(R)--||
Все ladder-схемы в статье сделаны скриптом Tools/ladder.py из файла программы.

Сети 2-4 здесь работают как экран оператора. При наладке на них видно значение AI при открытой и закрытой двери, так что пороги можно поправить прямо на месте.
Полное описание, схема делителя и вся программа лежат в Demo/security-alarm.md. Там же есть еще три готовые программы:
аквариум: свет и насос фильтра по расписанию, с датчиком освещенности;
WiFi watchdog: перезагружает роутер, когда его сигнал слабый или пропал;
термостат: держит температуру в ящике с помощью нагревателя и вентилятора, датчик - термистор.
Веб-интерфейс
Когда устройство подключено к роутеру, у него появляется веб-интерфейс. Браузер показывает ту же картинку, что и OLED, только крупнее, а кнопки можно нажимать прямо на странице или с клавиатуры. При этом в браузере видно больше сетей, чем на устройстве: он собирает их из кадров, которые уже видел.
Еще через веб-интерфейс можно скачать и загрузить программу и переключить режим работы. А прошивка обновляется по WiFi через тот же HTTP-сервер, об этом во второй части.

Своего редактора программы со страницами, формами и кнопками в браузере нет, и это сделано специально. Браузер просто показывает копию экрана устройства, поэтому умеет ровно то же, что и само устройство. Как это устроено, я расскажу во второй части.
Ограничения
Это самодельный ПЛК. Язык программы - подобие ladder из IEC 61131-3. Контроллер подходит для несложных процессов, где не нужна высокая надежность: ESP8266 с включенным WiFi на нее и не рассчитан, а на входах нет гальванической развязки.
Мало входов и выходов. Сейчас их всего 1 DI, 1 AI, 2 выхода и 4 переменные. Но можно добавить расширитель GPIO: исходный код доступен, и лицензия разрешает его менять. Если сделаете это как отдельную конфигурацию, буду рад pull request.
Значения только 0…255. Знаковых чисел, дробей и арифметики нет.
Каждая сеть - одна цепочка. Так ее удобно показывать на маленьком экране. Логическое ИЛИ собирается из нескольких сетей: например, каждое условие в своей сети ставит общую переменную через Set, а отдельная сеть сбрасывает ее через Reset, когда все условия ложны.
Размер программы. До 80 сетей, до 5 элементов в сети, файл до 4 КБ.
Одно радио. ESP8266 не может одновременно держать свою точку доступа и подключение к роутеру. Поэтому, например, в программе сигнализации веб-интерфейс работает только в режиме Stop.
Веб-интерфейс без пароля. Устройство рассчитано на домашнюю сеть, которой вы доверяете.
Лицензия
Бесплатно для хобби, обучения и для работы внутри своей компании. А если вы продаете устройства с AtsPLC или ставите его клиентам за деньги - нужна коммерческая лицензия.
Исходный код доступен всем, но в строгом смысле это не open source, правильнее называть это source available.
Что дальше
Идея оказалась удачной, и на ее основе я начал новый проект - SuPLC. Он пока еще в разработке.
SuPLC работает на нескольких платформах из одного кода: ESP32, ESP32-S3, ESP32-P4, ESP8266, LPC4088 и Linux. Дисплеи тоже разные: E-Ink 2.13", AMOLED 2.41" с тачем, LCD 3.5" с тачем, TFT 5.6". В нем уже есть Modbus TCP и RTU, сохраняемые переменные и значения до 65535.
Фото SuPLC на трех дисплеях

А во второй части я расскажу, как AtsPLC устроен внутри: как работает цикл контроллера без постоянного опроса, как рисуется картинка на 128x64, почему веб-интерфейс - это просто копия экрана, как устройство перезапускается без потери состояния и как все это тестируется.
Ссылки
Репозиторий: https://github.com/viordash/AtsPLC
Элементы: Docs/LADDER_ELEMENTS.md
Готовые программы: Demo/README.md
Комментарии (4)

NutsUnderline
05.11.2026 08:48AMOLED 2.41" (ESP32-S3),
в такой размер amoled не видел, с ними довольно мелкие платки, а можно название?

viordash Автор
05.11.2026 08:48я покупал этот девайс "Плата разработки Waveshare ESP32-S3-Touch-AMOLED-2.41 с 2.4” сенсорным AMOLED дисплеем 600x450, встроенный акселерометр и гироскоп (Без корпуса)"
по ссылке https://amperkot.ru/products/plata_razrabotki_waveshare_esp32s3touchamoled241_s_24_sensornyim_amoled_displeem_600x450_vstroennyiy_akselerometr_i_giroskop__bez_korpusa/42745417.html
NutsUnderline
mqtt прикрутить надо, думаю несложно,тогда очередь будет на этот девайс. из тех кто мечтает домашнюю автоматизацию на "понятных" iec языках
viordash Автор
я пробовал, но у esp8266 не хватает ресурсов на нормальную работу с mqtt.