Реализация обмена со счетчиком воды Пульсар М по RS485
Для интеграции по RS485 счетчиков Пульсар М существует готовая библиотека для Codesys 3.5. А вот для Codesys 2.3 не нашел, ниже моя реализация. Может кому пригодится.
IF NOT IN THEN
ComService(ENABLE := FALSE, SETTINGS := Settings, TASK := CLOSE_TSK);
STATE := 0;
tonStep(IN:=FALSE, PT:=T#0s);
tonDelay(IN:=FALSE, PT:=T#0s);
END_IF
IF NOT ComService.Ready THEN
RETURN;
END_IF
CASE STATE OF
0:
IF ComService.Ready THEN
STATE := 1;
END_IF
IF STC.Q THEN
ERROR := STC.ERROR;
IF ERROR = 0 THEN
dwTmp := SHL(STC.OUT.BUF[DATA_OFFSET + 0], 0) OR SHL(STC.OUT.BUF[DATA_OFFSET + 1], 8) OR SHL(STC.OUT.BUF[DATA_OFFSET + 2], 16) OR SHL(STC.OUT.BUF[DATA_OFFSET + 3], 24);
prTmp := ADR(dwTmp);
R_VOLTAGE := prTmp^;
mNextStep;
END_IF
END_IF
6:
mNextStepDelay;
(* Чтение температуры *)
7:
cmd := PULSAR_CMD(ADDR := ADDR, FUN := FUN_PARAM, ID:= wReqId, DATA := VALUE_TEMP, DATA_LEN := 2);
STC(in:=IN, DELAY:=REPEAT_DELAY, TIMEOUT:=READ_TIMEOUT, PORT:=ComPort, CMD:=cmd, ANSWER_LEN:=0);
IF STC.Q THEN
ERROR := STC.ERROR;
IF ERROR = 0 THEN
dwTmp := SHL(STC.OUT.BUF[DATA_OFFSET + 0], 0) OR SHL(STC.OUT.BUF[DATA_OFFSET + 1], 8) OR SHL(STC.OUT.BUF[DATA_OFFSET + 2], 16) OR SHL(STC.OUT.BUF[DATA_OFFSET + 3], 24);
prTmp := ADR(dwTmp);
R_TEMP := prTmp^;
mNextStep;
END_IF
END_IF
8:
mNextStepDelay;
(* Обновление даты счетчика *)
9:
(* Пропускаем, если уже обновили ИЛИ нет признака обновления даты*)
IF bTimeIsSet OR NOT UPDATE_DATE THEN
STATE := STATE + 2;
ELSE
IF STC.Q THEN
ERROR := STC.ERROR;
IF ERROR = 0 THEN
R_DT := SET_DT(
year := 2000 + STC.OUT.BUF[DATA_OFFSET + 0],
month := STC.OUT.BUF[DATA_OFFSET + 1],
day := STC.OUT.BUF[DATA_OFFSET + 2],
HOUR := STC.OUT.BUF[DATA_OFFSET + 3],
MINUTE := STC.OUT.BUF[DATA_OFFSET + 4],
SECOND := STC.OUT.BUF[DATA_OFFSET + 5]
);
mNextStep;
END_IF
END_IF
12:
mNextStepDelay;
(* Пауза между пакетами *)
13:
(* Задержка между запросами *)
IF NOT tonDelay.IN THEN
tonDelay(IN:=TRUE, PT:=DELAY);
ELSIF tonDelay.Q THEN
tonDelay(IN:=FALSE, PT:=T#0s);
STATE := STATE +1;
END_IF
(* Возврат в начало цикла *)
ELSE
STATE := 3;
END_CASE
tonDelay();
tonStep();
Модуль mNextStep
Код:
(* Формируем ID запроса инкрементом *)
wReqId := wReqId + 1;
IF wReqId = 16#FFFF THEN
wReqId := 1;
END_IF
STC(IN:=FALSE);
STATE := STATE + 1;
Модуль mNextStepDelay
Код:
(* Задержка между запросами *)
IF NOT tonStep.IN THEN
(* Таймер задержки и следующий шаг *)
tonStep(IN:=TRUE, PT:=STEP_DELAY);
ELSIF tonStep.Q THEN
tonStep(IN:=FALSE, PT:=T#0s);
STATE:=STATE+1;
END_IF
PULSAR_STC
Скрытый текст:
Код:
(* Реализация взаимодействия по COM порту со спецификой Пульсар М *)
FUNCTION_BLOCK PULSAR_STC
(* Входы функционального блока *)
VAR_INPUT
(* Вход разрешающий работу блока *)
IN: BOOL:=FALSE;
(* Указатель на буфер с массивом байт в котором содержится пересылаемая команда *)
CMD: BUFER;
(* Количество байт в ответе - ноль для вычисления из пакета сообщения Пульсар *)
ANSWER_LEN: WORD := 0;
(* Задержка повторного запроса *)
DELAY: TIME := T#500ms;
(* Номер порта в который отсылается команда и из которого принимаются данные *)
PORT: PORTS := 2;
(* Таймаут ответа *)
TIMEOUT: TIME := T#3000ms;
END_VAR
(* Выходы функционального блока *)
VAR_OUTPUT
(* Признак окончания чтения *)
Q: BOOL := FALSE;
(* Результат принятый от устройства *)
OUT: BUFER;
ERROR: PULSAR_ERRORS := NO_ERROR;
END_VAR
VAR
(*Таймер ожидания ответа*)
tonReply: TON;
(*Таймер периодичности опроса*)
tonDelay: TON;
(*Тригер позволяющий определить фронт сигнала IN и выполнить инициализацию блока*)
rtStart: R_TRIG;
rfEnd: F_TRIG;
(* Буфер чтения и указатель на текущую позицию в буфере *)
bufRead: BUFER;
ptrBufRead: POINTER TO BYTE;
(* Для расчета CRC, ID *)
wInCrc: WORD;
wCrc: WORD;
(*Идентификатор запроса для сверки*)
wID: WORD;
wCmdID: WORD;
bFun: BYTE;
bCmdFun: BYTE;
(* Ожидаемый размер ответа *)
wAnswerLen : WORD;
(* Статус работы блока -
1: задержка повтора,
2: очистка буфера,
3: отправка данных,
4: прием данных
*)
STATE: BYTE:=2;
END_VAR
VAR CONSTANT
(* Сколько читаем байт за цикл. Рекомендуется как минимум захватывать байт с размером *)
READ_ONCE: UINT := 10;
(* Смещение функции в буфере *)
FUN_OFFSET: BYTE := 4;
(* Смещение адреса в буфере *)
LEN_OFFSET: UINT := 5;
(* Смещение данных в буфере *)
DATA_OFFSET: UINT := 6;
(* Таймаут ввода\вывода *)
IO_TIMEOUT: DWORD := 500;
(* Смещение CRC c конца буфера *)
CRC_OFFSET: INT := -2;
(* Смещение ID c конца буфера *)
ID_OFFSET: INT := -4;
(* Конечное состояние *)
END_STATE: BYTE := 5;
END_VAR
(* Если пришел разрешающий сигнал запуска блока, то необходимо выполнить его инициализацию *)
IF rtStart.Q THEN
(* Выходы блока не сформированы *)
Q := FALSE;
ERROR := 0;
(* Переводим блок в режим отправки команды *)
STATE := 2;
END_IF
IF rfEnd.Q THEN
(* Выходы блока не сформированы *)
Q := FALSE;
ERROR := NO_ERROR;
(* Сборос таймера повторного запроса *)
tonDelay(IN:=FALSE, PT:=T#0s);
tonReply(IN:=FALSE, PT:=T#0s);
ANSWER_LEN := 0;
END_IF
IF NOT IN THEN
RETURN;
END_IF
(* Если работа блока разрешена, то выполняем чтение и запись в порт с заданными параметрами *)
CASE STATE OF
(* Задержка перед повторным запросом *)
1:
IF tonDelay.IN=FALSE THEN
(* Сборос таймера повторного запроса *)
tonDelay( IN:=TRUE, PT:=DELAY );
ELSIF tonDelay.Q=TRUE THEN
(* Останавливаем таймер задержки *)
tonDelay(IN:=FALSE, PT:=T#0s);
(* Переводим блок в следующий статус*)
STATE := STATE + 1;
END_IF
(* Перед отправкой команды необходимо очистить буфер от предыдущего ответа *)
2:
(* сбросить указатель буфера *)
ptrBufRead := ADR(OUT.BUF);
(* очистить буфер *)
_BUFFER_CLEAR(ptrBufRead, SIZEOF(OUT.BUF));
(* и обнулить размер принятого ответа *)
OUT.LEN := 0;
dwBytesSend := 0;
(* для возможности получения сообщений произвольного размера *)
wAnswerLen := ANSWER_LEN;
(* Если запись в порт совершена - размер команды совпадает с размером отправленных блоком отправки, то *)
IF dwBytesSend >= CMD.LEN THEN
(* Переводим блок в следующий статус *)
STATE := STATE + 1;
(* Если таймер запущен и закончил работать, а команда не отправлена, то формируем ошибку *)
ELSIF (tonReply.Q=TRUE AND tonReply.IN=TRUE) THEN
ERROR := WRITE_INCOMPLETE;
(* Выходы блока сформированы *)
Q := TRUE;
(* Переводим блок в следующий статус *)
STATE := STATE + 1;
END_IF
(* Чтение буфера приема *)
4:
(* В переменную dwBytesReceived получаем число принятых байт при чтении порта *)
dwBytesReceived := SysComRead(PORT, ptrBufRead, READ_ONCE, IO_TIMEOUT);
(* Если получили ответ от устройства, то принятую информацию собираем в буфер ответа *)
IF dwBytesReceived>0 THEN
(* Проверяем выход за пределы буфера *)
IF (OUT.LEN + DWORD_TO_UINT(dwBytesReceived) + READ_ONCE > BUFER_MAX_SIZE) THEN
ERROR := INVALID_RESPONSE_LEN;
Q := TRUE;
(* Переводим блок в следующий статус *)
STATE := END_STATE;
ELSE
ptrBufRead := ptrBufRead + dwBytesReceived;
OUT.LEN := OUT.LEN + DWORD_TO_UINT(dwBytesReceived);
END_IF
(* Если размер ответа не задан при вызове - берем размер из соответствующего байта фрейма *)
IF wAnswerLen = 0 THEN
IF (OUT.LEN > LEN_OFFSET) THEN
wAnswerLen := OUT.BUF[LEN_OFFSET];
END_IF;
ELSE
(* Получили нужное количество байт *)
IF OUT.LEN = wAnswerLen THEN
(* Вычисляем CRC *)
wInCrc := OUT.BUF[OUT.LEN+CRC_OFFSET+0] OR SHL(OUT.BUF[OUT.LEN+CRC_OFFSET+1], 8);
wCrc := MB_CRC_16(ADR(OUT.BUF), OUT.LEN - 2);
(* Вычисляем ID *)
wCmdID := CMD.BUF[CMD.LEN+ID_OFFSET+0] OR SHL(CMD.BUF[CMD.LEN+ID_OFFSET+1], 8);
wID := OUT.BUF[OUT.LEN+ID_OFFSET+0] OR SHL(OUT.BUF[OUT.LEN+ID_OFFSET+1], 8);
(* Вычисляем Функцию *)
bCmdFun := CMD.BUF[FUN_OFFSET];
bFun := OUT.BUF[FUN_OFFSET];
(* Проверяем CRC *)
IF NOT wCrc = wInCrc THEN
(* Если ответ пришел, но неправильный CRC *)
ERROR := INVALID_CRC;
(* Проверяем ID *)
ELSIF wCmdID <> 0 AND wID <> wCmdID THEN
(* Если ответ пришел, но ID отличается *)
ERROR := INVALID_TRANSACTION_ID ;
ELSIF bCmdFun <> 0 AND bFun = 0 THEN
(* Если ответ пришел, c ошибкой *)
ERROR := OUT.BUF[DATA_OFFSET] + 100;
IF ERROR > 108 THEN
ERROR := ERROR_UNKNOWN;
END_IF
ELSIF bCmdFun <> 0 AND bFun <> bCmdFun THEN
(* Если ответ пришел, но функция отличается *)
ERROR := INVALID_FUNCTION;
ELSE
(* Ошибок нет *)
ERROR := NO_ERROR;
END_IF
Q := TRUE;
(* Переводим блок в следующий статус *)
STATE := END_STATE;
ELSIF OUT.LEN > wAnswerLen THEN
(* Получено слишком много байт *)
ERROR := INVALID_RESPONSE_LEN;
Q := TRUE;
(* Переводим блок в следующий статус *)
STATE := END_STATE;
END_IF
END_IF
END_IF
(* Если таймер запущен и закончил работать, а нужного ответа нет, то формируем ошибки *)
IF (tonReply.Q=TRUE AND tonReply.IN=TRUE) THEN
(* Если ответ не пришел вообще, значит устройство не отвечает на запрос *)
IF OUT.LEN=0 THEN
(* Формируем ошибку нет связи *)
ERROR := TIME_OUT;
ELSE
(* Если ответ пришел, но не полный, формируем ошибку Timeout *)
ERROR := INVALID_RESPONSE_LEN;
END_IF
(* Выходы блока сформированы *)
Q := TRUE;
(* Переводим блок в следующий статус *)
STATE := STATE +1;
END_IF
(* Переводим блок в следующий статус *)
ELSE
(* Останавливаем работу таймера ожидания ответа *)
tonReply(IN:=FALSE, PT:=T#0s );
(* Выходы блока не сформированы *)
Q := FALSE;
STATE := 1;
END_CASE;
(* Вызываем таймер ожидания ответа и задержки повтора*)
tonReply();
tonDelay();
PULSAR_CMD
Скрытый текст:
Код:
(* Создание запроса в формате фрейма Пульсар М*)
FUNCTION PULSAR_CMD: BUFER
VAR_INPUT
ADDR: DWORD := 16#F00F0FF0;
FUN: BYTE := 0;
ID: WORD := 16#0000;
DATA: DWORD := 16#00000000;
DATA_LEN: BYTE := 0;
END_VAR
VAR CONSTANT
FUN_OFFSET: BYTE := 4;
LEN_OFFSET: BYTE := 5;
DATA_OFFSET: BYTE := 6;
ID_OFFSET: BYTE := 6;
CRC_OFFSET: BYTE := 8;
END_VAR
VAR
wCrc: WORD;
bOffset: BYTE;
dwShift: DWORD;
END_VAR
Код:
(*Адрес устройства *)
PULSAR_CMD.BUF[0] := DWORD_TO_BYTE(SHR(ADDR, 24 ) AND 16#FF);
PULSAR_CMD.BUF[1] := DWORD_TO_BYTE(SHR(ADDR, 16 ) AND 16#FF);
PULSAR_CMD.BUF[2] := DWORD_TO_BYTE(SHR(ADDR, 8 ) AND 16#FF);
PULSAR_CMD.BUF[3] := DWORD_TO_BYTE(SHR(ADDR, 0 ) AND 16#FF);
(* Функция *)
PULSAR_CMD.BUF[FUN_OFFSET] := FUN;
(* Блок данных если есть *)
IF DATA_LEN > 0 THEN
dwShift := 0;
FOR bOffset := 0 TO DATA_LEN - 1 DO
PULSAR_CMD.BUF[DATA_OFFSET + bOffset] := DWORD_TO_BYTE(SHR(DATA, dwShift) AND 16#FF);
dwShift := dwShift + 8;
END_FOR
END_IF;
(* Размер: адрес + функция + длина + данные + ID + CRC *)
PULSAR_CMD.LEN := 4 + 1 + 1 + DATA_LEN + 2 + 2;
(* Исключение для нулевой функции *)
IF FUN = 0 THEN
PULSAR_CMD.BUF[LEN_OFFSET] := 0;
ELSE
PULSAR_CMD.BUF[LEN_OFFSET] := UINT_TO_BYTE(PULSAR_CMD.LEN);
END_IF
(* ID запроса *)
PULSAR_CMD.BUF[ID_OFFSET + DATA_LEN + 1] := DWORD_TO_BYTE(SHR(ID, 8) AND 16#FF);
PULSAR_CMD.BUF[ID_OFFSET + DATA_LEN + 0] := DWORD_TO_BYTE(SHR(ID, 0) AND 16#FF);
(* Вычисление контрольной суммы кадра MODBUS RTU CRC *)
FUNCTION MB_CRC_16 : WORD
VAR_INPUT
DATA: POINTER TO BYTE; (* указатель на блок данных *)
SIZE: WORD; (* размер блока данных *)
END_VAR
VAR
bCnt: BYTE; (* счетчик битов *)
END_VAR
Код:
MB_CRC_16 := 16#FFFF;
WHILE SIZE > 0 DO
MB_CRC_16 := MB_CRC_16 XOR DATA^;
FOR bCnt := 0 TO 7 DO
IF MB_CRC_16.0 = 0 THEN
MB_CRC_16 := SHR(MB_CRC_16, 1);
ELSE
MB_CRC_16 := SHR(MB_CRC_16, 1) XOR 16#A001;
END_IF
END_FOR;
DATA := DATA + 1;
SIZE := SIZE - 1;
END_WHILE
Типы данных
BUFER
Скрытый текст:
Код:
TYPE BUFER :
STRUCT
LEN: UINT := 0;
BUF: ARRAY [0..BUFER_MAX_SIZE] OF BYTE;
END_STRUCT
END_TYPE
PULSAR_ERRORS
Скрытый текст:
Код:
(* Ошибки протокола Пульсар (при реализации с помощью Pulsar_STC) *)
TYPE PULSAR_ERRORS :
(
(* Общие коды ошибок *)
NO_ERROR := 0,
TIME_OUT := 5001,
HANDLE_INVALID := 5003,
ERROR_UNKNOWN := 5004,
WRONG_PARAMETER := 5005,
WRITE_INCOMPLETE := 5006,
(* Специфические ошибки протокола Пульсар-М *)
(* Отсутствует запрашиваемый код функции *)
INVALID_FUNCTION := 101,
(* Ошибка в битовой маске запроса *)
INVALID_MASK := 102,
(* Ошибочная длина запроса *)
INVALID_REQUEST_LEN := 103,
(* Отсутствует параметр *)
INVALID_PARAM := 104,
(* Запись заблокирована, требуется авторизация *)
AUTHORIZATION_REQUIRED := 105,
(* Записываемое значение (параметр) находится вне заданного диапазона *)
PARAM_OUT_OF_RANGE := 106,
(* Отсутствует запрашиваемый тип архива *)
NO_ARCHIVE_TYPE := 107,
(* Превышение максимального количества считываемых архивных значений за один пакет *)
TOO_MUCH_ARCHIVE_DATA := 108,
(* Некорректная длина ответа *)
(* (<10 байт или значение поля LEN не соответствует фактической длине ответа) *)
INVALID_RESPONSE_LEN := 120,
(* Некорректный адрес устройства в ответе *)
INVALID_ADDR := 121,
(* Некорректный идентификатор пакета в ответе *)
INVALID_TRANSACTION_ID := 122,
(* Некорректная CRC в ответе *)
INVALID_CRC := 123
) := NO_ERROR;
END_TYPE
Для интеграции по RS485 счетчиков Пульсар М существует готовая библиотека для Codesys 3.5. А вот для Codesys 2.3 не нашел, ниже моя реализация. Может кому пригодится.
IF NOT IN THEN
ComService(ENABLE := FALSE, SETTINGS := Settings, TASK := CLOSE_TSK);
STATE := 0;
tonStep(IN:=FALSE, PT:=T#0s);
tonDelay(IN:=FALSE, PT:=T#0s);
END_IF
IF NOT ComService.Ready THEN
RETURN;
END_IF
CASE STATE OF
0:
IF ComService.Ready THEN
STATE := 1;
END_IF
IF STC.Q THEN
ERROR := STC.ERROR;
IF ERROR = 0 THEN
dwTmp := SHL(STC.OUT.BUF[DATA_OFFSET + 0], 0) OR SHL(STC.OUT.BUF[DATA_OFFSET + 1], 8) OR SHL(STC.OUT.BUF[DATA_OFFSET + 2], 16) OR SHL(STC.OUT.BUF[DATA_OFFSET + 3], 24);
prTmp := ADR(dwTmp);
R_VOLTAGE := prTmp^;
mNextStep;
END_IF
END_IF
6:
mNextStepDelay;
(* Чтение температуры *)
7:
cmd := PULSAR_CMD(ADDR := ADDR, FUN := FUN_PARAM, ID:= wReqId, DATA := VALUE_TEMP, DATA_LEN := 2);
STC(in:=IN, DELAY:=REPEAT_DELAY, TIMEOUT:=READ_TIMEOUT, PORT:=ComPort, CMD:=cmd, ANSWER_LEN:=0);
IF STC.Q THEN
ERROR := STC.ERROR;
IF ERROR = 0 THEN
dwTmp := SHL(STC.OUT.BUF[DATA_OFFSET + 0], 0) OR SHL(STC.OUT.BUF[DATA_OFFSET + 1], 8) OR SHL(STC.OUT.BUF[DATA_OFFSET + 2], 16) OR SHL(STC.OUT.BUF[DATA_OFFSET + 3], 24);
prTmp := ADR(dwTmp);
R_TEMP := prTmp^;
mNextStep;
END_IF
END_IF
8:
mNextStepDelay;
(* Обновление даты счетчика *)
9:
(* Пропускаем, если уже обновили ИЛИ нет признака обновления даты*)
IF bTimeIsSet OR NOT UPDATE_DATE THEN
STATE := STATE + 2;
ELSE
IF STC.Q THEN
ERROR := STC.ERROR;
IF ERROR = 0 THEN
R_DT := SET_DT(
year := 2000 + STC.OUT.BUF[DATA_OFFSET + 0],
month := STC.OUT.BUF[DATA_OFFSET + 1],
day := STC.OUT.BUF[DATA_OFFSET + 2],
HOUR := STC.OUT.BUF[DATA_OFFSET + 3],
MINUTE := STC.OUT.BUF[DATA_OFFSET + 4],
SECOND := STC.OUT.BUF[DATA_OFFSET + 5]
);
mNextStep;
END_IF
END_IF
12:
mNextStepDelay;
(* Пауза между пакетами *)
13:
(* Задержка между запросами *)
IF NOT tonDelay.IN THEN
tonDelay(IN:=TRUE, PT:=DELAY);
ELSIF tonDelay.Q THEN
tonDelay(IN:=FALSE, PT:=T#0s);
STATE := STATE +1;
END_IF
(* Возврат в начало цикла *)
ELSE
STATE := 3;
END_CASE
tonDelay();
tonStep();
Модуль mNextStep
Код:
(* Формируем ID запроса инкрементом *)
wReqId := wReqId + 1;
IF wReqId = 16#FFFF THEN
wReqId := 1;
END_IF
STC(IN:=FALSE);
STATE := STATE + 1;
Модуль mNextStepDelay
Код:
(* Задержка между запросами *)
IF NOT tonStep.IN THEN
(* Таймер задержки и следующий шаг *)
tonStep(IN:=TRUE, PT:=STEP_DELAY);
ELSIF tonStep.Q THEN
tonStep(IN:=FALSE, PT:=T#0s);
STATE:=STATE+1;
END_IF
PULSAR_STC
Скрытый текст:
Код:
(* Реализация взаимодействия по COM порту со спецификой Пульсар М *)
FUNCTION_BLOCK PULSAR_STC
(* Входы функционального блока *)
VAR_INPUT
(* Вход разрешающий работу блока *)
IN: BOOL:=FALSE;
(* Указатель на буфер с массивом байт в котором содержится пересылаемая команда *)
CMD: BUFER;
(* Количество байт в ответе - ноль для вычисления из пакета сообщения Пульсар *)
ANSWER_LEN: WORD := 0;
(* Задержка повторного запроса *)
DELAY: TIME := T#500ms;
(* Номер порта в который отсылается команда и из которого принимаются данные *)
PORT: PORTS := 2;
(* Таймаут ответа *)
TIMEOUT: TIME := T#3000ms;
END_VAR
(* Выходы функционального блока *)
VAR_OUTPUT
(* Признак окончания чтения *)
Q: BOOL := FALSE;
(* Результат принятый от устройства *)
OUT: BUFER;
ERROR: PULSAR_ERRORS := NO_ERROR;
END_VAR
VAR
(*Таймер ожидания ответа*)
tonReply: TON;
(*Таймер периодичности опроса*)
tonDelay: TON;
(*Тригер позволяющий определить фронт сигнала IN и выполнить инициализацию блока*)
rtStart: R_TRIG;
rfEnd: F_TRIG;
(* Буфер чтения и указатель на текущую позицию в буфере *)
bufRead: BUFER;
ptrBufRead: POINTER TO BYTE;
(* Для расчета CRC, ID *)
wInCrc: WORD;
wCrc: WORD;
(*Идентификатор запроса для сверки*)
wID: WORD;
wCmdID: WORD;
bFun: BYTE;
bCmdFun: BYTE;
(* Ожидаемый размер ответа *)
wAnswerLen : WORD;
(* Статус работы блока -
1: задержка повтора,
2: очистка буфера,
3: отправка данных,
4: прием данных
*)
STATE: BYTE:=2;
END_VAR
VAR CONSTANT
(* Сколько читаем байт за цикл. Рекомендуется как минимум захватывать байт с размером *)
READ_ONCE: UINT := 10;
(* Смещение функции в буфере *)
FUN_OFFSET: BYTE := 4;
(* Смещение адреса в буфере *)
LEN_OFFSET: UINT := 5;
(* Смещение данных в буфере *)
DATA_OFFSET: UINT := 6;
(* Таймаут ввода\вывода *)
IO_TIMEOUT: DWORD := 500;
(* Смещение CRC c конца буфера *)
CRC_OFFSET: INT := -2;
(* Смещение ID c конца буфера *)
ID_OFFSET: INT := -4;
(* Конечное состояние *)
END_STATE: BYTE := 5;
END_VAR
(* Если пришел разрешающий сигнал запуска блока, то необходимо выполнить его инициализацию *)
IF rtStart.Q THEN
(* Выходы блока не сформированы *)
Q := FALSE;
ERROR := 0;
(* Переводим блок в режим отправки команды *)
STATE := 2;
END_IF
IF rfEnd.Q THEN
(* Выходы блока не сформированы *)
Q := FALSE;
ERROR := NO_ERROR;
(* Сборос таймера повторного запроса *)
tonDelay(IN:=FALSE, PT:=T#0s);
tonReply(IN:=FALSE, PT:=T#0s);
ANSWER_LEN := 0;
END_IF
IF NOT IN THEN
RETURN;
END_IF
(* Если работа блока разрешена, то выполняем чтение и запись в порт с заданными параметрами *)
CASE STATE OF
(* Задержка перед повторным запросом *)
1:
IF tonDelay.IN=FALSE THEN
(* Сборос таймера повторного запроса *)
tonDelay( IN:=TRUE, PT:=DELAY );
ELSIF tonDelay.Q=TRUE THEN
(* Останавливаем таймер задержки *)
tonDelay(IN:=FALSE, PT:=T#0s);
(* Переводим блок в следующий статус*)
STATE := STATE + 1;
END_IF
(* Перед отправкой команды необходимо очистить буфер от предыдущего ответа *)
2:
(* сбросить указатель буфера *)
ptrBufRead := ADR(OUT.BUF);
(* очистить буфер *)
_BUFFER_CLEAR(ptrBufRead, SIZEOF(OUT.BUF));
(* и обнулить размер принятого ответа *)
OUT.LEN := 0;
dwBytesSend := 0;
(* для возможности получения сообщений произвольного размера *)
wAnswerLen := ANSWER_LEN;
(* Если запись в порт совершена - размер команды совпадает с размером отправленных блоком отправки, то *)
IF dwBytesSend >= CMD.LEN THEN
(* Переводим блок в следующий статус *)
STATE := STATE + 1;
(* Если таймер запущен и закончил работать, а команда не отправлена, то формируем ошибку *)
ELSIF (tonReply.Q=TRUE AND tonReply.IN=TRUE) THEN
ERROR := WRITE_INCOMPLETE;
(* Выходы блока сформированы *)
Q := TRUE;
(* Переводим блок в следующий статус *)
STATE := STATE + 1;
END_IF
(* Чтение буфера приема *)
4:
(* В переменную dwBytesReceived получаем число принятых байт при чтении порта *)
dwBytesReceived := SysComRead(PORT, ptrBufRead, READ_ONCE, IO_TIMEOUT);
(* Если получили ответ от устройства, то принятую информацию собираем в буфер ответа *)
IF dwBytesReceived>0 THEN
(* Проверяем выход за пределы буфера *)
IF (OUT.LEN + DWORD_TO_UINT(dwBytesReceived) + READ_ONCE > BUFER_MAX_SIZE) THEN
ERROR := INVALID_RESPONSE_LEN;
Q := TRUE;
(* Переводим блок в следующий статус *)
STATE := END_STATE;
ELSE
ptrBufRead := ptrBufRead + dwBytesReceived;
OUT.LEN := OUT.LEN + DWORD_TO_UINT(dwBytesReceived);
END_IF
(* Если размер ответа не задан при вызове - берем размер из соответствующего байта фрейма *)
IF wAnswerLen = 0 THEN
IF (OUT.LEN > LEN_OFFSET) THEN
wAnswerLen := OUT.BUF[LEN_OFFSET];
END_IF;
ELSE
(* Получили нужное количество байт *)
IF OUT.LEN = wAnswerLen THEN
(* Вычисляем CRC *)
wInCrc := OUT.BUF[OUT.LEN+CRC_OFFSET+0] OR SHL(OUT.BUF[OUT.LEN+CRC_OFFSET+1], 8);
wCrc := MB_CRC_16(ADR(OUT.BUF), OUT.LEN - 2);
(* Вычисляем ID *)
wCmdID := CMD.BUF[CMD.LEN+ID_OFFSET+0] OR SHL(CMD.BUF[CMD.LEN+ID_OFFSET+1], 8);
wID := OUT.BUF[OUT.LEN+ID_OFFSET+0] OR SHL(OUT.BUF[OUT.LEN+ID_OFFSET+1], 8);
(* Вычисляем Функцию *)
bCmdFun := CMD.BUF[FUN_OFFSET];
bFun := OUT.BUF[FUN_OFFSET];
(* Проверяем CRC *)
IF NOT wCrc = wInCrc THEN
(* Если ответ пришел, но неправильный CRC *)
ERROR := INVALID_CRC;
(* Проверяем ID *)
ELSIF wCmdID <> 0 AND wID <> wCmdID THEN
(* Если ответ пришел, но ID отличается *)
ERROR := INVALID_TRANSACTION_ID ;
ELSIF bCmdFun <> 0 AND bFun = 0 THEN
(* Если ответ пришел, c ошибкой *)
ERROR := OUT.BUF[DATA_OFFSET] + 100;
IF ERROR > 108 THEN
ERROR := ERROR_UNKNOWN;
END_IF
ELSIF bCmdFun <> 0 AND bFun <> bCmdFun THEN
(* Если ответ пришел, но функция отличается *)
ERROR := INVALID_FUNCTION;
ELSE
(* Ошибок нет *)
ERROR := NO_ERROR;
END_IF
Q := TRUE;
(* Переводим блок в следующий статус *)
STATE := END_STATE;
ELSIF OUT.LEN > wAnswerLen THEN
(* Получено слишком много байт *)
ERROR := INVALID_RESPONSE_LEN;
Q := TRUE;
(* Переводим блок в следующий статус *)
STATE := END_STATE;
END_IF
END_IF
END_IF
(* Если таймер запущен и закончил работать, а нужного ответа нет, то формируем ошибки *)
IF (tonReply.Q=TRUE AND tonReply.IN=TRUE) THEN
(* Если ответ не пришел вообще, значит устройство не отвечает на запрос *)
IF OUT.LEN=0 THEN
(* Формируем ошибку нет связи *)
ERROR := TIME_OUT;
ELSE
(* Если ответ пришел, но не полный, формируем ошибку Timeout *)
ERROR := INVALID_RESPONSE_LEN;
END_IF
(* Выходы блока сформированы *)
Q := TRUE;
(* Переводим блок в следующий статус *)
STATE := STATE +1;
END_IF
(* Переводим блок в следующий статус *)
ELSE
(* Останавливаем работу таймера ожидания ответа *)
tonReply(IN:=FALSE, PT:=T#0s );
(* Выходы блока не сформированы *)
Q := FALSE;
STATE := 1;
END_CASE;
(* Вызываем таймер ожидания ответа и задержки повтора*)
tonReply();
tonDelay();
PULSAR_CMD
Скрытый текст:
Код:
(* Создание запроса в формате фрейма Пульсар М*)
FUNCTION PULSAR_CMD: BUFER
VAR_INPUT
ADDR: DWORD := 16#F00F0FF0;
FUN: BYTE := 0;
ID: WORD := 16#0000;
DATA: DWORD := 16#00000000;
DATA_LEN: BYTE := 0;
END_VAR
VAR CONSTANT
FUN_OFFSET: BYTE := 4;
LEN_OFFSET: BYTE := 5;
DATA_OFFSET: BYTE := 6;
ID_OFFSET: BYTE := 6;
CRC_OFFSET: BYTE := 8;
END_VAR
VAR
wCrc: WORD;
bOffset: BYTE;
dwShift: DWORD;
END_VAR
Код:
(*Адрес устройства *)
PULSAR_CMD.BUF[0] := DWORD_TO_BYTE(SHR(ADDR, 24 ) AND 16#FF);
PULSAR_CMD.BUF[1] := DWORD_TO_BYTE(SHR(ADDR, 16 ) AND 16#FF);
PULSAR_CMD.BUF[2] := DWORD_TO_BYTE(SHR(ADDR, 8 ) AND 16#FF);
PULSAR_CMD.BUF[3] := DWORD_TO_BYTE(SHR(ADDR, 0 ) AND 16#FF);
(* Функция *)
PULSAR_CMD.BUF[FUN_OFFSET] := FUN;
(* Блок данных если есть *)
IF DATA_LEN > 0 THEN
dwShift := 0;
FOR bOffset := 0 TO DATA_LEN - 1 DO
PULSAR_CMD.BUF[DATA_OFFSET + bOffset] := DWORD_TO_BYTE(SHR(DATA, dwShift) AND 16#FF);
dwShift := dwShift + 8;
END_FOR
END_IF;
(* Размер: адрес + функция + длина + данные + ID + CRC *)
PULSAR_CMD.LEN := 4 + 1 + 1 + DATA_LEN + 2 + 2;
(* Исключение для нулевой функции *)
IF FUN = 0 THEN
PULSAR_CMD.BUF[LEN_OFFSET] := 0;
ELSE
PULSAR_CMD.BUF[LEN_OFFSET] := UINT_TO_BYTE(PULSAR_CMD.LEN);
END_IF
(* ID запроса *)
PULSAR_CMD.BUF[ID_OFFSET + DATA_LEN + 1] := DWORD_TO_BYTE(SHR(ID, 8) AND 16#FF);
PULSAR_CMD.BUF[ID_OFFSET + DATA_LEN + 0] := DWORD_TO_BYTE(SHR(ID, 0) AND 16#FF);
(* Вычисление контрольной суммы кадра MODBUS RTU CRC *)
FUNCTION MB_CRC_16 : WORD
VAR_INPUT
DATA: POINTER TO BYTE; (* указатель на блок данных *)
SIZE: WORD; (* размер блока данных *)
END_VAR
VAR
bCnt: BYTE; (* счетчик битов *)
END_VAR
Код:
MB_CRC_16 := 16#FFFF;
WHILE SIZE > 0 DO
MB_CRC_16 := MB_CRC_16 XOR DATA^;
FOR bCnt := 0 TO 7 DO
IF MB_CRC_16.0 = 0 THEN
MB_CRC_16 := SHR(MB_CRC_16, 1);
ELSE
MB_CRC_16 := SHR(MB_CRC_16, 1) XOR 16#A001;
END_IF
END_FOR;
DATA := DATA + 1;
SIZE := SIZE - 1;
END_WHILE
Типы данных
BUFER
Скрытый текст:
Код:
TYPE BUFER :
STRUCT
LEN: UINT := 0;
BUF: ARRAY [0..BUFER_MAX_SIZE] OF BYTE;
END_STRUCT
END_TYPE
PULSAR_ERRORS
Скрытый текст:
Код:
(* Ошибки протокола Пульсар (при реализации с помощью Pulsar_STC) *)
TYPE PULSAR_ERRORS :
(
(* Общие коды ошибок *)
NO_ERROR := 0,
TIME_OUT := 5001,
HANDLE_INVALID := 5003,
ERROR_UNKNOWN := 5004,
WRONG_PARAMETER := 5005,
WRITE_INCOMPLETE := 5006,
(* Специфические ошибки протокола Пульсар-М *)
(* Отсутствует запрашиваемый код функции *)
INVALID_FUNCTION := 101,
(* Ошибка в битовой маске запроса *)
INVALID_MASK := 102,
(* Ошибочная длина запроса *)
INVALID_REQUEST_LEN := 103,
(* Отсутствует параметр *)
INVALID_PARAM := 104,
(* Запись заблокирована, требуется авторизация *)
AUTHORIZATION_REQUIRED := 105,
(* Записываемое значение (параметр) находится вне заданного диапазона *)
PARAM_OUT_OF_RANGE := 106,
(* Отсутствует запрашиваемый тип архива *)
NO_ARCHIVE_TYPE := 107,
(* Превышение максимального количества считываемых архивных значений за один пакет *)
TOO_MUCH_ARCHIVE_DATA := 108,
(* Некорректная длина ответа *)
(* (<10 байт или значение поля LEN не соответствует фактической длине ответа) *)
INVALID_RESPONSE_LEN := 120,
(* Некорректный адрес устройства в ответе *)
INVALID_ADDR := 121,
(* Некорректный идентификатор пакета в ответе *)
INVALID_TRANSACTION_ID := 122,
(* Некорректная CRC в ответе *)
INVALID_CRC := 123
) := NO_ERROR;
END_TYPE
Куча непоняток,
1) откуда в программе PULSAR_M взялись входы, выходы VAR_IN_OUT ? Что такое PULSAR_LOG, LOG_BUFFER и LOG_ERROR ?
2) откуда в ФБ NPT взялись входы In, Delay и выход Q ?
Действительно, лучше было проект выложить.
18.01.2025, 14:05
melky
kirill.k2 что у вас за счетчик, на котором вы смогли синхронизировать время?
19.01.2025, 02:27
kirill.k2
Цитата:
Сообщение от kondor3000
Куча непоняток,
1) откуда в программе PULSAR_M взялись входы, выходы VAR_IN_OUT ? Что такое PULSAR_LOG, LOG_BUFFER и LOG_ERROR ?
"Туда не смотри, сюда смотри" (c) - на картинке расширенная версия, с отладкой. В теме код финальной, отладка отключена. Добавил в исходный пост, спасибо за замечание.
Цитата:
Сообщение от kondor3000
Куча непоняток,
2) откуда в ФБ NPT взялись входы In, Delay и выход Q ?
Опять же, см.выше. Скриншот с реально рабочего проекта, для демонстрации возможностей. Конкретно то, о чем спрашиваете - это моя реализация NTP. И для работы синхронизации Пульсара она не нужна. Отвечая прямо - NTP.Delay - задержки перед повторной попыткой опроса NTP сервера, NTP.Q означает успешную синхронизацию, NTP.In - сигнал на старт. Ничего из этого не имеет прямого отношения к Пульсару.
19.01.2025, 02:38
kirill.k2
Вложений: 1
Цитата:
Сообщение от melky
kirill.k2 что у вас за счетчик, на котором вы смогли синхронизировать время?
Модель называлась как-то так (не реклама):
Скрытый текст:
Цитата:
Счетчик холодной воды Ду20 Пульсар с цифровым выходом (RS485) Вложение 81407
Обычный бытовой, для холодной воды, с модулем RS485. Причем старый, "аналоговый". Программа должна и с новыми "цифровыми" работать - разве что формат данных непосредственно показаний надо проверить и, возможно, поменять с 4 на 8 байт.
19.01.2025, 02:39
kirill.k2
Цитата:
Сообщение от IVM
Лучше бы проект выложить.
Крайне неудобный формат обмена. А вот оверквотинг - точно зло, снесите, пожалуйста.
19.01.2025, 03:09
kirill.k2
Добавил код либой. В виде либы не тестил, пока нет времени.
19.01.2025, 09:21
melky
Странно, какого года выпуск? Мой требует пароля при смене даты, который каким-то образом вычисляется из серийного номера.
Давно тех поддержка отмолчалась по этому поводу.
19.01.2025, 09:59
kondor3000
Цитата:
Сообщение от kirill.k2
Опять же, см.выше. Скриншот с реально рабочего проекта, для демонстрации возможностей. Конкретно то, о чем спрашиваете - это моя реализация NTP. И для работы синхронизации Пульсара она не нужна. Отвечая прямо - NTP.Delay - задержки перед повторной попыткой опроса NTP сервера, NTP.Q означает успешную синхронизацию, NTP.In - сигнал на старт. Ничего из этого не имеет прямого отношения к Пульсару.
Переменная NPT_SINC_READY на входе UPDATE_DATE то осталась, даже если выход Q не нужен, что тогда подавать на вход UPDATE_DATE,
просто TRUE ?
Счётчика у меня всё равно нет, интересна просто реализация.
А NPT синхронизация у меня не работает, думаю IP нужен другой, из серии 192.168.0.хх