(* Текущий отправляемый запрос на получение значения по адресу 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