UniVent - программное обеспечение в рамках пакета CRW-DAQ для вентилей, клапанов, насосов и т.д. Далее мы будем называть устройство "вентилем", хотя реально это может быть также клапан, затвор, насос или другое устройство, сходное с ними по логике работы.
Работу условного "вентиля" в двух словах можно описать так.
Имеется устройство, которое можно включить (открыть) и выключить (закрыть).
Условимся, что включение\выключение (ON=1\0) означает намерение, а открытие\закрытие - факт срабатывания концевиков.
Процесс открытия\закрытия может занимать время, а также может завершаться неудачей (сбой аппаратуры).
При этом устройство может находиться в промежуточном положении (включен, но еще не открыт).
Для контроля состояния устройство использует от 0 до 2 концевиков, на открытие (OPENED=1)
и на закрытие (CLOSED=1). В промежуточных состояниях или при сбое аппаратуры возможны
логически недопустимые состояния, например, "включен, открыт и закрыт" (концевики замкнуло).
Если концевик один, то концевик на закрытие программно эмулируется как "CLOSED=NOT OPENED".
Если концевиков нет, программно эмулируется "OPENED=ON, CLOSED=NOT ON".
С точки зрения исполнительной части имеется 2 канала управления - открыть,закрыть (OPEN,CLOSE)
и допускается три типа (режима работы) - позиционный, позиционный с нейтралью и импульсный.
Позиционный способ управления (например, клапан с одним соленоидом и возвратной пружиной) означает,
что управляющие сигналы формируются по состоянию ON как "OPEN=ON, CLOSE=NOT ON".
Позиционный с нейтралью (например, клапан с двумя соленоидами на открытие и закрытие)
задерживает подачу сигналов управления на заданное время после включения\выключения,
так что при включении\выключении система не сразу меняет состояние управления OPEN,CLOSE,
но на указанное время находится в нейтральном положении OPEN=0,CLOSE=0.
Наконец, при работе в импульсном режиме (например, насос с магнитными пускателями)
программа выдает импульс заданной длительности OPEN=1 при включении (когда ON меняется с 0 на 1)
или CLOSE=1 при выключении (когда ON меняется с 1 на 0).
При работе с импульсом прекращение сигнала OPEN,CLOSE происходит или по времени, если нет концевиков,
(случай насоса с магнитным пускателем) или по времени и концевику (что раньше сработает),
если есть концевики (случай затвора с двумя двигателями и двумя концевиками).
После прекращения импульса оба сигнала возвращаются в нейтральное состояние OPEN=0,CLOSE=0.
Наконец, устройство может быть заблокировано внешним сигналом.
При этом блокировка может быть запретительной и принудительной.
Запретительная не разрешает оператору менять состояние ON, но само состояние
не определяется фактом блокировки (заблокирован и не меняется).
Принудительная блокировка может не только запрещать менять состояние ON,
но и принудительно задавать его, как только возник сигнал блокировки
(заблокирован и принудительно выключен).
При этом сигнал блокировки согласован с логикой управления.
Например, если принудительная блокировка возникла в момент импульса пускателя,
надо сначала дождаться окончания операции, а потом уже менять состояние ON.
Ну, а теперь подробности...
Каждый условный "вентиль" связан с двумя цифровыми входами и одним цифровым выходом:
Link DigitalInput 0 with curve VENT.DI inverted bit 0 ; VENT OPENED
Link DigitalInput 1 with curve VENT.DI inverted bit 1 ; VENT CLOSED
Link DigitalOutput 0 with curve VENT.DO
Здесь предполагается, что состояние концевиков считывается в кривой VENT.DI. Номера входов DigitalInput должны быть заданы в программе при инициализации (см.ниже). Номер входа OPENED,CLOSED может меняться, но порядок (n,n+1) должен сохраняться. Номера битов подключаемой кривой и флаг инверсии позволяют гибко менять логику подключения в зависимости от имеющейся аппаратуры. Например, если вентиль имеет один концевик, то можно подать прямой бит на вход OPENED, а инверсный - на вход CLOSED. Нужно лишь следить, чтобы подаваемые сигналы правильно отражали логику работы: OPENED=1 означает факт открытия вентиля, CLOSED=1 - факт закрытия.
Кривая VENT.DO содержит 6 битов, которые можно использовать для управления:
Конструкция Link DigitalInput N with Data inverted bit M и наличие нескольких типов вентилей и сигналов управления позволяет реализовать весьма гибкую логику при подключении аппаратуры. Так, клапан может использовать непосредственно сигнал ON, а насос с двумя магнитными пускателями использует сигналы OPEN,CLOSE.
Каждый условный "вентиль" имеет также 5 тегов:
VENT.BTN = integer 0 ; Valve button state, 0..15
VENT.TYP = integer 2 ; Valve type, 0..2
VENT.CHK = integer 2 ; Valve check flag, 0..2
VENT.BLK = integer 0 ; Valve block flag
VENT.WDT = integer 1000 ; Valve watchdog timer, ms
VENT.BTN отображает состояние вентиля и имеет 4 бита:
Отображение VENT.BTN происходит следующим образом: (изображены соответственно насос магниторазрядный, насос форвакуумный, насос турбомолекулярный, клапан вертикально\горизонтально и затвор вертикально\горизонтально)
- вентиль отключен, не заблокирован, не открыт и не закрыт.
Промежуточное состояние или ошибка.
- вентиль включен, не заблокирован, не открыт и не закрыт.
Промежуточное состояние или ошибка.
- вентиль отключен, не заблокирован, открыт и не закрыт.
Промежуточное состояние или ошибка.
- вентиль включен, не заблокирован, открыт и не закрыт.
Нормальное состояние открытого клапана.
- вентиль отключен, не заблокирован, не открыт и закрыт.
Нормальное состояние закрытого клапана.
- вентиль включен, не заблокирован, не открыт и закрыт.
Промежуточное состояние или ошибка.
- вентиль отключен, не заблокирован, открыт и закрыт.
Скорее всего это ошибка (концевики замкнуло).
- вентиль включен, не заблокирован, открыт и закрыт.
Скорее всего это ошибка (концевики замкнуло).
- вентиль отключен, заблокирован, не открыт и не закрыт.
Промежуточное состояние или ошибка.
- вентиль включен, заблокирован, не открыт и не закрыт.
Промежуточное состояние или ошибка.
- вентиль отключен, заблокирован, открыт и не закрыт.
Промежуточное состояние или ошибка.
- вентиль включен, заблокирован, открыт и не закрыт.
Нормальное состояние открытого заблокированного клапана.
- вентиль отключен, не заблокирован, не открыт и закрыт.
Нормальное состояние закрытого заблокированного клапана.
- вентиль включен, заблокирован, не открыт и закрыт.
Промежуточное состояние или ошибка.
- вентиль отключен, заблокирован, открыт и закрыт.
Скорее всего это ошибка (концевики замкнуло).
- вентиль включен, не заблокирован, открыт и закрыт.
Скорее всего это ошибка (концевики замкнуло).
Можно заметить, что большинство состояний (кроме 3,4,11,12) являются временными (промежуточными) или ошибочными и отображаются желтым цветом, для привлечения внимания.
VENT.CHK является константой (не меняется в процессе работы) и принимает 3 значения:
Возможность манипуляции с номерами и инверсия битов на входах DigitalInput дают богатые возможности для адаптации при подключении аппаратуры.
VENT.TYP является константой (не меняется в процессе работы) и принимает 3 значения:
VENT.BLK является константой (не меняется в процессе работы) и принимает 4 значения:
VENT.WDT является константой (не меняется в процессе работы) и задает время нейтрального состояния (при TYP=1) или управляющего импульса (при TYP=2) в миллисекундах.
Для реализации "универсального вентиля" в программе должны присутствовать примерно такие фрагменты кода:
program vent_ctrl; // Сервер для управления вентилями
...
type
TTagRef = record tag,ndi,ndo:Integer; val:Real; end;
TValveRec = record BTN,TYP,CHK,BLK,WDT:TTagRef; WDTms:Real; end;
...
var VENT : TValveRec; { Valve record }
...
{
Initialize valve tags.
}
procedure Valve_InitTag(var Valve:TValveRec;Prefix:String);
begin
InitTag(Valve.BTN.tag, Prefix+'.BTN', 1);
InitTag(Valve.TYP.tag, Prefix+'.TYP', 1);
InitTag(Valve.CHK.tag, Prefix+'.CHK', 1);
InitTag(Valve.BLK.tag, Prefix+'.BLK', 1);
InitTag(Valve.WDT.tag, Prefix+'.WDT', 1);
end;
{
Initialize valve values.
}
procedure Valve_InitVal(var Valve:TValveRec);
begin
Valve.BTN.val:=iAnd(iNot(iGetTag(Valve.BTN.tag)),1);
Valve.TYP.val:=valEmpty;
Valve.CHK.val:=valEmpty;
Valve.BLK.val:=valEmpty;
Valve.WDT.val:=valEmpty;
Valve.WDTms:=0;
end;
{
Calculate time since tag switch on
}
procedure CalcWdtTime(var wdt:Real);
begin
if wdt<=0 then wdt:=msecnow;
end;
{
Calculate time since tag switch on
}
function GetWdtTime(wdt:Real):Real;
begin
if wdt>0 then GetWdtTime:=msecnow-wdt else GetWdtTime:=0;
end;
{
Check if bit modified
}
function ModifiedBit(val1,val2:Real; mask:Integer):Integer;
begin
ModifiedBit:=Ord( iAnd(Trunc(val1),mask)<>iAnd(Trunc(val2),mask) );
end;
{
Readout/control/update valves.
}
procedure Valve_Ctrl(var Valve:TValveRec; ndi,ndo,dim:Integer);
var diw,dow,btn,typ,chk,blk,wdt:Integer;
begin
{
Readout Digital Input Word diw: bit0:opened, bit1:closed
Readout sensor button state btn: bit0:ON
Readout valve control type 0:Level, 1:Level with dead time, 2:Pulse
Readout check flag chk: 0:no sensor, 1:open sensor, 2:open+close sensors
Readout block flag blk: 0:no blocks, 1:disabled, 2:disabled+OFF, 3:disabled+ON
Readout watchdog timer, ms
}
diw:=Trunc(DiWord(ndi,2));
btn:=iAnd(iGetTag(Valve.BTN.tag),1);
typ:=iGetTag(Valve.TYP.tag);
chk:=iGetTag(Valve.CHK.tag);
blk:=iGetTag(Valve.BLK.tag);
wdt:=iGetTag(Valve.WDT.tag);
{
Apply blocking:
BLK=0 : no blocking, work normal
BLK=1 : disable sensor clicks
BLK=2 : disable and force OFF
BLK=3 : disable and force ON
Note that blocking working only when watchdog is turned off.
}
if Valve.WDTms=0 then begin
if blk=2 then btn:=0;
if blk=3 then btn:=1;
end;
{
Apply check state:
CHK=2 : bit0:ON, bit1:opened, bit2:closed 2 sensors (opened,closed)
CHK=1 : bit0:ON, bit1:opened, bit2:not(opened) 1 sensor (opened)
CHK=0 : bit0:ON, bit1:ON, bit2:not(ON) no sensors, simulation
}
if chk=0 then btn:=iAnd(btn,1)+2*iAnd(btn,1)+4*iAnd(iNot(btn),1);
if chk=1 then btn:=iAnd(btn,1)+2*iAnd(diw,1)+4*iAnd(iNot(diw),1);
if chk=2 then btn:=iAnd(btn,1)+2*iAnd(diw,3);
btn:=btn+8*Ord(blk>0);
{
If ON/OFF bit changed, start WDT timer
}
if ModifiedBit(Valve.BTN.val,btn,1)>0
then CalcWdtTime(Valve.WDTms);
{
If WDT timer timeout finished, stop WDT timer.
}
if Valve.WDTms>0 then
if GetWdtTime(Valve.WDTms)>wdt
then Valve.WDTms:=0;
{
Find Digital Output Word:
TYP=0 : bit0:ON, bit1:opened, bit2:closed, bit3:blocked, bit4:ON, bit5:not(ON)
TYP=1 : bit0:ON, bit1:opened, bit2:closed, bit3:blocked, bit4:opening, bit5:closing with dead time
TYP=2 : bit0:ON, bit1:opened, bit2:closed, bit3:blocked, bit4:opening, bit5:closing pulse
}
dow:=btn+16*iAnd(btn,1)+32*iAnd(iNot(btn),1);
if typ=1 then dow:=btn+Ord(Valve.WDTms=0)*(16*iAnd(btn,1)+32*iAnd(iNot(btn),1));
if typ=2 then dow:=btn+Ord(Valve.WDTms>0)*(16*iAnd(btn,1)+32*iAnd(iNot(btn),1));
{
Write result control values
}
b:=iSetTag(Valve.BTN.tag,btn);
UpdateDo(ndo,time,dow);
{
Update DIM services
}
if dim>0 then
if ShouldRefresh(Valve.BTN.val, iGetTag(Valve.BTN.tag))+
ShouldRefresh(Valve.TYP.val, iGetTag(Valve.TYP.tag))+
ShouldRefresh(Valve.CHK.val, iGetTag(Valve.CHK.tag))+
ShouldRefresh(Valve.BLK.val, iGetTag(Valve.BLK.tag))+
ShouldRefresh(Valve.WDT.val, iGetTag(Valve.WDT.tag))>0
then DIM_UpdateTag(Valve.BTN.tag,'');
end;
...
{
Analyse data coming from standard input.
}
procedure StdIn_Process(Data:string);
var cmd,arg:String; tag:Integer;
procedure Valve_Refresh(var Valve:TValveRec);
var r:Real;
begin
if Valve.WDTms=0 then
if iGetTag(Valve.BLK.tag)=0 then
if TypeTag(tag)=1 then
if Valve.BTN.tag=tag then begin
r:=rVal(ExtractWord(2,arg));
if not IsNan(r) then begin
b:=iSetTag(tag,iOr(iAnd(iGetTag(tag),iNot(1)),iAnd(Trunc(r),1)));
DIM_UpdateTag(tag,'');
end;
Success(cmd+' '+arg);
Data:='';
end;
end;
begin
cmd:='';
arg:='';
if Length(Data)>0 then
if Data[1]='@' then begin
cmd:=ExtractWord(1,Data);
arg:=Copy(Data,Pos(cmd,Data)+Length(cmd)+1);
{
Example: @AssignTag VENT.BTN=1
}
if IsSameText(cmd,'@AssignTag') then begin
tag:=FindTag(ExtractWord(1,arg));
if TypeTag(tag)>0 then begin
Valve_Refresh(VENT);
end;
end else
{}
if Length(Data)>0 then begin
Trouble(' Unrecognized command "'+Data+'".');
Data:='';
end;
end;
cmd:='';
arg:='';
end;
...
begin
if RunCount=1 then begin
Valve_InitTag(VENT,'VENT'); // Инициализация тегов вентиля
Valve_InitVal(VENT); // Инициализация флагов состояния вентиля
VENT.BTN.ndi:=0; // Из каких DigitalInput считывать OPENED,CLOSED
VENT.BTN.ndo:=0; // На какой DigitalOutput выдавать управляющий сигнал
end else begin
{
Process standard input...
}
while StdIn_Readln(StdIn_Line) do StdIn_Process(StdIn_Line);
{
Main control routine
}
Valve_Ctrl(VENT, VENT.BTN.ndi, VENT.BTN.ndo, 1); // Здесь вся логика работы вентиля в цикле опроса
end;
end.
program vent_gui; // Клиент для интерфейса c пользователем
var tagVENT_BTN:Integer; // Тег кнопки VENT.BTN
tagVENT_BLK:Integer; // Тег блокировки VENT.BLK
begin
...
if ClickTag=tagVENT_BTN then begin
// При нажатии кнопки сенсора шлем серверу vent_ctrl сообщение
// типа @AssignTag VENT.BTN=0/1 с инверсией 0 бита ON
b:=DevSendMsg('&vent_ctrl @AssignTag '+NameTag(tagVENT_BTN)+'='+Str(Ord(not IsBit(iGetTag(tagVENT_BTN),0)));
end;
...
b:=iSetTag(tagVENT_BLK,1); // Запретительная блокировка устройства VENT, 2 - с принудительным отключением
...
end.