Рано или поздно возникает желание запустить на ПЛК какой-нибудь скриптик или своего демона, который бы гораздо быстрее и проще решал определенную задачу, чем стандартными инструментами среды разработки.
При таком подходе появляется очень интересный вопрос: «А как обмениваться данными между двумя различными процессами?»
Существуют различные решения: через файл, обмен через сокеты и разделяемая память. И как раз, на основах разделяемой памяти, или shared memory, я решил остановиться.
Разделяемая память
Техника разделяемой памяти позволяет осуществлять обмен информацией через общий для процессов сегмент памяти без использования системных вызовов ядра. Сегмент разделяемой памяти подключается в свободную часть виртуального адресного пространства процесса. Таким образом, два разных процесса могут иметь разные адреса одной и той же ячейки, подключенной разделяемой памяти.
И в целом это очень здорово, но есть и подводные камни такой технологии. За целостность данных отвечаете вы, так что стоит задуматься о реализации примитивов синхронизации, которые позволят избежать «Состояния гонки», также стоит продумать взаимодействия между ППО и какой-то софтиной.
Shared memory в Codesys
Для работы с разделяемой памятью в Codesys существует библиотека SysShm. Данная библиотека предоставляет набор функций:
Для теста возможностей я решил реализовать самый минимальный базовый функционал.
- Создание области памяти
- Запись в эту область
- Чтение данных
- Закрытие области памяти
Создание области разделяемой памяти
Начну сразу с листинга кода
ShmHandle:=SysSharedMemoryCreate(pResult:=ADR(ptrResult),
pszName:=Name,
pulSize:=ADR(pulSize),
ulPhysicalAddress:=0);
Для создания shared memory требуется функция SysSharedmemoryCreate.
Функция возвращает переменную типа RTS_IEC_HANDLE. Данный тип представляет собой псевдоним для POINTER TO BYTE.
Входными параметрами являются
- pResult — указатель на переменную, которая будет хранить результат выполнения функции
- pszName — переменная типа String с именем области памяти
- pulSize — указатель на размер области памяти в байтах
- ulPhysicalAddress — опциональный адрес памяти
После выполнения функции необходимо проверить результат выполнения. Все возможные результаты перечислены в библиотеке CmpErrors
Запись в память
Тут все тоже просто
SysSharedMemoryWrite(hShm:=ShmHandle,
ulOffset:=0,
pbyData:=ADR(Data),
ulSize:=4,
pResult:=ADR(ptrResult));
На вход данной функции необходимо передать:
- hShm — RTS_IEC_HANDLE, который вернула функция создания области памяти
- ulOffset — отступ в байтах
- pbyData — указатель на область памяти с данными, которые требуется записать в общую память
- ulSize — размер данных для записи
- pResult — указатель на переменную, которая будет хранить результат выполнения функции
Возвращает функция количество записанных байтов.
Набор данных которые были записаны
Чтение из Shared memory сторонней программой
Вот в этом моменте и начинается магия. Теперь набор этих данных можно вычитать при помощи сторонней программы, которая будет запущена на ПЛК. Для запуска программы стоит помнить, что системный вызовы в операционных системах отличаются, а также стоит правильно скомпилировать программу под нужную архитектуру.
Если с ОС еще понятно, то для определения архитектуры процессора на система linux необходимо в терминале ПЛК, доступ к которому вы можете получить по SSH ввести следующую команду
cat /proc/cpuinfo
У меня полученный результат выглядел так:
processor : 0
model name : ARMv7 Processor rev 2 (v7l)
И этого мне вполне достаточно, чтобы скомпилировать под нужный процессор.
После компиляции заливает на программу на плк, можно при помощи SCP и запускаем ее.
Логика моей программы проста. Она считывает данные из общей памяти, выводит в терминале эти данные и сколько байт считалось, а после записывает туда новый набор данных.
Чтение из памяти
Последний этап этого действия
SysSharedMemoryRead(hShm:=ShmHandle,
ulOffset:=0,
pbyData:=ADR(Data),
ulSize:=4,
pResult:=ADR(ptrResult));
На вход данной функции необходимо передать:
- hShm — RTS_IEC_HANDLE, который вернула функция создания области памяти
- ulOffset — отступ в байтах
- pbyData — указатель на область памяти куда будут прочитаны данные
- ulSize — размер данных для чтения
- pResult — указатель на переменную, которая будет хранить результат выполнения функции
Готово.
Закрытие области разделяемой памяти
Тут все еще проще
SysSharedMemoryClose(hShm:=ShmHandle);
На вход просто передаем RTS_IEC_HANDLE и получаем CmpErrors код, чтобы мы могли отследить выполнение функции.
Теперь со стороны ППО мы не можем ни прочитать, ни записать данные.
При попытке взаимодействия с закрытым RTS_IEC_HANDLE ППО контроллера выдаст исключение, а сам ПЛК уйдет в стоп.
Вывод
Очень удобный инструмент для своих задач, не обязательно, что он когда-то потребуется. Как и любая работа с памятью, требует четкого понимания действий программиста. Также при работе с данными может возникнуть состояние гонки, что тоже придется решать вам, а так пользуйтесь на здоровье.
Почта для связи и предложений: info@engcore.ru
Канал с новостями в Телеграмм: https://t.me/wtfcontrolsengineer
Блог: https://blog.engcore.ru/
Чат: https://t.me/wtfplc