mirror of
https://github.com/sergeyyarkov/IM151-ModbusTCP-Multigraph.git
synced 2026-10-11 02:09:14 +03:00
403 lines
18 KiB
Plaintext
403 lines
18 KiB
Plaintext
(* Текущий отправляемый запрос на получение значения по адресу 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
|
||
|
||
|
||
|
||
|