Разработка на платформе Beremiz Smart

ООО «Миконт»

ООО «Миконт»

Версия 1.3.2 для «Beremiz (smart) v1.5.0»

1 Создание тестовой программы

Создадим программу, генерирующую ШИМ-сигнал на основе входного аналогового значения.

1.1 Создание проекта

Создайте новый проект:

Создание нового проекта

Beremiz сразу же предложит создать основную программу. На выбор предлагается один из пяти существующих языков стандарта МЭК 61131-3/IEC 61131-3 (далее в тексте стандарт). Для этого тестового проекта выберите «ST».

Создание основной программы

Выберите в дереве проекта ресурс «resource1». Убедитесь, что задача и экземпляр программы созданы. Время цикла следует выбирать в зависимости от сложности алгоритма задачи и требований по скорости реакции на изменение внешних условий. Минимальное значение равно 10 мс.

Ресурсы проекта

Добавьте собственно контроллер СМАРТ. Для этого нажмите «+» и добавьте шину ввода/вывода «SMART IO Bus». Выберите тип порта «UART (Internal)», скорость оставьте 115200.

Создание шины ввода/вывода

Щёлкните правой кнопкой мыши на элементе «0.x: smart_io_0» и выберите контроллер. На данный момент доступны три типа: «smart-b02-4ai4di4do», «smart-b03-8ai4di4do1ao» и «smart-b04-8ai5di2do». Выберите один из контроллеров. Адрес устройства оставьте 33, его менять не нужно.

Добавление контроллера

1.2 Добавление уставок

Для задачи нужны 4 уставки: минимальное и максимальное значение входного параметра (в условных единицах измерения), период ШИМ-сигнала (в секундах) и признак инверсии выходного сигнала.

Добавьте настройки «SMART Settings» в дерево проекта и секцию для них (щёлкнув правой кнопкой на «1.x: smart_settings_0»). Добавьте 4 описания уставок.

Название Тип Значение по умолчанию Нижняя граница Верхняя граница
sMin REAL 0.0 0.0 10000.0
sMax REAL 100.0 0.0 10000.0
sPeriod REAL 5.0 1.0 60.0
sInverse DINT 0 0 1
Добавление уставок

1.3 Добавление переменных

Выше вы добавили лишь описание уставок, теперь необходимо добавить переменные для них, а также другие переменные. Для этого перейдите в «main_program» в дереве проекта слева.

Имя Класс Тип Адрес
turnOn Локальный DINT
ain0 Локальный REAL %ID0.0.300
value Локальный REAL
dutyCycle Локальный REAL
sMin Локальный REAL %MD1.0.0
sMax Локальный REAL %MD1.0.1
sPeriod Локальный REAL %MD1.0.2
sInverse Локальный DINT %MD1.0.3
dout0 Локальный BOOL %QX0.0.500
dout Локальный BOOL

Переменная turnOn используется для включения/выключения работы ШИМ-сигнала. Для переменной ain0 укажите адрес входного аналогового сигнала контроллера под номером 0 и задайте тип переменной «Вход».

Выбор входного аналогового сигнала и задание типа переменной «Вход»

Свяжите переменные уставок с их описанием и задайте тип переменной «Память».

Связывание переменных уставок с их описанием и задание типа переменной «Память»

Для переменной dout0 укажите выходной сигнал и задайте тип «Выход».

Выбор выходного дискретного сигнала и задание типа переменной «Выход»

Обозначение адресов описано в стандарте, пункт «6.5.5 Прямо представленные переменные (%)».

В программе вам потребуется засекать время для отсчёта периода ШИМ-сигнала. В стандарте для этого существует функциональный блок RTC. Для функционального блока нужно создать экземпляр. Это можно сделать вручную, добавив переменную типа RTC, а можно просто перетащить блок RTC из библиотеки из раздела «Дополнительные функции» в редактор кода и задать имя экземпляра. Тогда переменная добавится автоматически плюс в редакторе появится заглушка для вызова функционального блока RTC.

Перетаскивание блока RTC в редактор кода

Добавьте ещё три переменные и одну константу. Все переменные при старте контроллера инициализируются нулём, если не задано исходное значение. Для константы startTime можно задать время. Для нашей задачи не важна конкретная дата/время, вы можете задать любое. Если вы ничего не введёте, то будет задана нулевая точка отсчёта DT#1970-01-01-00:00:00. Про литералы времени можно почитать в стандарте, пункт «6.3.4 Литерал продолжительности времени».

Имя Класс Тип Исходное значение Квалификатор
cdt Локальный DT
impulseTime Локальный TIME
timeElapsed Локальный TIME
startTime Локальный DT DT#2023-01-01-00:00:00 Константа
Список переменных

1.4 Ввод программы

Сначала нужно пропорционально перевести значение входного аналогового сигнала ain0, находящееся в пределах [4, 20 мА], в значение value, находящееся в пределах уставок [sMin, sMax]. При этом проконтролировать условие sMin < sMax. Далее необходимо рассчитать значение коэффициента заполнения ШИМ-сигнала dutyCycle в процентах. Функция CONSTRAIN находится в библиотеке в разделе «SMART POU».

IF sMin < sMax THEN
  value := MAP(ain0, 4.0, 20.0, sMin, sMax);
  dutyCycle := (CONSTRAIN(value, sMin, sMax) - sMin) / (sMax - sMin) * 100.0;
ELSE
  value := 0.0;
  dutyCycle := 0.0;
END_IF;

Теперь нужно рассчитать период импульса impulseTime и начать отсчитывать период ШИМ-сигнала sPeriod.

ШИМ-сигнал

Вызов функционального блока RTC0 с IN := true начинает отсчёт времени с момента startTime и записывает текущее время в переменную cdt. Затем надо высчитать timeElapsed, то есть сколько прошло времени с момента startTime. Функция MULTIME умножает время (в нашем случае 1 секунду) на число. Если время превышает установленный пользователем период sPeriod, то таймер сбрасывается и начинает отсчёт заново RTC0(IN := FALSE).

Примечание. Максимальное время, которое позволяет засечь блок RTC, равно приблизительно 5 суток. Для бóльших интервалов используйте тип SMARTDT, см. приложение В.

impulseTime := dutyCycle / 100.0 * sPeriod;

