commit da1a2a58978f8c77c71b02ce0fccb20f52c388bb Author: sergeyyarkov Date: Fri May 23 16:06:48 2025 +0300 project diff --git a/Im151_mo.zip b/Im151_mo.zip new file mode 100644 index 0000000..deb54d8 Binary files /dev/null and b/Im151_mo.zip differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e7b99b7 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# IM151-ModbusTCP-Multigraph + +Пример чтения данных по протоколу Modbus для прибора Мультиграф Сталь без использования библиотеки Modbus. +Опрос данных осуществлятся за счет следующих функций: `TCON`, `TRCV`, `TSEND` и `TDISCONN`. + +[Ссылка на прибор.](https://tpchel.ru/Multigraf_stal) + +[Карта Modbus регистров](./modbus-register-map.pdf) + +В репозитории находится проект Step7 v5.6 для `IM151-8F PN/DP CPU`. diff --git a/modbus-register-map.pdf b/modbus-register-map.pdf new file mode 100644 index 0000000..0a29028 Binary files /dev/null and b/modbus-register-map.pdf differ diff --git a/read.SCL b/read.SCL new file mode 100644 index 0000000..c39f08e --- /dev/null +++ b/read.SCL @@ -0,0 +1,402 @@ +(* Текущий отправляемый запрос на получение значения по адресу 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 + + + +