понедельник, 7 октября 2024 г.

Продолжение по Owen и Codesys

В прошлый раз писал о том как запустить опрос нескольких устройств по протоколу modbus чтобы опрос велся в пакетном режиме - тогда советовали использовать символы, и приходилось делать еще множество преобразований, а еще чтобы сделать опрос сразу 30 устройств пришлось даже расширять память. Оказалось что Овен выпустили давным давно библиотеку, которая так же в пакетном режиме опрашивает модбас устройства и преобразований становится меньше.

Удобно то что добавлять и убирать опрашиваемые устройства можно в коде и гораздо проще чем через конфиг контроллера, хотя и сделал я это немного странновато, но работает. Удобно что в Кодесис есть экспорт программы, который экспортируется целиком в текстовом формате - можно экспортировать кусок программы, лишь один блок FB, что я и сделал и скопировал его в гитхаб гист. Этот блок вызывается из главной программы, и в него передается количество оправшиваемых модбас устройств, адреса которых записаны в отдельном массиве. Вообще там есть еще и множество глобальных переменных, но пока решил не добавлять 

(* @NESTEDCOMMENTS := 'Yes' *)
(* @PATH := '\/Program' *)
(* @OBJECTFLAGS := '0, 8' *)
(* @SYMFILEFLAGS := '2048' *)
FUNCTION_BLOCK ModbusRead
VAR_INPUT
devnumb:BYTE;
END_VAR
VAR_OUTPUT
END_VAR
VAR
mbadr:BYTE;
mbdep:BYTE;
get_modbus: MB_RD_INP_REGS; (*функция 04 - чтение трех параметров типа INT*)
Buffer: ARRAY[0..255] OF BYTE; (* байтовый буфер данных*)
cmpl: BOOL;
TimeOut: TIME:=T#10ms;(*таймаут*)
Exception: BYTE;
DataSize: WORD;
master1: BYTE:= 0;
i:BYTE:=1;
END_VAR
(* @END_DECLARATION := '0' *)
IF enabl = FALSE THEN
enabl := TRUE;
END_IF
(*Устанавливаем настройки COM-порта*)
IF port_opened=0 THEN
Settings.Port:=com_num; (*номер COM-порта 0 – RS-485, 1 – RS-232*)
Settings.dwBaudRate:=115200; (*скорость*)
Settings.byParity:=0;
Settings.dwTimeout:=0;
Settings.byStopBits:=1;
Settings.dwBufferSize:=0;
Settings.dwScan:=0;
END_IF
(*Открываем COM-порт*)
COM_SERVICE1(Enable:=(port_opened=0), Settings:=Settings , Task:=OPEN_TSK );
(*Если COM-порт открыт, то переходим к приему и передачи данных *)
IF COM_SERVICE1.ready THEN
port_opened:=2;
END_IF
IF port_opened=2 THEN (*Удачно проинициализировали*)
CASE master1 OF
0:
get_modbus(
Enable:=enabl , (* разрешение работы блока *)
Mode:=MB_RTU , (*режим передачи*)
DevAddr:=adrDep[i] , (*адрес*)
FirstAddr:=32000 , (*номер регистра*)
Quantity:=16, (*количество регистров*)
ComHandle:=Settings.Port ,(*номер COM-порта*)
TimeOut:=TimeOut , (*Таймаут T#50ms*)
Buffer:=Buffer , (* буфер данных *)
Complete=>cmpl , (* скопировать признак завершения операции *)
Exception=>err , (* скопировать регистр ошибок *)
ByteCnt=>DataSize ); (*кол-во считанных байтов *)
(*если установлен признак завершения операции, то *)
IF cmpl THEN
IF err=0 THEN (*Если нет ошибок, то получаем данные из буфера типа FLOAT*)
DEP[arrDep[i]].Ua:=GET_R(buffer[0],buffer[1],buffer[2],buffer[3]);
DEP[arrDep[i]].Ub:=GET_R(buffer[4],buffer[5],buffer[6],buffer[7]);
DEP[arrDep[i]].Uc:=GET_R(buffer[8],buffer[9],buffer[10],buffer[11]);
DEP[arrDep[i]].Ia:=GET_R(buffer[12],buffer[13],buffer[14],buffer[15]);
DEP[arrDep[i]].Ib:=GET_R(buffer[16],buffer[17],buffer[18],buffer[19]);
DEP[arrDep[i]].Ic:=GET_R(buffer[20],buffer[21],buffer[22],buffer[23]);
DEP[arrDep[i]].Freq:=GET_R(buffer[24],buffer[25],buffer[26],buffer[27]);
DEP[arrDep[i]].Ratio:=GET_R(buffer[28],buffer[29],buffer[30],buffer[31]);
comm_ok[arrDep[i]]:=1;
ELSE
comm_ok[arrDep[i]]:=0;
END_IF
master1:=1; (*переходим к выполнению следующего ФБ*)
END_IF
1:
get_modbus(
Enable:=enabl , (* разрешение работы блока *)
Mode:=MB_RTU , (*режим передачи*)
DevAddr:=adrDep[i], (*адрес*)
FirstAddr:=31000 , (*номер регистра*)
Quantity:=19, (*количество регистров*)
ComHandle:=Settings.Port ,(*номер COM-порта*)
TimeOut:=TimeOut , (*Таймаут T#50ms*)
Buffer:=Buffer , (* буфер данных *)
Complete=>cmpl , (* скопировать признак завершения
операции *)
Exception=>err , (* скопировать регистр ошибок *)
ByteCnt=>DataSize ); (*кол-во считанных байтов *)
(*если установлен признак завершения операции, то *)
IF cmpl THEN
IF err=0 THEN (*Если нет ошибок, то получаем данные из буфера типа BOOL*)
DEP[arrDep[i]].IN1:=buffer[1].0;
DEP[arrDep[i]].IN2:=buffer[3].0;
DEP[arrDep[i]].IN3:=buffer[5].0;
DEP[arrDep[i]].IN4:=buffer[7].0;
DEP[arrDep[i]].IN5:=buffer[9].0;
DEP[arrDep[i]].IN6:=buffer[11].0;
DEP[arrDep[i]].IN7:=buffer[13].0;
DEP[arrDep[i]].Enable:=buffer[15].0;
DEP[arrDep[i]].Ua_ON:=buffer[17].0;
DEP[arrDep[i]].Ub_ON:=buffer[19].0;
DEP[arrDep[i]].Uc_ON:=buffer[21].0;
DEP[arrDep[i]].phaseCross:=buffer[23].0;
DEP[arrDep[i]].MT3_phA:=buffer[25].0;
DEP[arrDep[i]].MT3_phB:=buffer[27].0;
DEP[arrDep[i]].MT3_phC:=buffer[29].0;
DEP[arrDep[i]].Cntr_CH_STAT:=buffer[31].0;
DEP[arrDep[i]].Cntr_CH_RSLT:=buffer[33].0;
DEP[arrDep[i]].power1:=buffer[35].0;
DEP[arrDep[i]].power2:=buffer[37].0;
comm_ok[arrDep[i]]:=1;
ELSE
comm_ok[arrDep[i]]:=0;
END_IF
master1:=2; (*переходим к выполнению следующего ФБ*)
END_IF
2:
get_modbus(
Enable:=enabl , (* разрешение работы блока *)
Mode:=MB_RTU , (*режим передачи*)
DevAddr:=adrDep[i] , (*адрес*)
FirstAddr:=33000 , (*номер регистра*)
Quantity:=14, (*количество регистров*)
ComHandle:=Settings.Port ,(*номер COM-порта*)
TimeOut:=TimeOut , (*Таймаут T#50ms*)
Buffer:=Buffer , (* буфер данных *)
Complete=>cmpl , (* скопировать признак завершения
операции *)
Exception=>err , (* скопировать регистр ошибок *)
ByteCnt=>DataSize ); (*кол-во считанных байтов *)
(*если установлен признак завершения операции, то *)
IF cmpl THEN
IF err=0 THEN (*Если нет ошибок, то получаем данные из буфера типа DWORD*)
DEP[arrDep[i]].c1:=GET_D(buffer[0],buffer[1],buffer[2],buffer[3]);
DEP[arrDep[i]].c2:=GET_D(buffer[4],buffer[5],buffer[6],buffer[7]);
DEP[arrDep[i]].c3:=GET_D(buffer[8],buffer[9],buffer[10],buffer[11]);
DEP[arrDep[i]].c4:=GET_D(buffer[12],buffer[13],buffer[14],buffer[15]);
DEP[arrDep[i]].c5:=GET_D(buffer[16],buffer[17],buffer[18],buffer[19]);
DEP[arrDep[i]].c6:=GET_D(buffer[20],buffer[21],buffer[22],buffer[23]);
DEP[arrDep[i]].c7:=GET_D(buffer[24],buffer[25],buffer[26],buffer[27]);
comm_ok[arrDep[i]]:=1;
ELSE
comm_ok[arrDep[i]]:=0;
END_IF
master1:=0;
i:=i+1;
IF i>devnumb THEN
i:=1;
END_IF
END_IF
END_CASE
IF err <> 0 THEN
enabl := FALSE;
END_IF
END_IF
END_FUNCTION_BLOCK

Еще хотел бы написать еще об одной интересной штучке, подсмотрел на форуме, но хочу сохранить еще раз - логика для отображения времени скана программы:


Простейший принцип - фиксируется время каждого цикла и отнимается от времени предыдущего цикла - сработает на любом другом ПЛК, интересно даже проверить это потом как-нибудь.

Еще одна интересная фишка - есть и в документации и снова как всегда пишу тут чтобы не искать в документации:

Как видно на картинке можно задать разные цвета заливки для блока с восклицательным знаком - при разных условиях цвет будет разный. При этом удобно использовать массив цветов (в данном случае cell_err, причем обратить внимание на тип DINT - цвет требует больше памяти чем просто INT) и цикл для задания цветов для нескольких устройств. Конечно же даже если условий сработает несколько, то цвет останется в итоге по последнему условию, но в данном случае такое поведение не принципиально, потому что при любом раскладе требуется обратить внимание на состоянии ячейки. Чтобы при нормальной работе не отображать восклицательный знак - его показ можно скрыть по условию если цвет будет равен нулю, но в конструкции добавляется NOT - потому что свойство невидимости (или тогда <>0 без NOT)
.cell_err[1]=0
И еще кое-что. Для того чтобы реализовать мигающий красный восклицательный знак, пришлось добавить мигающий бит в программу. Сначала сделал на таймерах ,а потом увидел что есть готовый блок в библиотеке Utils:


Комментариев нет:

Отправить комментарий