Ненадолго станем мастером Modbus RTU на шине RS485, будем читать регистры устройств, например, ряда электросчетчиков или насосов, связанных двумя проводами и контролируемых ПЛК.
Если вам нужно наоборот — побыть подчиненным, выставляя данные наружу, читайте — Сервер Modbus TCP, про адресацию — Взгляд со стороны клиента ModbusTCP.
Для удобства и отсутствия пайки возьмем EL6021, который оснащен клемником, а не D-sub разъемом, хотя это вряд ли имеет значения — все модули организованы одинаково, включая системные, расположенные на "теле" контроллера, такие как
-N031. Для RS232 и RS422 все аналогично, кроме линии связи и электрической части.
Для модулей под разъем (D-sub, 9-pin) характерно:
- RS232, разъем на модуле — "папа" (plug), на кабеле — "мама" (socket).
- RS485 / RS422, разъем на модуле — "мама" (socket), на кабеле — "папа" (plug).
Бекхофф пропагандирует первенство программной части, поэтому начнем от программы. Подробно про линковку переменных и прочие пошаговые прохождения квеста можно узнать из русскоязычной методички —
Modbus_Step-by-step.pdf
Лицензии и библиотеки
Для TwinCAT 2 библиотека поставляется отдельно и за деньги:
TS6255 | TwinCAT PLC Modbus RTU. Результатом будет библиотека TwinCAT —
ModbusRTU.lib.
Библиотека для TwinCAT 3
TF6255 | TC3 Modbus RTU — "Already included in the basis setup", — то есть она уже включена в стандартную поставку, но потребует дальнейшего лицензирования при выводе проекта в "поле". Резюмирую: пока пишете проект или "щупаете" возможности библиотеки — можно не покупать и пользоваться возобновляемой семидневной лицензией. После готовности проекта, заказываете лицензию и активируете ее на постоянной основе.
Буферы данных и PDO
Первое, что необходимо сделать, это связать программу технологического процесса и "железо". Для этого объявляется ФБ типа:
VAR
mb : ModbusRtuMasterV2_KL6x22B;
END_VAR
Тип ФБ выбирается, исходя из размера буфера данных модуля связи:
- ModbusRtuMasterV2_KL6x22B — с буфером в 22 байта.
- ModbusRtuMasterV2_KL6x5B — с буфером в 5 байт.
- ModbusRtuMasterV2_PcCOM — с буфером в 64 байта, рассчитано на системный порт контроллера.
- ModbusRtuMasterV2 — для использования только внутри библиотеки, но можно попробовать задействовать как универсальный ФБ для буферов произвольных размеров (буферы адресуются через указатели pComIn и pComOut).
Размер буфера данных можно изменять в разделе PDO, но не нужно — это востребовано только при портировании старых проектов. Поэтому часть наборов помечено как
Legacy — это наследие старых систем, а часть как
Standart — это нормальные, современные возможности по трансляции данных в реальном времени.
Для примера, возьмем
EL6022 (EL6021). Справочная система говорит, что этот модуль содержит:
Data buffer 864 bytes receive buffer, 128 bytes transmit buffer
Bit width in the process image 22 x 8 bit input, 22 x 8 bit output, 16 bit control, 16 bit status
Это означает, что на борту модуля есть аппаратный буфер на прием и отправку данных (Data buffer 864 bytes...), часть аппаратного буфера отражается (или мапится) в PDO-образы TwinCAT (process image 22 x 8 bit input...).
В случае с так называемым системным портом, например, модули
-N031 или порт панели
P205|CP6606-..., с буферами немного сложнее: необходимо вручную добавить последовательный порт
Miscellaneous → Serial Communication Port в устройства ввода-вывода
I/O Devices. После этого, по умолчанию, будет включен режим совместимости с коплерами
BK8xx0 Mode. Нам же необходим режим совместимости с
KL6xx1 Mode (Emulation). KL-EL — без разницы: разная шина — одинаковый принцип работы.
В режиме совместимости с KL6xx1 мы работаем с системным портом, как с модулем расширения EL6022, где
Data Bytes — это размер PDO области, а
Buffer Size — как бы аппаратный буфер. но не обязательно по настоящему аппаратный — он может эмулироваться операционной системой. Размер в
Data Bytes будет влиять на тип структуры данных для линковки с ПЛК-программой. Для стандартных 64-байт подойдет
ModbusRtuMasterV2_PcCOM. Как такового, требования не существует, но пусть аппаратный буфер будет как минимум в два раза больше буфера PDO. Помните, что все это не точно и требует плотного тестирования на вашей конкретной системе.
Модули EL60xx умеют становиться виртуальными COM-портами операционной системы: соседняя вкладка EL60xx → Virtual Com Port. После установки драйвера и активирования конфигурации, Windows обнаружит новый последовательный порт, к которому можно будет подключить произвольное внешнее устройство, не имеющее никакого отношения к TwinCAT (сканер штрих кодов, например).
После линковки Input/Output (COM TxPDO-Map Inputs и COM RxPDO-Map Outputs) частей со структурами типа:
TYPE MB_KL6inData22B
STRUCT
Status: WORD;
D: ARRAY [0..21] OF BYTE;
END_STRUCT
END_TYPE
...которые различаются только размером буфера, дальнейшая работа целиком ведется через экшены функционального блока. В TwinCAT 3 библиотека по прежнему работает через экшены: модных классов и методов, к сожалению, не завезли.
Экшены
ReadCoils — функция модбас #1, считать дискретный выход подчиненного (Read Coils), результат будет упакован (8 бит в байте).
ReadInputStatus — #2, прочитать дискретный вход подчиненного (Read Input Status), результат будет упакован (8 бит в байте).
ReadRegs — #3, прочитать регистр хранения (Read Holding Registers).
ReadInputRegs — #4, прочитать регистр ввода (Read Input Registers).
WriteSingleCoil — #5, записать (задать) состояние дискретного выхода подчиненного (Write Single Coil), данные должны быть упакованы (8 бит в байте).
WriteSingleRegister — #6, записать в регистр хранения (Write SingleRegister).
WriteMultipleCoils — #15, записать в несколько дискретных выходов подчиненного (Write Multiple Coils), данные должны быть упакованы (8 бит в байте).
WriteRegs — #16, записать в несколько регистров подчиненного (Preset Multiple Registers).
Diagnostics — #8, диагностика (Diagnostics).
Диагностика
Поле Error в
ModbusRtuMasterV2_KL6x22B сигнализирует об ошибке, а код ошибки содержится в поле ErrorId. Заниматься диагностикой удобно где-нибудь за пределами приема-передачи данных, заодно подсчитывая статистику пакетов.
VAR
LastError : MODBUS_ERRORS;
CountError : DINT;
CountSuccess : DINT;
Error : BOOL;
trigBusy : F_TRIG;
END_VAR
// обмен данными начинается
// [...]
// обмен данными заканчивается
END_CASE
trigBusy(CLK := mb.BUSY );
IF trigBusy.Q THEN
IF mb.Error THEN
Error := TRUE;
LastError := mb.ErrorId; // Запоминаем код последней ошибки
CountError := CountError + 1; // Подсчитываем количество ошибок
ELSE
Error := FALSE;
CountSuccess := CountSuccess + 1; // Количество удачных пакетов
END_IF
END_IF
Список всех ошибок есть в статье
Modbus RTU Error Codes. Все названия констант "говорящие" и вполне понятные.
Практика
Для обмена данными понадобятся как минимум два буфера, которые могут быть как массивами, так и структурами. Можно читать пачки регистров в структуры, но не забывайте — Modbus оперирует регистрами размером в "слово" (
WORD, uint, sint, int).
VAR
ReadData : ARRAY [1..10] OF WORD; // массив из данных десяти регистров
WriteData : ARRAY [1..10] OF WORD; // массив данных для десяти регистров
END_VAR
VAR CONSTANT
WORDS_TO_READ : // [...] сколько слов (WORD) прочитать
WORDS_TO_WRITE : // [...] сколько слов (WORD) отправить
END_VAR
Сбрасываем блоки перед применением, и после применения тоже сбрасываем:
CASE state OF
INIT_STATE:
mb.ReadRegs(Execute := FALSE);
mb.WriteRegs(Execute := FALSE);
Читаем:
READ_STATE:
mb.ReadRegs(
UnitID := 1, // адрес подчиненного, начинается с 1
Quantity := WORDS_TO_READ, // сколько слов (WORD) прочитать
MBAddr := 0, // адрес модбас, может отсчитываться как от нуля,
// так и от единицы, читайте документацию на подчиненного
cbLength := WORDS_TO_READ * 2, // сколько прочитать, но уже в байтах
pMemoryAddr := ADR(ReadData), // локальный буфер куда будут записаны прочитанные регистры
Execute := TRUE,
Timeout := T#5s // таймаут на выполнение операции
);
IF NOT mb.BUSY THEN
mb.ReadRegs(Execute := FALSE);
IF mb.Error THEN
state := INIT_STATE; // обрабатываем ошибки
ELSE
state := // [...] // продолжаем работу
END_IF
END_IF
Пишем:
WRITE_STATE:
mb.WriteRegs(
UnitID := 1,
Quantity := WORDS_TO_WRITE, // сколько слов (WORD) записать
MBAddr := 0,
cbLength := WORDS_TO_WRITE * 2, // сколько записать, но уже в байтах
pMemoryAddr := ADR(WriteData), // локальный буфер данные которого
// будут записаны в регистры подчиненного
Execute := TRUE,
Timeout := T#5s // таймаут на выполнение операции
);
IF NOT mb.BUSY THEN
mb.WriteRegs(Execute := FALSE);
IF mb.Error THEN
state := INIT_STATE; // обрабатываем ошибки
ELSE
state := // [...] // продолжаем работу
END_IF
END_IF
И не забывайте про время обработки цикла ПЛК-задачи: его продолжительность должна быть достаточной для вычитывания буферов, иначе начнет происходить периодическое переполнение и вы будете терять актуальную информацию.