RTC0(IN := TRUE, PDT := startTime, CDT => cdt);
timeElapsed := SUB_DT_DT(IN1 := cdt, IN2 := startTime);
IF timeElapsed > MULTIME(IN1 := TIME#1S, IN2 := sPeriod) THEN
  RTC0(IN := FALSE);
  timeElapsed := TIME#0S;
END_IF;

Далее нужно сравнить пройденное время с рассчитанным периодом импульса и установить промежуточную переменную dout в TRUE, если время ещё не закончилось, и в FALSE в противном случае.

dout := timeElapsed < MULTIME(IN1 := TIME#1S, IN2 := impulseTime);

Инвертируем выходной сигнал, если установлен флаг инверсии.

IF sInverse = 1 THEN
  dout := NOT dout;
END_IF;

Отключаем сигнал, если флаг работы сброшен.

IF (turnOn = 0) THEN
  dout := FALSE;
END_IF;

Наконец, устанавливаем физический выход dout0 в значение перменной dout.

dout0 := dout;

1.5 Интерфейс пользователя

Перед тем как создать меню пользовательского интерфейса, соберём проект. Это нужно, чтобы Beremiz создал файл со списком наших переменных, из которого в дальнейшем мы будем выбирать переменные для отображения в меню.

Собираем проект

Добавим «SMART HMI» в дерево проекта. Сначала добавим комбобокс для выбора инверсии ШИМ-сигнала (щёлкнув правой кнопкой на «2.x: smart_hmi_0»), назовём его «inverse_combo». Добавим для него два варианта: «Нет» и «Да». В качестве объекта выберем уставку sInverse.

Добавляем комбобокс
Выбираем уставку для комбобокса

Теперь добавим меню уставок (также щёлкнув правой кнопкой на «2.x: smart_hmi_0»), назовём его «settings_menu». Введём текст «Настройки» в поле заголовка меню «Menu name». У нас 4 уставки, добавим 4 соответствующих строки.

Формат Тип RW SU Параметр Объект
Мин: %0.1f ITEM_TYPE_SETTING 0 &settings_meta_0[0]
Макс: %0.1f ITEM_TYPE_SETTING 0 &settings_meta_0[1]
Период: %0.1f ITEM_TYPE_SETTING 0 &settings_meta_0[2]
Инверсия: %s ITEM_TYPE_COMBO 0 &inverse_combo
Создаём меню уставок

Формат строк соответствует формату функции printf() на языке Си (здесь на русском).

«RW» означает возможность изменять объект. При установленном флаге «SU» изменение потребует пароля изготовителя. По умолчанию пароль 0000, его можно поменять из главного меню контроллера, пункт «Доступ».

Для минимального и максимального значения, а также для периода выбираем тип «ITEM_TYPE_SETTING»; в качестве объекта выбираем соответствующую уставку в «Settings» → «section0». Для инверсии выбираем тип «ITEM_TYPE_COMBO», а в качестве объекта — «Combobox» → «inverse_combo».

Выбираем комбобокс

Добавляем главное меню объекта, назовём его «object_menu», в поле заголовка введём текст «Меню объекта». Добавим 5 строк.

Формат Тип RW SU Параметр
Включить ITEM_TYPE_CHECK 0
ain0: %0.1f мА ITEM_TYPE_FLOAT_P 0
value: %0.1f ITEM_TYPE_FLOAT 0
К.заполнения: %3.0f %% ITEM_TYPE_FLOAT 0
Настройки… ITEM_TYPE_SUBMENU 0

Для переменных выбираем объекты из списка
«IEC Variables» → «CONFIG.RESOURCE1.INSTANCE0.<название_переменной>». Для преобразованного значения value и коэффициента заполнения выберем тип «ITEM_TYPE_FLOAT». Переменная ain0 прямо представлена в памяти (у неё указан адрес «%ID0.0.300»), поэтому указываем тип с суффиксом «…_P»: «ITEM_TYPE_FLOAT_P» 1.

Для подменю «Настройки…» выбираем тип «ITEM_TYPE_SUBMENU» и объект «Меню» → «settings_menu».

Объект
&RESOURCE1__INSTANCE0.TURNON
&RESOURCE1__INSTANCE0.AIN0
&RESOURCE1__INSTANCE0.VALUE
&RESOURCE1__INSTANCE0.DUTYCYCLE
&settings_menu

Для типа «ITEM_TYPE_CHECK» (чекбокс) нужно выбирать 32-битную целочисленную переменную, в нашем случае turnOn. В качестве параметра указывается номер бита в переменной, который отвечает за этот чекбокс. В данной реализации «Beremiz (smart) v1.2.75-1» нельзя выбирать для чекбокса прямо представленную переменную, то есть переменную, у которой указан адрес. В будущих реализациях это будет исправлено.
Устанавливаем признак «RW».

Нужно не забыть удвоить знак процента % в строке формата коэффициента заполнения, чтобы он отобразился на экране контроллера.

Главное меню объекта

Последним шагом нужно выбрать это меню в качестве стартового. Для этого двойным щелчком мыши выбираем в дереве проекта пункт «2.x: smart_hmi_0» и для «Application_menu» выбираем «object_menu».

Собираем проект Собираем проект.

Теперь в папке нашего проекта в подпапке build находится файл smart.bin, который можно загрузить в контроллер.

В приложении Б эта же программа представлена на языке FBD.

2 Загрузка программы в контроллер

Вы можете загрузить файл программы smart.bin в контроллер двумя способами.

2.1 USB

Запишите файл на флеш-накопитель, вставьте его в USB-порт контроллера СМАРТ и перезагрузите контроллер с зажатыми клавишами «влево»  и «вправо» .

На экране контроллера отобразится следующая информация (детали могут отличаться в зависимости от версии загрузчика):

bootloader v0.6
001 search smart.bin
found
loading: 100%

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

2.2 TFTP через командную строку

Подключите контроллер к сети Ethernet. Зайдите в меню контроллера «Настройки» → «Сеть» → «Ethernet», установите IP-адрес контроллера в вашей подсети, а также маску и шлюз. Перезагрузите контроллер с зажатыми клавишами «влево»  и «вправо» .

На экране контроллера должна отобразиться следующая информация (детали могут отличаться в зависимости от версии загрузчика):

bootloader v0.6
010 search smart.bin
not found
ipv4:<ip-адрес контроллера>
tftpd listen :69
link up

Теперь воспользуйтесь любым клиентом TFTP.

1. Можно использовать TFTP-клиент, входящий в состав Windows 7, 8, 10. По умолчанию он не устанавливается, поэтому для установки откройте диалог «Выполнить», используя сочетание клавиш Win+R, введите команду OptionalFeatures и отметьте пункт «Клиент TFTP».

Установка клиента TFTP

Для загрузки программы выполните в командной строке (находясь в папке build):

tftp.exe -i <ip-адрес контроллера> PUT smart.bin

В случае успешной загрузки будет выведено следующее сообщение (на русскоязычной Windows):

Успешная передача: <размер smart.bin> байт за 2 сек., <количество байт/с>

2. Можно использовать свободно распространяемый (freeware) TFTP-клиент от WinAgents Software Group, также его можно скачать с нашего сайта tftp.exe.

Скопируйте tftp.exe в папку build, например, и выполните в командной строке:

tftp.exe -i -t1 <ip-адрес контроллера> PUT smart.bin

В случае успешной загрузки будет выведено примерно следующее сообщение:

File smart.bin was transferred successfully.
<размер smart.bin> bytes transfered for 3 seconds, 0 bytes/second

2.3 TFTP из IDE Beremiz

В версии 1.5.0 добавлена возможность загружать программу прямо из среды Beremiz.

Задайте IP-адрес контроллера и перезагрузите его, как указано в предыдущем разделе.

Укажите IP-адрес в свойствах проекта. Для этого щёлкните дважды по названию или иконке проекта, перейдите на вкладку «Конфигурация» и введите IP-адрес контроллера:

Конфигурация проекта

Сохраните проект.

Нажмите кнопку «Подключиться к целевому ПЛК» Подключиться к ПЛК.

Затем нажмите «Передать на ПЛК» Передать на ПЛК.

При успешном соединении ПО будет загружено на контроллер:

Загрузка ПО

3 Элементы SMART

Рассмотрим элементы проекта, относящиеся к контроллерам СМАРТ. Для примеров будем использовать тот же тестовый проект, который создали в главе 1.

3.1 SMART IO Bus

К контроллеру могут быть подключены модули СМАРТ по интерфейсу RS-485. Чтобы управлять модулями добавьте (по аналогии с действиями в разделе 1.1) шину «SMART IO Bus» и выберите для неё в качестве последовательного порта «RS485_1» или «RS485_2». Оставьте установленную по умолчанию скорость 115200, модули настроены на эту скорость.

Контроллер СМАРТ

Для текущей версии Beremiz доступны 5 модулей: «smart-b321-16di», «smart-b322-16do», «smart-b323-8di8do» (он также соответствует модулю СМАРТ-323-8DI.4DO.4DR), «smart-b331-8ai2u» и «smart-b332-6ao». Для каждого модуля задайте уникальный адрес от 1 до 128.

Адрес модуля

На самом модуле выставите этот адрес с помощью DIP-переключателей JP2.

Расположение переключателей JP2

Когда все переключатели выключены (находятся в положении «вверх»), то у модуля установлен адрес по умолчанию 33. Если 8-й переключатель включен (находится в положении «вниз»), то оставшиеся 7 переключателей задают адрес в двоичном виде.

В таблице указано соответствие адреса модуля положениям переключателей JP2.

Адрес 1 2 3 4 5 6 7 8
33
1
2
3
4
33
128

У всех модулей на одной шине адреса должны быть уникальны. В то время как у контроллера и модуля адреса могут совпадать, так как адресное пространство у них разное.

У каждого модуля существуют 2 специальных входных сигнала: state типа BOOL и last_error типа DINT.

Специальные входные сигналы

Сигнал state принимает значение TRUE, если есть связь с модулем, FALSE в противном случае. Сигнал last_error детализирует ошибку:

Значение Ошибка
0 Нет ошибки (state = TRUE)
1 Таймаут (неверный адрес модуля или скорость <> 115200)
2 Ошибка CRC
3 Другая ошибка (например, несоответствие типа модуля)

3.2 SMART Settings

В первой главе вы уже использовали уставки. Для удобства уставки можно распределять по нескольким секциям.

Значения по умолчанию для переменных, связанных с уставками, устанавливаются в следующих случаях:

В библиотеке в разделе «SMART POU» есть 4 функции: SETTINGS_GETF, SETTINGS_SETF, SETTINGS_GETI, SETTINGS_SETI. Вы можете не добавлять переменные для уставок, как описано в разделе 1.3, а воспользоваться этими функциями. Первый параметр функции это номер секции уставок, второй — номер самой уставки. В нашем случае:

Примечание. В будущих версиях Beremiz способ работы с уставками может быть изменён.

3.3 SMART Journal

В контроллерах СМАРТ журнал используется для периодического и/или событийного сохранения значений переменных.

Добавьте к проекту элемент «SMART Journal» и для него добавьте 4 переменные.

Имя Тип Обычный Power Restore NetRW
on DINT
ain0 REAL
val REAL
duty REAL
Переменные журнала

В журнал сохраняются только первые 4 символа имени переменной. Имена могут дублироваться и даже отсутствовать. Здесь имена регистрозависимые.

Примечание. Для чтения журналов используется программа Fork. В случаях, когда имена переменных дублируются или отсутствуют, не получится импортировать журнал в базу данных (хотя открыть получится).

Тип переменной может быть только REAL или DINT.

Флаг «Обычный» означает, что значение переменной сохраняется в журнал при вызове функции SAVE_JOURNAL(TRUE). Функция находится в разделе «SMART POU» библиотеки.

Флаг «Power» означает, что значение переменной будет сохранено при отключении питания контроллера. Если в дополнение к этому признаку установить признак «Restore», то при восстановлении питания значение будет восстановлено.

Если переменные журнала доступны по связи с верхнего уровня (см. разделы SMART Modbus RTU Server и Smart Modbus TCP Server), то установка флага «NetRW» разрешает модификацию значения переменной.

Таким образом, установив все флаги для переменной on, вы и сохраняете признак работы задачи в журнал, и автоматически вновь запускате работу ШИМ-сигнала при перезагрузке контроллера, и разрешаете запуск/останов работы с верхнего уровня.

Теперь, как и в случае с уставками, создайте 4 переменные в программе «main_program».

Примечание. В текущей версии Beremiz приходится дублировать журнальные переменные в основной программе. В будущих версиях этот подход будет изменён.

Имя Класс Тип Адрес
jTurnOn Локальный DINT %MD4.0
jAin0 Локальный REAL %MD4.1
jValue Локальный REAL %MD4.2
jDutyCycle Локальный REAL %MD4.3
storeTurnOn Локальный DINT

В качестве адреса выбирайте «smart_journal_0» и соответствующую переменную, выберите класс переменной «Память».

Дополнительная сложность заключается в том, что запуск и останов задачи может быть выполнен как с контроллера (переменная turnOn), так и с верхнего уровня (jTurnOn). Кроме того, в случае перезагрузки контроллера из-за установленного флага «Restore» нужно перезаписывать значение из переменной журнала jTurnOn в переменную turnOn. Для этого нужна ещё одна промежуточная переменная storeTurnOn и следующие условные операторы:

(* Контролируем управление с контроллера *)
IF (storeTurnOn <> turnOn) THEN
  storeTurnOn := turnOn;
  jTurnOn := turnOn;
END_IF;

(* Контролируем управление с верхнего уровня
   и восстановление после перезагрузки контроллера *)
IF (storeTurnOn <> jTurnOn) THEN
  storeTurnOn := jTurnOn;
  turnOn := jTurnOn;
END_IF;

Далее просто скопируйте значения остальных переменных в журнальные переменные.

jAin0 := ain0;
jValue := value;
jDutyCycle := dutyCycle;

Для периодической записи журнала добавьте ещё 2 переменные:

Имя Класс Тип Адрес
RTC1 Локальный RTC
journalTime Локальный DT

Ну и раз в час (TIME#1H), например, записывайте переменные в журнал:

RTC1(IN := TRUE, PDT := startTime, CDT => journalTime);
IF SUB_DT_DT(IN1 := journalTime, IN2 := startTime) >= TIME#1H THEN
  RTC1(IN := FALSE);
  SAVE_JOURNAL(TRUE);
END_IF;
Журналирование переменных

3.4 SMART HMI

В разделе «Интерфейс пользователя» были освещены практически все моменты по созданию пользовательского интерфейса. Рассмотрим оставшиеся типы элементов меню.

Простой вывод строки (поле «Format») без параметров. Максимальная ширина строки, которая входит на экран, равна 21 символу. Строки бо́льшего размера обрезаются. В полях «Object» и «Parameter» задавать ничего не нужно.

Вывод значения бита (0 или 1) 32-битной переменной. В поле «Format» в тексте нужно указать спецификатор формата для вывода целого значения %d или %u. В поле «Object» укажите переменную без прямо указанного адреса (%MD…, %ID…) для «ITEM_TYPE_BIT» или с указанным адресом для «ITEM_TYPE_BIT_P». В поле «Parameter» укажите номер бита от 0 до 31.

Вывод 8 младших битов 32-битной переменной. Как и для «ITEM_TYPE_BIT» в поле «Format» укажите 8 спецификаторов %d. Можно сгруппировать их, например, по четыре: «Биты: %d%d%d%d %d%d%d%d». В поле «Object» укажите переменную без прямо указанного адреса (%MD…, %ID…). Поле «Parameter» не используется.

В одной строке можно вывести до 3-х значений переменных. Для этого вы должны создать массив или структуру минимум из этого количества переменных. Про создание массивов и структур написано в разделе «Типы данных».

Кратко про добавление массива непосредственно в раздел переменных: создайте новую переменную, выберите «Тип» → «Массив» и задайте размерность «0..2» (или «1..3», например). В поле «Object» укажите этот массив. В этой версии Beremiz вы не сможете выбрать массив из списка, поэтому вам придётся ввести его вручную, типа

&RESOURCE1__INSTANCE0.<ИМЯ ПЕРЕМЕННОЙ МАССИВА>.

Обратите внимание, что имя переменной массива вводится в верхнем регистре.

В поле «Parameter» укажите число переменных, которое вы хотите вывести. Число, отличное от 2 или 3 выведет только одно значение.

Например, для вывода трёх значений типа REAL с одной цифрой после точки введите в поле «Format» строку «%5.1f %5.1f %5.1f».

3.5 SMART Native

В этом блоке вы можете создавать функции на языке C и затем использовать их в программах/функциях/функциональных блоках на языке ST. Пример использования рассмотрен в разделе «Функции».

3.6 SMART Modbus RTU Server

Контроллеры СМАРТ поддерживают протокол Modbus. В Beremiz реализованы протоколы Modbus RTU Server (slave) и Modbus RTU Client (master) для интерфейсов RS-232 и RS-485, а также Modbus TCP Server (slave) для Ethernet (см. изображение).

Добавьте для проекта элемент «SMART Modbus RTU Server», укажите порт контроллера, по которому с верхнего уровня вы будете к нему обращаться, а также скорость и адрес устройства.

Сервер Modbus RTU

Если вы хотите обращаться к уставкам и журнальным переменным, то отметьте соответствующие пункты «Доступ к области настроек» и «Доступ к области журнала». На верхнем уровне журнальные переменные будут доступны, начиная с адреса 49000 в том порядке, в котором они описаны в «SMART Journal». Уставки будут доступны, начиная с адреса 50000 для секции 0, 51000 для секции 1, и. т. д.

В Modbus используется 16-битная адресация, в то время как и уставки, и журнальные переменные являются 32-битными. Поэтому адреса для них будут кратны 2, например, определённая в разделе 1.2 уставка sMin (под № 0) будет доступна по адресу 50000, уставка sMax (под № 1) — по адресу 50002, и. т. д.

Вы можете добавить одну или несколько произвольных областей регистров хранения («Holding Registers»). Для этого нажмите правой кнопкой мыши на элементе «5.x: smart_modbus_rtu_server» и выберите «Добавить HoldingRegisters».

Holding Registers

Задайте адрес, по которому эта область будет доступна с верхнего уровня, количество переменных и их тип («AccessType»). Переменные типов INT, UINT, WORD будут доступны по адресам «Address» + 0, «Address» + 1, «Address» + 2 и т. д., переменные типов DINT, UDINT, DWORD, REAL — по адресам «Address» + 0, «Address» + 2, «Address» + 4 и т. д.

Чтобы иметь в программе доступ к этим областям, определите в ней переменные и задайте для них соответствующий адрес класса «Память» (как в предыдущем разделе).

Примечание. Так же, как и для журнала, сейчас для Modbus-регистров приходится создавать переменные в основной программе. В будущих версиях Beremiz этот подход будет изменён.

3.7 SMART Modbus RTU Client

Добавьте элемент «SMART Modbus RTU Client», выберите порт, по которому вы будете обращаться к ведомым устройствам и скорость доступа. Остальные параметры можно оставить по умолчанию.

Клиент Modbus RTU

Modbus-клиент читает регистры хранения («Holding Registers») и входные регистры («Input Registers»); записывает регистры хранения («Holding Registers»).

Нажмите правой кнопкой мыши на только что добавленном элементе
«6.x: smart_modbus_client_0» и выберите нужный тип регистров.

Типы регистров

Все три типа имеют одинаковый набор параметров:

Чтобы иметь в программе доступ к этим областям, определите в ней переменные и задайте для них соответствующий адрес класса «Память» (так же, как и в разделе «SMART Modbus RTU Server»).

Для каждого блока регистров существуют 4 специальных входных сигнала: status типа BOOLlast_error типа USINT, counter_ok типа UDINT и counter_error также типа UDINT.

Специальные входные сигналы

Сигнал status принимает значение TRUE, если производится обмен данными, FALSE, если нет.

Сигнал last_error детализирует ошибку:

Значение Ошибка
0 Нет ошибки
1 Таймаут (неверный адрес или неверная скорость)
2 Ошибка CRC
3 Другая ошибка

Переменные counter_ok и counter_error — счётчики успешных и неуспешных операций обмена данными соответственно.

Также для каждого блока существует выходной сигнал enable типа BOOL, который позволяет включать и выключать обмен данными.

3.8 SMART Modbus TCP Server

Для элемента «SMART Modbus TCP Server» справедливо то же описание, что и для «SMART Modbus RTU Server>. только здесь вместо интерфейса и скорости вы указываете номер порта (по стандарту 502).

IP-адрес задаётся на контроллере. Для этого зайдите в меню контроллера «Настройки» → «Сеть» → «Ethernet», установите IP-адрес в вашей подсети, а также маску и шлюз. Настройки применятся после перезагрузки.

3.9 SMART HTTP Server

В контроллерах СМАРТ реализован небольшой RESTful-сервер, поддерживающий запрос GET для получения значений переменных.

Добавьте элемент «SMART HTTP Server» в проект и задайте номер порта (по умолчанию 5000). Так же, как и для Modbus-серверов, доступны флаги «Маршрут для области настроек» и «Маршрут для области журнала», разрешающие чтение значений уставок и переменных журнала.

Сервер HTTP

Кроме этого, можно добавить один или несколько путей («routes») со списком переменных. Например, если вы не хотите разрешать доступ ко всем журнальным переменным, а только к определённым, то нажмите правой кнопкой мыши на «7.x: smart_http_server» и выберите «Добавить route». Название пути будет использоваться в HTTP-запросе для получения значений указанных в нём переменных. Для примера добавьте две переменные ain0 и dutyCycle типа REAL.

Путь HTTP-сервера

Аналогично действиям для уставок, журнала и Modbus-серверов, добавьте для них соответствующие переменные в «main_program» и задайте адреса.

Имя Класс Тип Адрес
httpAin0 Локальный REAL %MD7.0.0
httpDutyCycle Локальный REAL %MD7.0.1

В программу добавьте следующие 2 строки.

httpAin0 := ain0;
httpDutyCycle := dutyCycle;

Теперь эти две переменные доступны по запросу:

http://<ip-адрес контроллера>:<порт>/<название пути>

Про IP-адрес контроллера написано в предыдущем разделе, для значений по умолчанию запрос будет выглядеть так:

http://192.168.77.100:5000/route_0

Если вы установили признак «JournalRoute», то журнальные переменные доступны по адресу .../vars. При установленном признаке «SettingsRoute» уставки доступны по адресу .../settings/<номер секции>; для секции 0, например, .../settings/0.

Ответ приходит в формате JSON (заголовок HTTP-ответа «Content-Type: application/json»):

[{
    "name": "ain0",
    "value": 8.754606
}, {
    "name": "dutyCycle",
    "value": 29.716290
}]

В приложении А содержится код файлов smart.html и smart.js. На странице smart.html выводятся 4 журнальных значения с обновлением каждые 3 секунды.

Примечание. Подразумевается, что переменные те же, которые вы определили для журнала в разделе о журнале, а для HTTP-сервера отметили флаг «JournalRoute». URL запроса http://192.168.77.100:5000/vars, его можно скорректировать в smart.js.

4 Другие элементы Beremiz

Разберём остальные элементы стандарта, то, как они представлены в Beremiz.

4.1 Типы данных

Добавьте новый тип данных в дереве проекта (пункт «Типы данных»). В выпадающем списке «Механизм создания типа» задайте один из 5 вариантов:

Типы данных

4.1.1 Синоним

Просто задаётся синоним для одного из базовых типов. В языке C это аналогично

typedef <base_type> <synonym>;

4.1.2 Поддиапазон

Несмотря на возможность задавать диапазон значений для целочисленных типов, выход за пределы диапазона в этой версии Beremiz не контролируется. Таким образом, сейчас «Поддиапазон» ничем не отличается от «Синонима».

4.1.3 Перечисление

Переименуйте тип данных в «StatusMessage». Введите несколько значений для перечисления по правилам задания идентификаторов.

Пример перечисления

Примечание 1. В стандарте и, следовательно, в Beremiz имена, ключевые слова и другие идентификаторы регистронезависимые, то есть идентификаторы «name», «Name» и «NAME» являются идентичными.

Примечание 2. Как уже было сказано в главе 1, все переменные инициализируются нулевым значением, если не указано исходное значение. В случае перечислимого типа для переменной будет задано первое в списке значение.

Можно задать общее исходное значение для переменных этого типа. Это исходное значение может быть перекрыто при определении переменной непосредственно в программе.

В ST-коде перечисления используются следующим образом (пусть status — переменная перечислимого типа StatusMessage):

status := None;
...
IF status = Information THEN
...

В случае, когда два или более типа имеют одинаковые значения, то в коде нужно специфицировать значение именем типа:

status := StatusMessage#None;
...
IF status = StatusMessage#Information THEN
...

Опять же, в этой версии Beremiz, при наличии в проекте перечислимых типов с одинаковыми значениями, не получится задать такое исходное значение ни для типа в целом, ни для переменной в частности. Эта недоработка будет исправлена в дальнейшем.

4.1.4 Массив

Для массива нужно выбрать базовый тип и задать диапазон для каждой из размерностей в виде <минимальный индекс>..<максимальный индекс>. На рисунке представлен тип двумерного массива с названием Array0, для первой размерности указан диапазон 0..2, для второй — 1..10.

Пример массива

Пусть a — переменная типа Array0. Обращение к элементам массива в ST-коде:

a[0, 10] := 16#ff; (* Присвоить элементу [0, 10] значение 255 *)

4.1.5 Структура

Для следующего раздела вам потребуется создать структуру. Для этого добавьте элемент «Тип данных», выберите «Механизм создания типа» — «Структура» и добавьте шесть элементов типа DINT, соблюдая приведённый порядок.

# Имя Тип
1 DAY DINT
2 MONTH DINT
3 YEAR DINT
4 HOUR DINT
5 MINUTE DINT
6 SECOND DINT

Назовите структуру DATETIME.

Структура DATETIME

4.2 Функции

4.2.1 SMART Native

Как было сказано в разделе «SMART Native», элемент «SMART Native» позволяет создавать функции на языке C. Создайте элемент «SMART Native» и скопируйте следующий код в пункт «8.x smart_native_0»:

#include "iec_std_lib.h"
#include "smart_api.h"

tm get_current_datetime()
{
    rtc_t rtc;
    rtc_get(&rtc);

    tm result; // Структура в файле iec_std_lib.h.
    result.tm_sec  = rtc.sec;
    result.tm_min  = rtc.min;
    result.tm_hour = rtc.hour;
    result.tm_day  = rtc.day;
    result.tm_mon  = rtc.month;
    result.tm_year = (int) rtc.year + 2000;

    return result;
}

Здесь первой строкой вы подключаете заголовочный файл, который используется в Beremiz (файл находится в <установочная директория Beremiz>/matiec/lib/C).

Второй строкой подключается библиотека проекта SMART (файл находится в <установочная директория Beremiz>/smart2-app/lib/inc). Функция rtc_get() возвращает текущее время часов реального времени контроллера (меню контроллера «Настройки» → «Дата и время»).

SMART Native

4.2.2 Функция NOW

Теперь создайте элемент «Функция» на языке ST.

Создание функции

Выберите возращаемый тип DATETIME. По стандарту у функции должен быть хотя бы один входной параметр. Так как для этой функции параметр не нужен, то просто добавьте параметр любого типа класса «Вход».

В теле ST-функции тоже можно писать код на языке C, обрамляя каждую строку двойными фигурными скобками. Добавьте следующий код в тело функции:

{{ extern tm get_current_datetime(); }}
{{ tm dt = get_current_datetime(); }}
{{ NOW.DAY = dt.tm_day; }}
{{ NOW.MONTH = dt.tm_mon; }}
{{ NOW.YEAR = dt.tm_year; }}
{{ NOW.HOUR = dt.tm_hour; }}
{{ NOW.MINUTE = dt.tm_min; }}
{{ NOW.SECOND = dt.tm_sec; }}
RETURN;

В первой строке объявляем используемую функцию, во второй строке вызываем её. Далее переносим шесть значений из внутренней структуры в возвращаемую переменную типа DATETIME.

Функция NOW

Для проверки работы функции можно вывести текущее время на дисплей контроллера. Добавьте переменную current_time типа DATETIME в «main_program» и строку кода в программу:

current_time := NOW(TRUE);

Воспользуемся тем, что на одной строке меню можно отобразить сразу три значения. Откройте объект «2.2.x object_menu» и добавьте две строки:

Формат Тип Параметр Объект
Date: %02d.%02d.%04d ITEM_TYPE_INT 3 &RESOURCE1__INSTANCE0 .CURRENT_TIME
Time: %02d:%02d:%02d ITEM_TYPE_INT 3 &RESOURCE1__INSTANCE0 .CURRENT_TIME.value.HOUR

Для вывода даты в качестве объекта указана переменная current_time. В этом случае будут использованы три первых поля структуры DATETIME: DAY, MONTH и YEAR. Для вывода времени нужно указать на следующие три поля, начав с поля HOUR. Чтобы это сделать, для поля нужно использовать префикс «value.». Обратите внимание, что названия объектов вводятся в верхнем регистре, за исключением префикса «value.».

4.3 Функциональные блоки

Для примера использования функционального блока изменим условие записи переменных в журнал, используемое в главе 3. Как правило, при анализе журнала удобнее использовать метки времени, выровненные по границе часа, чтобы значения минут и секунд были равны нулю.

Создайте элемент «Функциональный блок» на языке ST, назовите HAS_HOUR_CHANGED. Блок должен сообщать, изменилось ли значение часа в часах реального времени контроллера. Выбор в пользу именно функционального блока, а не функции, обусловлен тем, что нам нужно хранить предыдущее значение часа между вызовами блока.

Создайте три переменные для блока: две внутренние локальные и одну выходную.

Имя Класс Тип Исходное значение
CURRENT_DATETIME Локальный DATETIME
PREVIOUS_HOUR Локальный DINT 24
HAS_CHANGED Выход BOOL

Код функционального блока:

CURRENT_DATETIME := NOW(TRUE);
HAS_CHANGED := CURRENT_DATETIME.HOUR <> PREVIOUS_HOUR;
PREVIOUS_HOUR := CURRENT_DATETIME.HOUR;
RETURN;
Функциональный блок HAS_HOUR_CHANGED

Теперь добавьте две переменные в основную программу:

Имя Класс Тип
hasHourChanged Локальный HAS_HOUR_CHANGED
hourHasChanged Локальный BOOL

Ну и, наконец, закомментируйте предыдущий код…

(* Предыдущая версия *)
(* RTC1(IN := TRUE, PDT := startTime, CDT => journalTime);
IF SUB_DT_DT(IN1 := journalTime, IN2 := startTime) >= TIME#1H THEN
  RTC1(IN := FALSE);
  SAVE_JOURNAL(TRUE);
END_IF; *)

…и введите новый:

(* Новая версия *)
hasHourChanged(HAS_CHANGED => hourHasChanged);
IF hourHasChanged THEN
  SAVE_JOURNAL(TRUE);
END_IF;

Теперь журнал будет сохраняться при смене реального часа.

4.4 Программы

Задачи можно выполнять не только циклически, как «main_program», но и по прерыванию. Например, пусть требуется сохранять журнал при выходе из строя входного аналогового сигнала ain0.

Добавьте ещё одну программу, назовите её «save_program».

Создание программы «save_program»

Добавьте переменную alarm типа BOOL, свяжите её с выходом %QX0.0.501 (пусть это будет лампа «Внимание») и введите следующий код:

SAVE_JOURNAL(TRUE);
alarm := TRUE;

Теперь откройте «resource1» и введите глобальную переменную trigger типа BOOL. Добавьте задачу «task1», задайте тип запуска «Прерывание», а в качестве источника прерывания укажите только что созданную переменную trigger. Создайте экземпляр «instance1» типа «save_program» с задачей «task1».

Задача по прерыванию

Последним шагом добавьте три переменные в «main_program»

Имя Класс Тип Адрес
trigger Внешний BOOL
alarm Локальный BOOL %QX0.0.501
reset Локальный BOOL %IX0.0.100

и соответствующий код:

trigger := ain0 < 3.0 OR ain0 > 22.0;
IF reset THEN
  alarm := FALSE;
END_IF;

Класс переменной trigger «Внешний», так как она определена в «resource1». Взводим trigger в случае выхода ain0 за границы диапазона [3.0—22.0 мА] и сбрасываем, когда значение возвращается в указанные пределы. Программа «save_program» будет вызываться по фронту значения 0 → 1; будет сохраняться журнал и устанавливаться выход alarm.

По входному сигналу reset (кнопка «Снятие сигнализации») выход alarm будет сбрасываться.

Основная программа

Приложение А (проверка HTTP-сервера)

Файл smart.js

async function get_vars() {
    const response = await fetch("http://192.168.77.100:5000/route_0");
    const json = await response.json();

    for (const { name, value } of json) {
        document.getElementById(name).innerHTML = value.toFixed(2);
    }
}

function on_timer() {
    let get_var_promise = Promise.resolve();
    get_var_promise.then(() => get_vars());
}

on_timer();
setInterval(on_timer, 3000);

Файл smart.html

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="utf-8" />
  <title>SMART</title>
  <script src="smart.js"></script>
  <style type="text/css">
    p {
      font-size: 150%;
    }
  </style>
</head>
<body>
  <div>
    <h1><u>ШИМ-сигнал</u></h1>
    <p>ain0: <span id="ain0"
        style="font-family: monospace">0</span> мА</p>
    <p>Коэффициент заполнения: <span id="dutyCycle"
        style="font-family: monospace">0</span> %</p>
  </div>
</body>
</html>

Приложение Б (программа на FBD)

Программа из главы 1 на языке FBD

Список переменных

Имя Класс Тип Адрес
turnOn Локальный DINT
ain0 Локальный REAL %ID0.0.300
value Локальный REAL
dutyCycle Локальный REAL
sMin Локальный REAL %MD1.0.0
sMax Локальный REAL %MD1.0.1
sPeriod Локальный REAL %MD1.0.2
sInverse Локальный DINT %MD1.0.3
timeElapsed Локальный TIME
dout0 Локальный BOOL %QX0.0.500
RTC0 Локальный RTC

Блоки FBD

ST

value := MAP(ain0, 4.0, 20.0, sMin, sMax);

FBD

Расчёт value

ST

IF sMin < sMax THEN
  dutyCycle := (CONSTRAIN(value, sMin, sMax) - sMin) / (sMax - sMin) * 100.0;
ELSE
  dutyCycle := 0.0;
END_IF;

FBD

Расчёт dutyCycle

ST

RTC0(IN := TRUE, PDT := DT#2022-01-01-00:00:00, CDT => cdt);
timeElapsed := SUB_DT_DT(IN1 := cdt, IN2 := DT#2022-01-01-00:00:00);
IF timeElapsed > MULTIME(IN1 := TIME#1S, IN2 := sPeriod) THEN
  RTC0(IN := FALSE);
END_IF;

FBD

RTC0 и расчёт timeElapsed

ST

impulseTime := dutyCycle / 100.0 * sPeriod;
dout := timeElapsed < MULTIME(IN1 := TIME#1S, IN2 := impulseTime);

IF sInverse = 1 THEN
  dout := NOT dout;
END_IF;

IF (turnOn = 0) THEN
  dout := FALSE;
END_IF;

dout0 := dout;

FBD

Вывод dout0

Приложение В (тип SMARTDT)

Стандартные тип DT и блок RTC позволяют замерять интервалы не более 5 суток. Для замера более длительных интервалов, вплоть до 68 лет, в Beremiz Smart введён новый тип SMARTDT и функции работы с ним.

Структура SMARTDT

TYPE
  SMARTDT : STRUCT
    YEAR : UINT; (* Год [1970—2105], *)
                 (* 65535 как признак отрицательного интервала *)
    MONTH : UINT; (* Месяц [1—12] *)
    DAY : UINT; (* День [1—31, 0—24855 для интервальных значений] *)
    HOUR : UINT; (* Час [0—23] *)
    MINUTE : UINT; (* Минута [0—59] *)
    SECOND : UINT; (* Секунда [0—59] *)
    MSEC : UINT; (* Миллисекунда [0—999] *)
  END_STRUCT;
END_TYPE

Функция DT_TO_SMARTDT

Преобразует стандартный тип DT в SMARTDT.

Пример:

VAR
  SDT : SMARTDT;
END_VAR

SDT := DT_TO_SMARTDT(DT#2023-01-01-00:00:00.500);

Функция TIME_TO_SMARTDT

Тип SMARTDT позволяет также хранить интервальные значения. В это случае поле DAY может хранить значения от 0 до 24855 (≈ 68 лет). Функция преобразует стандартный интервальный тип TIME в SMARTDT. В случае отрицательного интервала в поле YEAR записывается значение 65535.

Пример:

VAR
  SDT : SMARTDT;
END_VAR

SDT := TIME_TO_SMARTDT(TIME#365D_12H_30M);
SDT := TIME_TO_SMARTDT(TIME#365D_12H_30M_05S_300MS);
SDT := TIME_TO_SMARTDT(TIME#-730D); (* SDT.DAY = 730, SDT.YEAR = 65535 *)

Функция GET_SMARTDT

Формирует SMARTDT из набора год, месяц, день, час, минута, секунда, миллисекунда.

Пример:

VAR
  SDT : SMARTDT;
END_VAR

SDT := GET_SMARTDT(2023, 02, 28); (* 00:00:00.000 *)
SDT := GET_SMARTDT(2023, 02, 28, 05, 29, 30); (* 0 миллисекунд *)
SDT := GET_SMARTDT(2023, 02, 28, 05, 29, 30, 500);

Функциональный блок GET_CURRENT_SMARTDT

Возвращает текущую дату и время с часов реального времени контроллера.

Пример:

VAR
  CURRENT_SDT : SMARTDT;
  CURRENT_RTC: GET_CURRENT_SMARTDT;
END_VAR

CURRENT_RTC(SDT => CURRENT_SDT);

Функция SUB_SMARTDT_SMARTDT

Вычисляет разницу между двумя переменными типа SMARTDT. Максимальный интервал ≈ 68 лет.

Пример:

VAR
  DAYS: TIME;
END_VAR

DAYS := SUB_SMARTDT_SMARTDT(GET_SMARTDT(2023, 02, 28),
    GET_SMARTDT(2021, 02, 28));
(* DAYS = TIME#730D *)

Функция ADD_SMARTDT_TIME

Прибавляет интервал времени TIME к переменной типа SMARTDT. Интервал может быть отрицательным.

Пример:

VAR
  SDT : SMARTDT;
END_VAR

SDT := ADD_SMARTDT_TIME(DT_TO_SMARTDT(DT#2022-01-01-00:00:00),
    TIME#365D_0H_1M_2S_200MS);
(* SDT.YEAR = 2023, SDT.MONTH = 01, SDT.DAY = 01 *)
(* SDT.HOUR = 00, SDT.MINUTE = 01, SDT.SECOND = 02, SDT.MSEC = 200 *)

SDT := ADD_SMARTDT_TIME(DT_TO_SMARTDT(DT#2022-01-01-00:00:00),
    TIME#-365D_0H_1M_2S_200MS);
(* SDT.YEAR = 2020, SDT.MONTH = 12, SDT.DAY = 31 *)
(* SDT.HOUR = 23, SDT.MINUTE = 58, SDT.SECOND = 57, SDT.MSEC = 800 *)

Функция SUB_SMARTDT_TIME

Вычитает интервал времени TIME из переменной типа SMARTDT. Интервал может быть отрицательным.

SUB_SMARTDT_TIME(SMARTDT:IN1, TIME:IN2) :=
    ADD_SMARTDT_TIME(IN1, MULTIME(IN2, -1));

Функциональный блок SMARTDT_TIMER

Таймер отсчёта времени с точностью до миллисекунд. Максимальное время ≈ 68 лет.

Пример таймера отсчёта трёхсекундных интервалов:

VAR
  ELAPSED: TIME;
  TIMER : SMARTDT_TIMER;
END_VAR

(* Запуск таймера/продолжение отсчёта *)
TIMER(START := TRUE, TIME_ELAPSED => ELAPSED);
IF (ELAPSED >= TIME#3S) THEN
  TIMER(START := FALSE); (* Сброс таймера по истечении 3 секунд *)
END_IF;

Приложение Г (версия и описание ПО)

Для программы вы можете указать версию и краткое описание.

Для этого щёлкните дважды по названию или иконке проекта:

Свойства проекта

Заполните поля «Имя проекта» и «Имя продукта». Укажите последовательно версию проекта, версию продукта и релиз продукта. Общая версия ПО будет сформирована как
«Версия проекта».«Версия продукта».«Релиз продукта».

На вкладке «Автор» заполняются поля

Вкладка «Автор»

На вкладке «Прочее» можно добавить до 8 строк описания содержимого:

Вкладка «Прочее»

Длина всех полей ограничена 18 символами, включая строки описания содержимого.

Версию, имя проекта, имя продукта и название компании можно увидеть в меню «Настройки» → «О системе» → «Прикладное ПО».

Примечание. В контроллере эта информация хранится в кодировке Windows-1251, поэтому набор символов ограничен. Используются только следующие символы:

 
!"#$%&’()*+,-./0123456789:;<=>?@
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`
abcdefghijklmnopqrstuvwxyz{|}~
АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ
абвгдеёжзийклмнопрстуфхцчшщъыьэюя

Список изменений

Версия документации Версия Beremiz (smart) Описание
1.0.2 v1.2.75-8 Первая версия документации.
1.1.0 —″— Добавлено описание типа SMARTDT и функций для работы с ним.
1.1.1 —″— Добавлено описание специальных входных сигналов state и last_error.
1.2.0 v1.3.1 Добавлен «SMART Modbus RTU Client».
Добавлена возможность указывать версию прикладного ПО
и его описание.
1.3.0 v1.4.0 Добавлен тип контроллера СМАРТ-В04.
Версия прикладного ПО и его описание
теперь задаётся прямо в свойствах проекта.
1.3.1 v1.4.1 Изменился способ описания прикладного ПО и его версии (приложение Г).
1.3.2 v1.5.0 Добавлена возможность загрузки ПО на контроллер
прямо из среды Beremiz (раздел 2.3 документации).

  1. Для вывода прямо представленных целочисленных переменных и битов нужно выбирать, соответственно, «ITEM_TYPE_INT_P» и «ITEM_TYPE_BIT_P».↩︎