2025-05-23 16:06:48 +03:00

403 lines
18 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(* Текущий отправляемый запрос на получение значения по адресу Modbus. *)
TYPE UDT_MbCurrReqData
STRUCT
FnCode : BYTE := B#16#0;
StartAddr : WORD := W#16#0;
QtyRegs : WORD := W#16#0;
END_STRUCT
END_TYPE
(* Структура Modbus TCP пакета. *)
TYPE UDT_MbTCPAduReq
STRUCT
TransactionId : WORD := W#16#1;
ProtocolType : WORD := W#16#0;
DataLength : WORD := W#16#6;
UnitId : BYTE := B#16#0;
FnCode : BYTE := B#16#4;
StartAddr : WORD := W#16#0;
QtyRegs : WORD := W#16#0;
END_STRUCT
END_TYPE
(* Параметры TCP соединения. *)
TYPE TCON_PAR
STRUCT
(* Размер типа `TCON_PAR`. Всегда 64 байт*)
block_length : WORD := W#16#40;
(* Идентификатор соединения. От W#16#1 до W#16#0FFF *)
id : WORD := W#16#1;
(*
* Тип соединения.
* W#16#11 — TCP/IP native
* W#16#12 — ISO on TCP
* W#16#13 — UDP
* W#16#01 — TCP/IP native (режим совместимости)
*
* Примечание:
* TCP (compatibility mode): B#16#01 for CPU 315 or 317 <= FW V2.3
* TCP: B#16#11 for CPU 315 or 317 >= FW V2.4,
* IM 151-8 PN/DP CPU, CPU314C, CPU319, CPU412(H), CPU414(H), CPU416(H), CPU417(H)
* These specifications data may vary depending on the firmware used.
* For additional information, refer to:
* http://support.automation.siemens.com/WW/view/en/24294554
*
*)
connection_type : BYTE := B#16#11;
(*
* Идентификатор типа установления соединения.
* Для TCP и ISO on TCP:
* TRUE: Активная установка соединения (клиент).
* FALSE: Пассивная установка соединения (сервер).
*
* Для UDP — всегда FALSE.
*)
active_est : BOOL := FALSE;
(*
* Идентификатор PN/IE интерфейса. Находится в конфигурации HW как номер слота:
* IM 151-8 PN/DP CPU — B#16#1
* CPU 314C, 315 или 317 — B#16#2
* CPU 319 — B#16#3
* CPU 412(H), 414(H), 416(H) and 417(H) — B#16#5
* CPU в стойке 1 станции H — B#16#15
*
* Примечание:
* In H stations: The S7400 PN CPU in rack 0 communicates via local_device_id = 5 and the
* CPU in rack 1 communicates via local_device_id = 15Hex.
*)
local_device_id : BYTE := B#16#5;
(*
* Размер параметра `local_tsap_id` в байтах:
* Для TCP — 0 или 2 (для активного соединения допустимо только 0).
* Для ISO on TCP — 2 или 16.
* Для UDP — 2.
*)
local_tsap_id_len : BYTE := B#16#2;
(* Параметр не используется и должно присвоено значение 0. *)
rem_subnet_id_len : BYTE := B#16#0;
(* Размер параметра `rem_staddr`:
* Неустановленное соединение — B#16#0
* Установленное — B#16#4
*)
rem_staddr_len : BYTE := B#16#0;
(* Размер параметра `rem_tsap_id`. Активное соединения — 2, пассивное — 0. *)
rem_tsap_id_len : BYTE := B#16#0;
(* Размер параметра `next_staddr`. Для PN интерфейса — B#16#0. *)
next_staddr_len : BYTE := B#16#0;
(* Параметр используется для установки локального порта. В режиме сервера (пассивное соединение).
*
* Для `connection_type` W#16#01:
* local_tsap_id[1] младший байт порта в hex
* local_tsap_id[2] старший байт порта в hex
* local_tsap_id[3-16] B#16#00
*
* Для `connection_type` W#16#11:
* local_tsap_id[1] старший байт порта в hex
* local_tsap_id[2] младший байт порта в hex
* local_tsap_id[3-16] B#16#00
*)
local_tsap_id : ARRAY[1..16] OF BYTE;
(* Параметр не используется и должно присвоено значение 0. *)
rem_subnet_id : ARRAY[1..6] OF BYTE;
(* Массив байтов IP адреса удаленного партнера. IP адрес не назначается для неустановленного соединения.
* Порядок байтов зависит от `connection_type`.
* Пример для IP адреса 192.168.0.1:
* Для `connection_type` — B#16#01
* rem_staddr[1] = B#16#01 (1),
* rem_staddr[2] = B#16#00 (0),
* rem_staddr[3] = B#16#A8 (168),
* rem_staddr[4] = B#16#C0 (192),
* rem_staddr[5-6]= B#16#00 (reserved)
* Для `connection_type` — B#16#11
* rem_staddr[1] = B#16#C0 (192),
* rem_staddr[2] = B#16#A8 (168),
* rem_staddr[3] = B#16#00 (0),
* rem_staddr[4] = B#16#01 (1),
* rem_staddr[5-6]= B#16#00 (reserved)
*)
rem_staddr : ARRAY[1..6] OF BYTE;
(*
* Параметр используется для установки порта партнера. Порядок байтов зависит от `connection_type`. Диапозон зависит от CPU.
*
* Для `connection_type` — B#16#01:
* local_tsap_id[1] младший байт порта в hex
* local_tsap_id[2] старший байт порта в hex
* local_tsap_id[3-16] B#16#00
*
* Для `connection_type` — B#16#11
* local_tsap_id[1] старший байт порта в hex
* local_tsap_id[2] младший байт порта в hex
* local_tsap_id[3-16] B#16#00
*)
rem_tsap_id : ARRAY[1..16] OF BYTE;
(* Этот параметр определяет номер стойки и слота коммуникационного процессора который будет использоваться.
* Если CPU использует встроенный PN интерфейс, значение параметра должно быть 0.
*)
next_staddr : ARRAY[1..6] OF BYTE;
(* Параметр не используется и должно присвоено значение 0. *)
spare : WORD := W#16#0;
END_STRUCT
END_TYPE
(* Блок DB хранения данных о приборе. *)
DATA_BLOCK "multigraphSteelDataDB"
STRUCT
IP : ARRAY[1..6] OF BYTE := B#16#C0, B#16#A8, B#16#0B, B#16#02; // IP адрес: 192.168.11.2
SerialNumber : DWORD; // Серийный номер прибора
DeviceType : WORD; // Тип прибора
SteelTempSquare : REAL; // Измеренная площадка по температуре в град.
CurrDate : DWORD; // Текущая дата прибора
CurrTime : DWORD; // Текущее время прибора
END_STRUCT
BEGIN
END_DATA_BLOCK
FUNCTION_BLOCK "multigraphSteelReadFB"
TITLE = 'multigraphSteelRead'
VERSION: '0.1'
VAR_INPUT
netId : WORD := W#16#1; // Идентификатор сетевого подключения
ip : ARRAY[1..6] OF BYTE;
unitId : BYTE; // Идентификатор подчиненного Modbus устройства, всегда `1`
intervalBit : BOOL; // Чтение данных по запросу
connect : BOOL; // Запрос за подключение к прибору
END_VAR
VAR_OUTPUT
netError : BOOL; // Ошибка соединения
netStatus : WORD; // Статус ошибки подключения
isConnected : BOOL; // Статус подключения
isBusy : BOOL;
rdError : BYTE; // Код ошибки Modbus
END_VAR
CONST
mbRdInputRegsFn := 16#04; // Код функции Modbus на чтение входных регистров
mbHRRegsFn := 16#03; // Код функции Modbus на чтение и запись регистров хранения
mbADULength := 12; // Кол-во байт при отправке TCP пакета
mbMaxResLength := 260; // Кол-во байт в ответе от сервера
mbMaxReqRegsLength := 5; // Максимальное кол-во опрашиваемых адресов
(*
* Modbus адреса прибора.
* Карта регистров: https://tpchel.ru/upload/news/2024/2.556.124%20%D0%9F%D1%80%D0%BE%D1%82%D0%BE%D0%BA%D0%BE%D0%BB%20Modbus_%D0%9C-%D0%A1%D0%A2%D0%90%D0%9B%D0%AC_05.02.2024.pdf
*)
mbDeviceTypeAddr := W#16#0006; // Тип прибора / Формат: U16
mbSerialNumberAddr := W#16#0047; // Серийный номер прибора / Формат: U32
mbSteelTempSquareAddr := W#16#1388; // Измеренная площадка по температуре в град. / Формат: F32
mbCurrDateAddr := W#16#0069; // Текущая дата прибора / Формат: U32 / (1-2й байты - год(старший), 3й байт - месяц, 4й байт - день)
mbCurrTimeAddr := W#16#006B; // Текущее время прибора / Формат: U32 / (1й байты - час (старший), 2й байт - минута, 3й байт - секунда, 4й - не исп.)
END_CONST
VAR
netConnect : TCON;
netDisconn : TDISCON;
netSend : TSEND;
netReceive : TRCV;
netConfig : "TCON_PAR";
onRisingEdgeBit : BOOL;
intervalBitLastScan : BOOL;
isReqJobDone : BOOL;
isReqSent : BOOL := FALSE;
mbReqADU : "UDT_MbTCPAduReq";
mbRawResADU : ARRAY[1..mbMaxResLength] OF BYTE;
mbReqRegisters : ARRAY[1..mbMaxReqRegsLength] OF "UDT_MbCurrReqData"; // Опрашиваемые адреса
mbReqIdx : INT := 1;
END_VAR
VAR_TEMP
i : INT;
END_VAR
BEGIN
isReqJobDone := netConnect.DONE;
isBusy := netConnect.BUSY;
netError := netConnect.ERROR;
netStatus := netConnect.STATUS;
IF NOT isConnected THEN
IF NOT netConnect.BUSY THEN
netDisconn(ID := netId, REQ := intervalBit);
isReqSent := FALSE;
END_IF;
(* ** Конфигурирование параметров TCP *************************************************** *)
netConfig.block_length := W#16#40;
netConfig.connection_type := B#16#11; // TCP
netConfig.id := netId;
netConfig.rem_subnet_id_len := 0;
netConfig.local_tsap_id_len := 0;
netConfig.local_device_id := B#16#01; // Slot ID (X1) / IM 151-8 PN/DP CPU
(* IP адрес прибора *)
netConfig.rem_staddr := ip;
netConfig.rem_staddr_len := B#16#4;
(* Порт прибора *)
netConfig.rem_tsap_id[1] := B#16#01;
netConfig.rem_tsap_id[2] := B#16#F6;
netConfig.rem_tsap_id_len := B#16#02;
(* Клиент *)
netConfig.active_est := TRUE;
(* ** Конфигурирование адресов Modbus прибора ******************************************* *)
mbReqRegisters[1].FnCode := mbRdInputRegsFn;
mbReqRegisters[1].StartAddr := mbDeviceTypeAddr;
mbReqRegisters[1].QtyRegs := W#16#0001;
mbReqRegisters[2].FnCode := mbRdInputRegsFn;
mbReqRegisters[2].StartAddr := mbSerialNumberAddr;
mbReqRegisters[2].QtyRegs := W#16#0002;
mbReqRegisters[3].FnCode := mbRdInputRegsFn;
mbReqRegisters[3].StartAddr := mbSteelTempSquareAddr;
mbReqRegisters[3].QtyRegs := W#16#0002;
mbReqRegisters[4].FnCode := mbHRRegsFn;
mbReqRegisters[4].StartAddr := mbCurrDateAddr;
mbReqRegisters[4].QtyRegs := W#16#0002;
mbReqRegisters[5].FnCode := mbHRRegsFn;
mbReqRegisters[5].StartAddr := mbCurrTimeAddr;
mbReqRegisters[5].QtyRegs := W#16#0002;
netConnect(REQ := connect, ID := netId, CONNECT := netConfig);
(* Инициализация на первый запрос *)
mbReqADU.UnitId := unitId;
mbReqADU.TransactionId := 1;
mbReqIdx := 1;
mbReqADU.FnCode := mbReqRegisters[mbReqIdx].FnCode;
mbReqADU.StartAddr := mbReqRegisters[mbReqIdx].StartAddr;
mbReqADU.QtyRegs := mbReqRegisters[mbReqIdx].QtyRegs;
END_IF;
isConnected := netConnect.STATUS = 0 AND netConnect.ERROR = 0;
onRisingEdgeBit := intervalBit AND NOT intervalBitLastScan;
IF isConnected THEN
(* ** Прием данных *********************************************************************************************************************************************************** *)
(* 1. При получении данных идет проверка на ошибку Modbus и случае ошибки от сервера Modbus `rdError` будет его кодом (https://www.simplymodbus.ca/exceptions.htm).
* 2. Если ошибка отсутствует, то происходит проверка запрошенного адреса с адресом в массиве `mbReqRegisters`, и происходит чтение данных.
* 3. После успешного чтения происходит запрос другого Modbus адреса.
*)
netReceive(ID := netId, EN_R := TRUE, DATA := mbRawResADU, LEN := 0); // данные приходят асинхронно.
IF netReceive.NDR THEN
IF BYTE_TO_BOOL(SHR(IN := mbRawResADU[8], N := 7)) AND 1 THEN
rdError := mbRawResADU[9];
ELSE
rdError := 0;
IF mbReqADU.StartAddr = mbSerialNumberAddr THEN
"multigraphSteelDataDB".SerialNumber := READ_U32_BE(BYTE1 := mbRawResADU[10], BYTE2 := mbRawResADU[11], BYTE3 := mbRawResADU[12], BYTE4 := mbRawResADU[13]);
ELSIF mbReqADU.StartAddr = mbDeviceTypeAddr THEN
"multigraphSteelDataDB".DeviceType := READ_U16_BE(BYTE1 := mbRawResADU[10], BYTE2 := mbRawResADU[11]);
ELSIF mbReqADU.StartAddr = mbSteelTempSquareAddr THEN
"multigraphSteelDataDB".SteelTempSquare := READ_F32(BYTE1 := mbRawResADU[10], BYTE2 := mbRawResADU[11], BYTE3 := mbRawResADU[12], BYTE4 := mbRawResADU[13]);
ELSIF mbReqADU.StartAddr = mbCurrDateAddr THEN
"multigraphSteelDataDB".CurrDate := READ_U32_BE(BYTE1 := mbRawResADU[10], BYTE2 := mbRawResADU[11], BYTE3 := mbRawResADU[12], BYTE4 := mbRawResADU[13]);
ELSIF mbReqADU.StartAddr = mbCurrTimeAddr THEN
"multigraphSteelDataDB".CurrTime := READ_U32_BE(BYTE1 := mbRawResADU[10], BYTE2 := mbRawResADU[11], BYTE3 := mbRawResADU[12], BYTE4 := mbRawResADU[13]);
END_IF;
FOR i := 1 TO mbMaxResLength DO mbRawResADU[i] := 0; END_FOR;
mbReqIdx := mbReqIdx + 1;
IF mbReqIdx > mbMaxReqRegsLength THEN
mbReqIdx := 1;
END_IF;
(* Следующий запрос *)
mbReqADU.FnCode := mbReqRegisters[mbReqIdx].FnCode;
mbReqADU.StartAddr := mbReqRegisters[mbReqIdx].StartAddr;
mbReqADU.QtyRegs := mbReqRegisters[mbReqIdx].QtyRegs;
END_IF;
END_IF;
(* *************************************************************************************************************************************************************************** *)
(* ** Отправка на запрос адреса Modbus *************************************************************************************************************************************** *)
IF netSend.ERROR THEN
isConnected := FALSE;
END_IF;
(*IF onRisingEdgeBit THEN
IF netSend.BUSY THEN
RETURN; // Не отсылать новый запрос если предыдущий еще не отправился
ELSE
IF isReqSent THEN
RETURN; // Не отсылать новый запрос запрос если придыдущий успешно отправлен но данные еще не пришли.
END_IF;
isReqSent := TRUE;
END_IF;
END_IF;*)
netSend(ID := netId, LEN := mbADULength, REQ := onRisingEdgeBit, DATA := mbReqADU);
IF netSend.DONE THEN
isReqSent := FALSE;
mbReqADU.TransactionId := INT_TO_WORD(WORD_TO_INT(mbReqADU.TransactionId) + 1);
END_IF;
intervalBitLastScan := intervalBit;
END_IF;
(* *************************************************************************************************************************************************************************** *)
END_FUNCTION_BLOCK
FUNCTION "READ_U16_BE" : WORD
VAR_INPUT
BYTE1 : BYTE;
BYTE2 : BYTE;
END_VAR
BEGIN
READ_U16_BE := SHL(IN := BYTE_TO_WORD(BYTE1), N := 8) OR BYTE_TO_WORD(BYTE2);
END_FUNCTION
FUNCTION "READ_U32_BE" : DWORD
VAR_INPUT
BYTE1 : BYTE;
BYTE2 : BYTE;
BYTE3 : BYTE;
BYTE4 : BYTE;
END_VAR
BEGIN
READ_U32_BE := SHL(IN := BYTE_TO_DWORD(BYTE1), N := 24) OR SHL(IN := BYTE_TO_DWORD(BYTE2), N := 16) OR SHL(IN := BYTE_TO_DWORD(BYTE3), N := 8) OR BYTE_TO_DWORD(BYTE4);
END_FUNCTION
FUNCTION "READ_F32" : REAL
VAR_INPUT
BYTE1 : BYTE;
BYTE2 : BYTE;
BYTE3 : BYTE;
BYTE4 : BYTE;
END_VAR
BEGIN
READ_F32 := DWORD_TO_REAL(IN := SHL(IN := BYTE_TO_DWORD(BYTE1), N := 24) OR SHL(IN := BYTE_TO_DWORD(BYTE2), N := 16) OR SHL(IN := BYTE_TO_DWORD(BYTE3), N := 8) OR BYTE_TO_DWORD(BYTE4));
END_FUNCTION