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 битов, которые можно использовать для управления:

  1. ON - состояние кнопки "включен", задаваемое оператором при щелчке мышью. Этот бит означает намерение оператора включить\выключить устройство.
  2. OPENED - прочитанное состояние концевика "открыт".
  3. CLOSED - прочитанное состояние концевика "закрыт".
  4. BLOCKED - состояние сигнала блокировки.
  5. OPEN - управляющий сигнал на открытие.
  6. CLOSE - управляющий сигнал на закрытие.

Конструкция 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 бита:

  1. ON - Бит управления. Показывает, что оператор включил вентиль. Это, конечно, не обязательно значит, что вентиль в самом деле включился. Этот бит означает намерение включить или выключить вентиль. Бит отображается в виде утопленной кнопки.
  2. OPENED - Бит концевика "открыт". В случае вентиля с двумя концевиками (VENT.CHK=2) этот бит должен считываться из аппаратуры, как концевик на открытие, 1 означает что вентиль открылся. В случае клапана с одним концевиком (VENT.CHK=1) этот бит должен считываться из аппаратуры как концевик на включение, 1 означает что клапан открылся. В случае клапана без концевиков (VENT.CHK=0) этот бит принудительно присваивается равным состоянию бита ON, то есть как будто концевик есть и всегда мгновенно срабатывает. При этом цифровые входы не используются.
  3. CLOSED - Бит концевика "закрыт". В случае вентиля с двумя концевиками (VENT.CHK=2) этот бит должен считываться из аппаратуры как концевик на закрытие, 1 означает что вентиль закрылся. В случае клапана с одним концевиком (VENT.CHK=1) этот бит должен считываться из аппаратуры как инверсный концевик на включениетак что концевик на открытие одновременно работает как инверсный концевик на закрытие. В случае клапана без концевиков (VENT.CHK=0) этот бит принудительно присваивается равным инверсному состоянию бита ON, то есть как будто концевик есть и всегда мгновенно срабатывает.
  4. BLOCKED - Бит блокировки. Блокировка является внешним сигналом, задаваемым в теге VENT.BLK, как описано ниже.

Отображение VENT.BTN происходит следующим образом: (изображены соответственно насос магниторазрядный, насос форвакуумный, насос турбомолекулярный, клапан вертикально\горизонтально и затвор вертикально\горизонтально)

  1.               - вентиль отключен, не заблокирован, не открыт и не закрыт. Промежуточное состояние или ошибка.
  2.               - вентиль включен, не заблокирован, не открыт и не закрыт. Промежуточное состояние или ошибка.
  3.               - вентиль отключен, не заблокирован, открыт и не закрыт. Промежуточное состояние или ошибка.
  4.               - вентиль включен, не заблокирован, открыт и не закрыт. Нормальное состояние открытого клапана.
  5.               - вентиль отключен, не заблокирован, не открыт и закрыт. Нормальное состояние закрытого клапана.
  6.               - вентиль включен, не заблокирован, не открыт и закрыт. Промежуточное состояние или ошибка.
  7.               - вентиль отключен, не заблокирован, открыт и закрыт. Скорее всего это ошибка (концевики замкнуло).
  8.               - вентиль включен, не заблокирован, открыт и закрыт. Скорее всего это ошибка (концевики замкнуло).
  9.               - вентиль отключен, заблокирован, не открыт и не закрыт. Промежуточное состояние или ошибка.
  10.               - вентиль включен, заблокирован, не открыт и не закрыт. Промежуточное состояние или ошибка.
  11.               - вентиль отключен, заблокирован, открыт и не закрыт. Промежуточное состояние или ошибка.
  12.               - вентиль включен, заблокирован, открыт и не закрыт. Нормальное состояние открытого заблокированного клапана.
  13.               - вентиль отключен, не заблокирован, не открыт и закрыт. Нормальное состояние закрытого заблокированного клапана.
  14.               - вентиль включен, заблокирован, не открыт и закрыт. Промежуточное состояние или ошибка.
  15.               - вентиль отключен, заблокирован, открыт и закрыт. Скорее всего это ошибка (концевики замкнуло).
  16.               - вентиль включен, не заблокирован, открыт и закрыт. Скорее всего это ошибка (концевики замкнуло).

     Можно заметить, что большинство состояний (кроме 3,4,11,12) являются временными (промежуточными) или ошибочными и отображаются желтым цветом, для привлечения внимания.

VENT.CHK является константой (не меняется в процессе работы) и принимает 3 значения:

  1. Клапан без концевиков. Поскольку контроль по концевикам отсутствует, считается, что состояние открыт-закрыт меняется сразу же при включении-выключении кнопки. При этом биты OPENED, CLOSED принудительно устанавливаются в ON и NOT ON соответственно. То есть работа происходит так, будто концевики на открытие и закрытие работают мгновенно и точно соответствуют кнопке включения вентиля. При этом состояние цифровых входов OPENED,CLOSED игнорируется.
  2. Вентиль с одним концевиком. Значение OPENED считывается из DigitalInput, а значение CLOSED принудительно устанавливается в NOT OPENED. То есть работа происходит так, будто концевик на закрытие работает мгновенно в противофазе с концевиком на открытие. При этом состояние цифрового входа CLOSED игнорируется.
  3. Вентиль с двумя концевиками. Значение OPENED считывается из DigitalInput n+0. Значение CLOSED считывается из DigitalInput n+1. В нормальном состоянии должно быть OPENED=ON, CLOSED=NOT ON. Однако временно могут возникать промежуточные состояния, например, вентиль включили, но концевик еще не сработал.

Возможность манипуляции с номерами и инверсия битов на входах DigitalInput дают богатые возможности для адаптации при подключении аппаратуры.

VENT.TYP является константой (не меняется в процессе работы) и принимает 3 значения:

  1. Вентиль позиционный. Сигнал управления формируется как OPEN=ON, CLOSE=NOT ON. Например, клапан с одним соленоидом и возвратной пружиной, который открывается или закрывается по уровню сигнала OPEN.
  2. Вентиль позиционный с нейтралью (промежуточным состоянием). Сигнал управления формируется как OPEN=ON, CLOSE=NOT ON, но только с той разницей, что после включения\выключения в течение VENT.WDT миллисекунд вентиль находится в нейтральном (промежуточном) состоянии OPEN=0, CLOSE=0. Например, клапан с двуми соленоидами на открытие и закрытие. Поскольку одновременно два соленоида не должны включаться, требуется промежуточное состояние (нейтраль).
  3. Вентиль с двумя концевиками. Значение OPENED считывается из DigitalInput n+0. Значение CLOSED считывается из DigitalInput n+1. В нормальном состоянии должно быть OPENED=ON, CLOSED=NOT ON. Однако временно могут возникать промежуточные состояния, например, вентиль включили, но концевик еще не сработал.

VENT.BLK является константой (не меняется в процессе работы) и принимает 4 значения:

  1. Все в порядке, нормальная работа, нет блокировки.
  2. Запретительная блокировка. Состояние ON не меняется, но кнопка блокируется, так что до снятия блокировки оператор не может нажать кнопку и включить\выключить устройство.
  3. Заблокирован и принудительно выключен. Кроме запрета на нажатие кнопки принудительно устанавливается ON=0 для отключения устройства.
  4. Заблокирован и принудительно включен. Кроме запрета на нажатие кнопки принудительно устанавливается ON=1 для включения устройства.

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.