Хочу закрыть этот вопрос окончательно.
У OPC Lectus есть возможность добавлять узлы работающие через такие вот ретрансляторы:
Lectus.jpg
А так как он является OPC-сервером, то интегрируется с любой Scada.
Будет полезно новичкам, как пример использования библиотеки UNM.lib и как возможность организации доступа нескольких мастеров в одну RS485-сеть.
Видео испытания кода
ModbusGate.JPG
Код:
PROGRAM PLC_PRG
VAR
Port: DWORD := 255;
State: INT := 0; (* Состояние ретранслятора *)
Addr: BYTE := 1; (* Адрес ретранслятора - задается перед заливкой в ПЛК *)
CRC: WORD; (* Контрольная сумма пакетов *)
Timeout: TIME := T#50ms; (* Таймаут ожидания ответного пакета из нижней сети - задается перед заливкой в ПЛК *)
pBuf: POINTER TO STRING;
pByte: POINTER TO BYTE;
IN: POINTER TO RBDATA; (* Принятый байт *)
TMR: TON; (* Таймер отсчета таймаутов *)
HeadTCP: ARRAY[1..4] OF BYTE; (* Заголовок ModbusTCP *)
Buf: ARRAY [1..128] OF BYTE; (* Буфер *)
Item: INT;
Count: WORD;
END_VAR
CASE State OF
0: (* Выполняем захват всех UNM-устройств *)
IF LockDevice(0) = 1 AND LockDevice(1) = 1 AND LockDevice(2) = 1 THEN
Count := 0; State := 1;
END_IF
1: (* Прием пакета с верхней сети *)
IF Port = 255 THEN (* Пока ждем кто начнет первым *)
IN := GetByte(0); (* Слушаем TCP *)
IF IN > 0 THEN
Port := 0; Count := 1; Buf[Count] := IN^.data; (* Итак TCP был первым *)
ELSE
IN := GetByte(1); (* Слушаем RS232 *)
IF IN > 0 THEN
Port := 1; Count := 1; Buf[Count] := IN^.data; (* Итак RS-232 был первым *)
END_IF
END_IF
ELSE (* Здесь кто был первым уже известно *)
IN := GetByte(Port); (* Принимаем очередной байт *)
IF IN > 0 THEN
TMR(IN:=FALSE); Count := Count + 1; Buf[Count] := IN^.data; (* И сохраняем в буфер *)
ELSE
IF Count > 0 THEN
TMR(IN := TRUE, PT := T#1ms); (* Здесь желательно установить PT:= 3.5/(Baudrate*10) *)
IF TMR.Q THEN (* Прем пакета завершен *)
TMR(IN:=FALSE);
IF Count >= 5 THEN (* Длина пакета достаточна для анализа *)
CASE Port OF
0: (* Обработка ModbusTCP-пакета *)
FOR CRC := 1 TO 4 DO (* Сохраним заголовок ModbusTCP-пакета *)
HeadTCP[CRC] := Buf[CRC];
END_FOR
pByte := ADR(Buf) + 6; CRC := CalcCRC(pByte, Count - 6); (* Вычислим CRC *)
pByte := ADR(Buf) + Count; pByte^ := WORD_TO_BYTE(CRC MOD 256); (* Добавляем младший байт CRC *)
pByte := pByte + 1; pByte^ := WORD_TO_BYTE(CRC / 256); (* Добавляем старший байт CRC *)
pBuf := ADR(Buf) + 6; SetByte(2, pBuf^, Count - 4); (* Отправляем в порт RS485-1 *)
Count := 0; State := 2; (* Переходим к ожиданию ответа *)
1: (* Обработка ModbusRTU-пакета *)
pByte := ADR(Buf); CRC := CalcCRC(pByte, Count - 2); (* Вычисляем CRC *)
pByte := ADR(Buf) + Count - 2;
IF pByte^ = (CRC MOD 256) THEN (* Проверяем младший байт CRC *)
pBYte := ADR(Buf) + Count - 1;
IF pByte^ = (CRC / 256) THEN (* Проверяем старший байт CRC *)
pByte := ADR(Buf);
IF pByte^ = Addr THEN (* Проверяем адрес ретранслятора *)
pBuf := ADR(Buf) + 1; SetByte(2, pBuf^, Count - 3); (* Отправляем в RS485 встроенный пакет *)
Count := 0; State := 2; (* Переходим к ожиданию ответа *)
ELSE
Count := 0; Port := 255; (* Пакет не нам - ждем следующий *)
END_IF
ELSE
Count :=0; Port := 255; (* Неверный CRC - ждем следующий *)
END_IF
ELSE
Count := 0; Port := 255; (* Неверный CRC - ждем следующий *)
END_IF
END_CASE
ELSE
Count := 0; Port := 255; (* Пакет слишком маленький - ждем следующий *)
END_IF
END_IF (* Ожидаем окончание приема *)
END_IF (* Ожидаем входной пакета *)
END_IF (* Ожидаем очередной байт *)
END_IF (* Ожидаем первый байт для определения первого UNM *)
2: (* Ожидание ответного пакета из нижней сети *)
IN := GetByte(2);
IF IN > 0 THEN
TMR(IN := FALSE); Count := Count + 1; Buf[Count] := IN^.data;
ELSE
IF Count = 0 THEN
TMR(IN := TRUE, PT := Timeout);
IF TMR.Q THEN
TMR(IN:=FALSE); Count := 0; Port := 255; State := 1; (* Нет ответа - начинаем все с начала*)
END_IF
ELSE
TMR(IN := TRUE, PT := T#1ms); (* Контроль окончания приема *)
IF TMR.Q THEN (* Окончание приема *)
TMR(IN:=FALSE);
pByte := ADR(Buf); CRC := CalcCRC(pByte, Count - 2);
pByte := ADR(Buf) + Count - 2;
IF pByte^ = (CRC MOD 256) THEN
pByte := ADR(Buf) + Count - 1;
IF pByte^ = (CRC / 256) THEN
CASE Port OF
0: (* Заворачиваем ModbusTCP *)
FOR Item := Count + 7 TO 7 BY -1 DO (* Сдвинем буфер на 6 байт вправо*)
Buf[Item] := Buf[Item - 6];
END_FOR
FOR Item := 1 TO 4 DO (* Вернем заголовок ModbusTCP *)
Buf[Item] := HeadTCP[Item];
END_FOR
pByte := ADR(Buf) + 4; pByte^ := WORD_TO_BYTE((Count -2) / 256); (* Добавим CRC *)
pByte := ADR(Buf) + 5; pByte^ := WORD_TO_BYTE((Count-2) MOD 256);
pBuf := ADR(Buf); SetByte(0, pBuf^, Count + 4); (* Вернем верхней сети 6(Head) + Count - 2(CRC) *)
Count := 0; Port := 255; State := 1; (* И начнем все с начала *)
1: (* Заворачиваем в ModbusRTU *)
FOR Item := Count + 2 TO 2 BY -1 DO (* Сдвигаем буфер на 1 байт вправо *)
Buf[Item] := Buf[Item-1];
END_FOR
pByte := ADR(Buf); pByte^ := Addr; (* Добавляем адрес ретранслятора *)
CRC := CalcCRC(pByte, Count + 1); (* Рассчитаем CRC нового пакета *)
pByte := ADR(Buf) + Count + 1; pByte^ := WORD_TO_BYTE(CRC MOD 256); (* Добавим младший байт CRC *)
pByte := ADR(Buf) + Count + 2; pByte^ := WORD_TO_BYTE(CRC / 256); (* Добавим старший байт CRC *)
pBuf := ADR(Buf); SetByte(1, pBuf^, Count + 3); (* Вернем верхней сети 1(Addr) + Count + 2(CRC) *)
Count := 0; Port := 255; State := 1; (* И начнем все с начала *)
END_CASE
ELSE
Count :=0; Port := 255; State := 1; (* Неверна CRC - все с начала *)
END_IF
ELSE
Count :=0; Port := 255; State := 1; (* Неверна CRC - все с начала *)
END_IF
END_IF (* Ожидание окончания приема *)
END_IF (* Ожидаем очередной байт *)
END_IF (* Ожидаем входной пакет *)
END_CASE
Код:
FUNCTION CalcCRC : WORD
VAR
Cnt: BYTE;
END_VAR
VAR_INPUT
pData: POINTER TO BYTE;
Size: WORD;
END_VAR
(* Вычисление контрольной суммы MODBUS RTU CRC *)
CalcCRC := 16#FFFF;
WHILE Size > 0 DO
CalcCRC := CalcCRC XOR pData^;
FOR Cnt := 0 TO 7 DO
IF CalcCRC.0 = 0 THEN
CalcCRC := SHR(CalcCRC, 1);
ELSE
CalcCRC := SHR(CalcCRC, 1) XOR 16#A001;
END_IF
END_FOR;
pData := pData + 1;
Size := Size - 1;
END_WHILE