Групповые запросы в CodeSys 2.3 через STRING-модуль
Эта статья предполагает, что вы используете язык программирования ST. Он наиболее удобен для работы со множеством переменных и разными логическими условиями. Я сожалею, но примеров на FBD/CFC не будет.
Также я предполагаю, что вы знаете про то, как работать с конфигурацией ПЛК, умеете объявлять переменные и константы, а также понимаете про то, как устроена память ПЛК и как в ней хранятся данные. Если хотите — почитайте старый пост про начальную работу с CodeSys 2.3, а ещё пост про память и переменные ПЛК (адресация).
Хочу предупредить о том, что исходные примеры и концепты для этой статьи были выдуманы в 2021 году, когда я ещё не умел отображать переменные на память ПЛК (я рассказал про это в этой статье). Поэтому здесь кое-где использованы дикие подвыверты с копированием памяти через буферы. Мне важно передать концепт и идею решения. Вы можете переделать код под себя.
Когда-нибудь я всё же напишу обещанный ещё в 2021 году пост про Modbus, где расскажу всё, что мне кажется интересным (его тонкости и особенно про групповые запросы). Пока же я описываю идеи, которые могут пригодится уже сейчас тем, кто программирует на CodeSys. В данном случае — снова на CodeSys 2.3.
Сегодня я расскажу о хитрой особенности среды CodeSys 2.3 при помощи которой можно штатными средствами (через конфигурацию ПЛК) создавать групповые запросы на чтение или запись нескольких регистров Modbus подряд. Это удобно, когда мы хотим максимально сократить число запросов к устройствам для ускорения их опроса.
Содержание
1. Групповые запросы Modbus: удобство и скорость.
Вот посмотрите на то, сколько элементов добавлено в конфигурацию ПЛК для опроса модуля ОВЕН МВ110-224.8А (8 универсальных аналоговых входов; напоминаю пост про модули ввода-вывода ОВЕН) и для модуля МУ110-224.6У (6 выходов 0..10V):
Множество регистров для чтения данных из модуля аналоговых входов без групповых запросов
Тут у нас 16 + 6 = 24 запроса! Да! Каждый объект в дереве конфигурации ПЛК — это отдельный Modbus-запрос. Сам ПЛК никак их не объединяет и не группирует. В результате у нас будет много накладных расходов, связанных с посылкой каждого запроса и получения ответа на него.
Если ваш проект медленный (например, вы опрашиваете модули ввода-вывода для получения медленно меняющейся температуры), то на это можно наплевать. В этом случае единственная сложность будет в том, чтобы набить эти 16 запросов для модуля аналоговых входов в конфигурацию.
А вот если мы хотим ускорить обмен данными, сократить много лишних данных, которые передаются по Modbus и упростить себе создание конфигурации ПЛК — то там, где это возможно, нам следует использовать групповые запросы Modbus.
Суть групповых запросов в том, чтобы читать регистры подряд друг за другом. Читаются они командами 0x03 (Read Holding Registers) или 0x04 (Read Input Registers), а записываются командами 0x10 (Write Multiple Registers). В таких командах указывается номер первого регистра и число регистров, которые надо прочитать. Для устройства такая команда так и выглядит: «Дай данные из 10 регистров, начиная со 120» вместо десяти команд типа «Дай данные из регистра 120», «Дай данные из регистра 121», …, «Дай данные из регистра 129».
Чтобы такие групповые запросы работали, устройство ДОЛЖНО поддерживать их. Такая поддержка зависит от разработчика устройства.
Например, датчики климата WirenBoard WB-MSW (мой пост про них с руганью про старую версию) долгое время позволяли читать свои регистры в любом количестве подряд, но имели пропуски в карте регистров. Например, регистры шли так: 0, 1, …, 3, 4, 5, …, 8, 9, 10, 11. Хотелось бы читать регистры с 0 по 11, но из-за пропусков это было невозможно. Сейчас (после того, как я очень ругался и попросил сделать это) такая функция датчиками WB-MSW поддерживается.
Модули ввода-вывода ОВЕН тоже не позволяют читать несуществующие регистры Modbus, но у них в картах регистров чаще всего нет пропусков: можно читать все регистры подряд, и за это им спасибо!
Среда CodeSys 3.5 умеет создавать групповые запросы в явном виде: в Канале опроса нужно указать начальный регистр и их количество для чтения. Данные, которые получаются таким образом, отдаются сразу в виде массива WORD’ов (или байт), который можно обработать.
Вот скриншот из поста про измерители параметров электросети ОВЕН КМС-Ф1, где читаются сразу два регистра (в столбце «Длина» указано 2 — два регистра):
Каналы для опроса КМС-Ф1 по Modbus в CodeSys v3.5 (чтение напряжения и тока)
А вот в CodeSys 2.3 явного создания групповых запросов нет. Каждый элемент, который добавлен в дерево конфигурации ПЛК будет являться одним запросом.
На всякий случай упомяну то, что речь идёт про стандартный способ — через дерево конфигурации ПЛК. Если использовать библиотеки для опроса Modbus, то через них можно создавать любые запросы, конечно же.
Однако решение есть! Это специальные модули STRING, которые можно добавить в конфигурацию ПЛК так же, как и модули чтения-записи регистров.
2. Модули STRING. Ограничения. Настройка для модулей IO ОВЕН.
Итак, разбираемся с этими модулями STRING. Они есть в двух вариантах:
- Модуль для чтения («String input module»; в названии в ПЛК не написана буква «e»);
- Модуль для записи («String output module»).
Добавление модуля STRING в конфигурацию ПЛК для групповых запросов
Вот возможности и ограничения модулей STRING:
- Они работают как групповые запросы. Команду для чтения/записи можно выбрать в их настройках;
- Прочитанные значения возвращается как строка длиной 80 символов. Это значит, что максимальная длина данных в запросе — 80 байт или 40 регистров.
- Длина строки всегда фиксирована и составляет 80 символов вне зависимости от того, сколько регистров вы читаете;
- Прочитанные данные НЕ будут выглядеть в виде строки, так как (как мы говорили в посте про организацию памяти в ПЛК) в CodeSys концом строки считается байт (символ) с кодом 0, а этот символ может быть частью данных (например, условная температура равна 0 градусов).
Поэтому строку здесь следует считать массивом байт! - В настройках нужно указать номер регистра, откуда начать чтение, и число байт (а не номер конечного регистра)!
Главное, не путайте то, где байты, а где регистры. Это главная мутность данного модуля. Прям сразу вспоминается классика («Хроники лаборатории»):
Прототип реактора готов к испытаниям. Hас не допустили. Даже на территорию — сказалось криминальное прошлое. Глупо с их стороны. Откуда им знать, что в инструкции давление смеси в атмосферах, а на пульте градуировка — в фунтах на квадратный дюйм… Реактор проплавил пол, три этажа и ушел метров на 70 в грунт. Там и остался, исчерпав запас горючего. Как и предполагалось.
Итак, давайте применим наши новые возможности модулей STRING. Мы будем читать данные с модуля МВ110-224.8А так, чтобы число запросов к нему было минимальным.
Для начала нам надо знать, сколько регистров мы хотим считывать и понять, уложимся ли мы в 80 байт (40 регистров). Смотрим на карту регистров модуля и слегка материмся, так как здесь данные измерений перемежаются с данными статусов каналов и другими данными.
Вот это пример, который я когда-нибудь рассмотрю в посте про Modbus: иногда хорошо иметь несколько карт регистров с разной группировкой данных. Например, пусть будет дополнительная карта, в которой все значения во Float будут идти подряд, а потом все значения статусов каналов тоже подряд. Тогда нам нужно было бы 8 x 2 = 16 регистров для Float и 8 регистров для статусов. Всего 16 + 8 = 24 регистра. Это был бы очень короткий запрос к модулю.
Карта регистров модуля .8А (8 аналоговых входов)
В данной карте регистров (я использую документацию от модуля HW 1.0) значения измерений в целом виде с точкой, в формате с плавающей запятой и статусы измерения каналов идут подряд. Поэтому, раз мы хотим вычитать все значения каналов из модуля, сократив число запросов, нам придётся читать всю карту регистров.
И эта карта занимает регистры с 0 по 47. То есть, всего 48 регистров. А ограничение модуля STRING — 40 регистров. Что это значит? Нет, это не ошибка. Просто мы сами для себя должны разделить нашу карту на такие запросы, которые будут не длиннее 40 регистров.
Конечно же, проще всего разделить карту регистров пополам: 4 + 4 канала (24 + 24 регистра), но я в своём старом примере разделил карту так, чтобы максимально заполнить STRING данными: 6 + 2 канала (36 + 12 регистров). Это не ошибка, и всё хорошо работает.
Важное замечание. Если модуль имеет данные, собранные по группам (как здесь), то лучше всего делить запросы так, чтобы каждый из них содержал целое число групп (то есть, не разрывать группу на два запроса). Это сильно упростит получение данных в будущем.
Так как всратый модуль STRING хочет от нас значения то в регистрах, то в байтах, я нарисовал для нас таблички, где всё подсчитал. Байты я считаю с единицы, чтобы мы могли правильно вписать их количество в штуках.
Привязка байтов STRING к карте регистров модуля .8А (8 аналоговых входов)
Итого вот что получится:
- Первый запрос: Регистры с 0 по 35 (36 штук), всего 36 x 2 = 72 байта;
- Второй запрос: Регистры с 36 по 47 (12 штук), всего 12 x 2 = 24 байта.
А вот и эти два запроса в конфигурации ПЛК. Мы превратили 16 запросов всего в два длинных (в комментариях к ним я написал свои подсчёты, чтобы потом не запутаться):
Конфигурация STRING-модулей для чтения регистров модуля .8А (8 аналоговых входов)
Настраиваем первый запрос, указывая нужные параметры. Выбираем команду 0x03 (Read Holding Registers), указываем начальный адрес равный 0 и число байт равным 72:
Конфигурация STRING-модулей для чтения регистров модуля .8А (первый запрос)
Настраиваем второй запрос. Снова команда 0x03 (Read Holding Registers), начальный адрес 36 и число байт 24.
Конфигурация STRING-модулей для чтения регистров модуля .8А (второй запрос)
После этого ПЛК будет читать все данные быстро и с меньшим числом запросов!
Теперь посмотрим, как сделать групповой запрос на запись. Будем записывать сразу все 6 выходов модуля МУ110-224.6У (6 выходов 0..10V).
В карте регистров эти выходы занимают регистры от 0 до 5 (всего 6 штук — по числу выходов):
Карта регистров модуля .6У (6 аналоговых выходов)
Вот так они будут считаться по байтам (6 регистров = 12 байт):
Привязка байтов STRING к карте регистров модуля .6У (6 аналоговых выходов)
Поэтому мы добавляем String output module, задаём ему команду 0x10 (Write Multiple Registers), начальный регистр равный 0 и число байт равным 12:
Конфигурация STRING-модулей для записи регистров модуля .6У (6 аналоговых выходов)
А вот пример на чтение данных из модуля МЭ110-224.1М (измерение параметров электросети для 1 фазы). Здесь я в 2011 году взял значения в формате DWORD и положения их десятичных точек. Если пролистать карту регистров дальше, то там будут значения в формате FLOAT, что ещё больше сократит число запросов.
В карте модуля нужные мне значения начинаются с регистра 24 и идут до регистра 44 (всего 21 регистр):
Карта регистров модуля .1М (измерение параметров электросети)
Снова рисуем табличку и считаем байты и регистры:
Привязка байтов STRING к карте регистров модуля .1М (измерение параметров электросети)
И снова настраиваем модуль на команду 0x03 (Read Holding Registers), указываем начальный адрес равный 24 и число байт равным 42:
Конфигурация STRING-модулей для чтения регистров модуля .1М (измерение параметров электросети)
3. Извлечение данных из STRING[79] в наши переменные.
Как я уже говорил, технически эта строка представляет собой не настоящую строку, а массив байт, в которых будут находиться наши данные. И эти данные будут лежать подряд друг за другом.
Напоминаю вам мою картиночку-пример того, как данные разных переменных хранятся в памяти ПЛК (из поста про переменные и память):
Организация памяти: Распределение переменных в памяти (без выравнивания)
Сейчас я понимаю, что если бы порядок байт в числах WORD и DWORD был бы нужным мне, то я бы мог просто отобразить все эти данные на готовые структуры (почитайте пост про эту технологию). Это ещё больше упростило бы код.
Но в 2021 году, когда я выдумывал этот концепт, я не знал, какой порядок байт был в данных из модулей ввода-вывода ОВЕН, и поэтому решил склеивать их из отдельных байтов вместе. Этот приём пригодится как раз в тех случаях, когда данные идут в непрямом порядке: например, где младшие и старшие байты и/или регистры переставлены.
Я применил приём, которым активно пользовался при программировании для микроконтроллеров или на СИ. Он основан на том, что биты в числах можно двигать влево или вправо для того, чтобы изменить это число. Если двигать по 8 бит, то получится, что мы двигаем сразу весь байт.
К чему я это рассказываю? А вот к чему. В STRING мы получим два числа-байта: 0xC0 и 0xFE, которые идут подряд. Чтобы склеить из них значение WORD, нам надо каким-то образом сделать так, чтобы число 0xC0 сдвинулось влево и стало числом 0xC000. По идее математики рассчитают, на что его надо домножить…
…но есть удобная команда SHL — сдвиг влево на нужное число бит. Она поддерживается CodeSys штатно. Величина сдвига указывается в битах. Поэтому для сдвига старшего байта нам надо записать так: SHL(0xC0, 8).
Тогда весь механизм склейки будет работать так, как показано на рисунке ниже:
Механизм соединения двух байтов в WORD
Точно так же можно склеить число DWORD или FLOAT32 из четырёх байтов:
Механизм соединения четырёх байтов в DWORD / FLOAT32
Вот этим приёмом я и стал пользоваться.
Для хранения данных из модуля МВ110-224.8А я создал структуру для одного канала:
Структура для хранения данных из регистров модуля .8А (8 аналоговых входов)
А потом написал функцию, которая получала кусочек буфера данных STRING и обрабатывала его:
Функция обработки данных из STRING модуля .8А (8 аналоговых входов): Заголовок
Вся обработка состояла из того, что я переводил байты в WORD, потом сдвигал их и склеивал при помощи побитового ИЛИ (OR) в WORD’ы, которые потом превращал в значения регистров.
Функция обработки данных из STRING модуля .8А (8 аналоговых входов): Код
Для данных в формате FLOAT32 я завёл мелкий буфер, куда копировал их побайтно, а потом снова копировал в готовую переменную типа REAL. Если вы присмотритесь к коду, то увидите, что тут порядок байт такой: «3412», а не «1234».
Точно так же я поступил с модулем МЭ110-224.1М. Завёл под него структуру:
Структура для хранения данных из регистров модуля .1М (измерение параметров электросети)
Потом создал функцию обработки:
Функция обработки данных из STRING модуля .1М (измерение параметров электросети): Заголовок
И… составил там пояснения по карте регистров, так как она была для меня более сложная, чем у модуля МВ110-224.8А:
Функция обработки данных из STRING модуля .1М (измерение параметров электросети): Пояснения
А дальше написал код, суть которого в том, что мы склеиваем DWORD из четырёх байтов значения, потом склеиваем WORD для положения десятичной точки, а потом всё это пересчитываем.
" width=
У этого модуля тоже свой порядок байт: «2143», а не «1234». Поэтому я и собирал значения по байтам.
В итоге основная программа примера содержит объявление нужных переменных (экземпляров структур, куда будут поступать значения из модулей):
Программа ПЛК для опроса модулей IO через STRING: Объявления
А в коде программы выполняется вызов моих функций, которые заполняют эти структуры данными. Для того, чтобы каждая функция получила правильную позицию в буфере данных, я вычисляю его адрес при помощи оператора ADR(), прибавляя к нему количество регистров, которые занимает 1 канал, умноженные на номер канала:
Программа ПЛК для опроса модулей IO через STRING: Код вызова функций получения данных
А вот для аналоговых выходов нам нужно будет сделать наоборот: разделить число WORD на два байта и записать их в наш буфер отдельно. Для этого мы маской выделяем старшие или младщие 8 бит (байт) из числа, и потом сдвигаем старшие байты вправо на 8 бит.
Программа ПЛК для опроса модулей IO через STRING: Код выдачи значений на аналоговые выходы
Такие решения работают у меня во многих проектах на CodeSys 2.3 Они сильно упрощают мне конфигурирование ПЛК и ускоряют опрос модулей.
На этом мой мастер-класс по этой теме закончен. Я создал для него пример всех объявлений в виде проекта. Просьба соблюдать все копирайты и ссылаться на этот пост. Его можно скачать здесь. Видимо, это сделают те, кто дочитал до конца:
CS-PLC-AI-STRING-Demo.zip (~27 кб)
Проекту исполнилось 16 лет! Поддержать проект материально, проспонсировать проекты Автора или сделать ему подарок можно на этой странице: "Донаты и Спонсорство, Список Желаний".
0 Отзыв на “Ускоряем CodeSys v2.3: Групповые запросы Modbus через STRING-модули”