Версия 1.3.2 для «Beremiz (smart) v1.5.0»
Создадим программу, генерирующую ШИМ-сигнал на основе входного аналогового значения.
Создайте новый проект:
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, его менять не нужно.
Для задачи нужны 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 |
Выше вы добавили лишь описание уставок, теперь необходимо добавить переменные для них, а также другие переменные. Для этого перейдите в «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
.
Добавьте ещё три переменные и одну константу. Все переменные при старте контроллера инициализируются нулём, если не задано исходное значение. Для константы 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 | Константа |
Сначала нужно пропорционально перевести значение входного аналогового сигнала 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;
Перед тем как создать меню пользовательского интерфейса, соберём проект. Это нужно, чтобы 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.
Вы можете загрузить файл программы smart.bin
в контроллер двумя способами.
Запишите файл на флеш-накопитель, вставьте его в USB-порт контроллера СМАРТ и перезагрузите контроллер с зажатыми клавишами «влево» ◄ и «вправо» ►.
На экране контроллера отобразится следующая информация (детали могут отличаться в зависимости от версии загрузчика):
bootloader v0.6
001 search smart.bin
found
loading: 100%
Затем программа загрузится, после чего контроллер автоматически перезагрузится.
Подключите контроллер к сети 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».
Для загрузки программы выполните в командной строке (находясь в папке 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
В версии 1.5.0 добавлена возможность загружать программу прямо из среды Beremiz.
Задайте IP-адрес контроллера и перезагрузите его, как указано в предыдущем разделе.
Укажите IP-адрес в свойствах проекта. Для этого щёлкните дважды по названию или иконке проекта, перейдите на вкладку «Конфигурация» и введите IP-адрес контроллера:
Сохраните проект.
Нажмите кнопку «Подключиться к целевому ПЛК» .
Затем нажмите «Передать на ПЛК» .
При успешном соединении ПО будет загружено на контроллер:
Рассмотрим элементы проекта, относящиеся к контроллерам СМАРТ. Для примеров будем использовать тот же тестовый проект, который создали в главе 1.
К контроллеру могут быть подключены модули СМАРТ по интерфейсу 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.
Когда все переключатели выключены (находятся в положении «вверх»), то у модуля установлен адрес по умолчанию 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 | Другая ошибка (например, несоответствие типа модуля) |
В первой главе вы уже использовали уставки. Для удобства уставки можно распределять по нескольким секциям.
Значения по умолчанию для переменных, связанных с уставками, устанавливаются в следующих случаях:
В библиотеке в разделе «SMART POU» есть 4 функции: SETTINGS_GETF
, SETTINGS_SETF
, SETTINGS_GETI
, SETTINGS_SETI
. Вы можете не добавлять переменные для уставок, как описано в разделе 1.3, а воспользоваться этими функциями. Первый параметр функции это номер секции уставок, второй — номер самой уставки. В нашем случае:
SETTINGS_GETF(0, 0)
для sMin
;SETTINGS_GETF(0, 1)
для sMax
;SETTINGS_GETF(0, 2)
для sPeriod
;SETTINGS_GETI(0, 3)
для sInverse
.Примечание. В будущих версиях Beremiz способ работы с уставками может быть изменён.
В контроллерах СМАРТ журнал используется для периодического и/или событийного сохранения значений переменных.
Добавьте к проекту элемент «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;
В разделе «Интерфейс пользователя» были освещены практически все моменты по созданию пользовательского интерфейса. Рассмотрим оставшиеся типы элементов меню.
Простой вывод строки (поле «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»
.
В этом блоке вы можете создавать функции на языке C и затем использовать их в программах/функциях/функциональных блоках на языке ST. Пример использования рассмотрен в разделе «Функции».
Контроллеры СМАРТ поддерживают протокол Modbus. В Beremiz реализованы протоколы Modbus RTU Server (slave) и Modbus RTU Client (master) для интерфейсов RS-232 и RS-485, а также Modbus TCP Server (slave) для Ethernet (см. изображение).
Добавьте для проекта элемент «SMART Modbus RTU Server», укажите порт контроллера, по которому с верхнего уровня вы будете к нему обращаться, а также скорость и адрес устройства.
Если вы хотите обращаться к уставкам и журнальным переменным, то отметьте соответствующие пункты «Доступ к области настроек» и «Доступ к области журнала». На верхнем уровне журнальные переменные будут доступны, начиная с адреса 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».
Задайте адрес, по которому эта область будет доступна с верхнего уровня, количество переменных и их тип («AccessType»). Переменные типов INT, UINT, WORD
будут доступны по адресам «Address» + 0, «Address» + 1, «Address» + 2 и т. д., переменные типов DINT, UDINT, DWORD, REAL
— по адресам «Address» + 0, «Address» + 2, «Address» + 4 и т. д.
Чтобы иметь в программе доступ к этим областям, определите в ней переменные и задайте для них соответствующий адрес класса «Память» (как в предыдущем разделе).
Примечание. Так же, как и для журнала, сейчас для Modbus-регистров приходится создавать переменные в основной программе. В будущих версиях Beremiz этот подход будет изменён.
Добавьте элемент «SMART Modbus RTU Client», выберите порт, по которому вы будете обращаться к ведомым устройствам и скорость доступа. Остальные параметры можно оставить по умолчанию.
Modbus-клиент читает регистры хранения («Holding Registers») и входные регистры («Input Registers»); записывает регистры хранения («Holding Registers»).
Нажмите правой кнопкой мыши на только что добавленном элементе
«6.x: smart_modbus_client_0» и выберите нужный тип регистров.
Все три типа имеют одинаковый набор параметров:
WORD, INT, UINT
являются 16-битными, адреса кратны 1, остальные являются 32-битными, адреса кратны 2);Чтобы иметь в программе доступ к этим областям, определите в ней переменные и задайте для них соответствующий адрес класса «Память» (так же, как и в разделе «SMART Modbus RTU Server»).
Для каждого блока регистров существуют 4 специальных входных сигнала: status типа BOOL
, last_error типа USINT
, counter_ok типа UDINT
и counter_error также типа UDINT
.
Сигнал status принимает значение TRUE
, если производится обмен данными, FALSE
, если нет.
Сигнал last_error детализирует ошибку:
Значение | Ошибка |
---|---|
0 | Нет ошибки |
1 | Таймаут (неверный адрес или неверная скорость) |
2 | Ошибка CRC |
3 | Другая ошибка |
Переменные counter_ok и counter_error — счётчики успешных и неуспешных операций обмена данными соответственно.
Также для каждого блока существует выходной сигнал enable типа BOOL
, который позволяет включать и выключать обмен данными.
Для элемента «SMART Modbus TCP Server» справедливо то же описание, что и для «SMART Modbus RTU Server>. только здесь вместо интерфейса и скорости вы указываете номер порта (по стандарту 502).
IP-адрес задаётся на контроллере. Для этого зайдите в меню контроллера «Настройки» → «Сеть» → «Ethernet», установите IP-адрес в вашей подсети, а также маску и шлюз. Настройки применятся после перезагрузки.
В контроллерах СМАРТ реализован небольшой RESTful-сервер, поддерживающий запрос GET для получения значений переменных.
Добавьте элемент «SMART HTTP Server» в проект и задайте номер порта (по умолчанию 5000). Так же, как и для Modbus-серверов, доступны флаги «Маршрут для области настроек» и «Маршрут для области журнала», разрешающие чтение значений уставок и переменных журнала.
Кроме этого, можно добавить один или несколько путей («routes») со списком переменных. Например, если вы не хотите разрешать доступ ко всем журнальным переменным, а только к определённым, то нажмите правой кнопкой мыши на «7.x: smart_http_server» и выберите «Добавить route». Название пути будет использоваться в HTTP-запросе для получения значений указанных в нём переменных. Для примера добавьте две переменные ain0
и dutyCycle
типа REAL
.
Аналогично действиям для уставок, журнала и 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
.
Разберём остальные элементы стандарта, то, как они представлены в Beremiz.
Добавьте новый тип данных в дереве проекта (пункт «Типы данных»). В выпадающем списке «Механизм создания типа» задайте один из 5 вариантов:
Просто задаётся синоним для одного из базовых типов. В языке C это аналогично
typedef <base_type> <synonym>;
Несмотря на возможность задавать диапазон значений для целочисленных типов, выход за пределы диапазона в этой версии Beremiz не контролируется. Таким образом, сейчас «Поддиапазон» ничем не отличается от «Синонима».
Переименуйте тип данных в «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, при наличии в проекте перечислимых типов с одинаковыми значениями, не получится задать такое исходное значение ни для типа в целом, ни для переменной в частности. Эта недоработка будет исправлена в дальнейшем.
Для массива нужно выбрать базовый тип и задать диапазон для каждой из размерностей в виде <минимальный индекс>..<максимальный индекс>
. На рисунке представлен тип двумерного массива с названием Array0
, для первой размерности указан диапазон 0..2
, для второй — 1..10
.
Пусть a
— переменная типа Array0
. Обращение к элементам массива в ST-коде:
a[0, 10] := 16#ff; (* Присвоить элементу [0, 10] значение 255 *)
Для следующего раздела вам потребуется создать структуру. Для этого добавьте элемент «Тип данных», выберите «Механизм создания типа» — «Структура» и добавьте шесть элементов типа DINT, соблюдая приведённый порядок.
# | Имя | Тип |
---|---|---|
1 | DAY | DINT |
2 | MONTH | DINT |
3 | YEAR | DINT |
4 | HOUR | DINT |
5 | MINUTE | DINT |
6 | SECOND | DINT |
Назовите структуру DATETIME
.
Как было сказано в разделе «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()
возвращает текущее время часов реального времени контроллера (меню контроллера «Настройки» → «Дата и время»).
Теперь создайте элемент «Функция» на языке 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.
Для проверки работы функции можно вывести текущее время на дисплей контроллера. Добавьте переменную 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.».
Для примера использования функционального блока изменим условие записи переменных в журнал, используемое в главе 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;
Теперь добавьте две переменные в основную программу:
Имя | Класс | Тип |
---|---|---|
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;
Теперь журнал будет сохраняться при смене реального часа.
Задачи можно выполнять не только циклически, как «main_program», но и по прерыванию. Например, пусть требуется сохранять журнал при выходе из строя входного аналогового сигнала ain0
.
Добавьте ещё одну программу, назовите её «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
будет сбрасываться.
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>
Программа из главы 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 |
ST
value := MAP(ain0, 4.0, 20.0, sMin, sMax);
FBD
ST
IF sMin < sMax THEN
dutyCycle := (CONSTRAIN(value, sMin, sMax) - sMin) / (sMax - sMin) * 100.0;
ELSE
dutyCycle := 0.0;
END_IF;
FBD
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
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
Стандартные тип DT
и блок RTC
позволяют замерять интервалы не более 5 суток. Для замера более длительных интервалов, вплоть до 68 лет, в Beremiz Smart введён новый тип 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
в SMARTDT
.
Пример:
VAR
SDT : SMARTDT;
END_VAR
SDT := DT_TO_SMARTDT(DT#2023-01-01-00:00:00.500);
Тип 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 *)
Формирует 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);
Возвращает текущую дату и время с часов реального времени контроллера.
Пример:
VAR
CURRENT_SDT : SMARTDT;
CURRENT_RTC: GET_CURRENT_SMARTDT;
END_VAR
CURRENT_RTC(SDT => CURRENT_SDT);
Вычисляет разницу между двумя переменными типа SMARTDT
. Максимальный интервал ≈ 68 лет.
Пример:
VAR
DAYS: TIME;
END_VAR
DAYS := SUB_SMARTDT_SMARTDT(GET_SMARTDT(2023, 02, 28),
GET_SMARTDT(2021, 02, 28));
(* DAYS = TIME#730D *)
Прибавляет интервал времени 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 *)
Вычитает интервал времени TIME
из переменной типа SMARTDT
. Интервал может быть отрицательным.
SUB_SMARTDT_TIME(SMARTDT:IN1, TIME:IN2) :=
ADD_SMARTDT_TIME(IN1, MULTIME(IN2, -1));
Таймер отсчёта времени с точностью до миллисекунд. Максимальное время ≈ 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 документации). |
Для вывода прямо представленных целочисленных переменных и битов нужно выбирать, соответственно, «ITEM_TYPE_INT_P» и «ITEM_TYPE_BIT_P».↩︎