asys
asys

Reputation: 731

Multi independent input timer

Imagine that we have independent boolean variables that can occur independently. Now, if at least one of the variables occurs for a certain period of time, an alert will be activated. The common solution is that we use a timer for each variable.

Is there an optimal solution that can only be used with a single timer?

Example for 2 variables with 1 second as passed time:

VAR
    Timer1,Timer2:TON;
    bVar1,bVar2:BOOL;
    tSetDelay:TIME:=T#1S;
    Alarm:BOOL;
END_VAR

Timer1(IN:=bVar1,PT:=tSetDelay);
Timer2(IN:=bVar2,PT:=tSetDelay);

IF Timer1.Q RO Timer2.Q THEN
     Alarm:=TRUE;
END_IF

If we use OR it won't be true

Timer(IN:=bVar1 OR bVar2,PT:=tSetDelay);

cause the vars may have overlap in the same tSetDelay time, it means that they may happen less than the delay, but the timer output be true.

In this example, we have only 2 variables, but if we have many more variables it will be more important to find a better solution.

Upvotes: 0

Views: 329

Answers (2)

Sergey Romanov
Sergey Romanov

Reputation: 3080

You cannot do this without individual timers. Here is how I would approach this.

Set global constant and variables

VAR_GLOBAL
    glbEvents: ARRAY[1..c_EventsNum] OF stMyEvents; (* Events array *)
END_VAR
VAR_GLOBAL CONSTANT
    c_EventsNum: INT := 3; (* Number of events *)
END_VAR

Now you can map glbEvents[1].State to inputs of PLC

Define new structure

TYPE stMyEvents : STRUCT
        State : BOOL; (* Event current state *)
        StateM : BOOL; (* Memmory of state in previouse cycle to get the trigger *)
        Timer: TON := (PT := T#1S); (* Timer *)
    END_STRUCT
END_TYPE

Create function

FUNCTION ProcessEvents : BOOL
    VAR
        iCount: INT; (* Counter *)
    END_VAR

    FOR iCount := 1 TO c_EventsNum DO
        glbEvents[iCount].Timer(IN := glbEvents[iCount].State);
        IF glbEvents[iCount].Timer.Q THEN
            ProcessEvents := TRUE;
            EXIT;
        END_IF;
    END_FOR;
END_FUNCTION

Implementation

PROGRAM PLC_PRG
    VAR
        xAlarm: BOOL; (* Alarm *)
    END_VAR

    IF ProcessEvents() THEN
        // Alarm happened        
    END_IF;
END_PROGRAM

With this approach although you do not have 1 Timer, you have certain level of abstraction that makes it more flexible to support and modify.

But if you absolutely do not want to have so many TON timers, you can create your own timer. It will be single timer in one FB

VAR_GLOBAL
    glbEvents: ARRAY[1..c_EventsNum] OF stMyEvents; (* Events array *)
END_VAR
VAR_GLOBAL CONSTANT
    c_EventsNum: INT := 3; (* Number of events *)
END_VAR

TYPE stMyEvents : STRUCT
        State : BOOL; (* Event current state *)
        StateM : BOOL; (* Memmory of state in previouse cycle to get the trigger *)
        TimeM: TIME; (* Memmory of event start time *)
    END_STRUCT
END_TYPE

FUNCTION_BLOCK ProcessEvents
    VAR
        iCount: INT; (* Counter *)
    END_VAR
    VAR_OUTPUT
        Q: BOOL; (* Impulse if alarm *)
    END_VAR

    Q := FALSE;
    FOR iCount := 1 TO c_EventsNum DO
        (* Get raising edge and save the timer *)
        IF glbEvents[iCount].State AND NOT glbEvents[iCount].StateM THEN
            glbEvents[iCount].TimeM := TIME();    
        END_IF;
        glbEvents[iCount].StateM := glbEvents[iCount].State;

        (* If event is low reset timer *)
        IF NOT glbEvents[iCount].State THEN
            glbEvents[iCount].TimeM := T#0S; 
        END_IF;
        
        (* if more than a second make impuls on Q *)
        IF (glbEvents[iCount].TimeM > T#0S) AND ((TIME() - glbEvents[iCount].TimeM) >= T#1S) THEN
            Q := TRUE;
            glbEvents[iCount].TimeM := T#0S;    
        END_IF;
    END_FOR;
END_FUNCTION_BLOCK

PROGRAM PLC_PRG
    VAR
        fbeventProcess: ProcessEvents; (* function block *)
    END_VAR

    fbeventProcess();
    IF fbeventProcess.Q THEN
        // Alarm happened        
    END_IF;
END_PROGRAM

Upvotes: 1

Steve
Steve

Reputation: 1018

It it not possible to manage this with a single timer. Given the that you want to ensure that the triggering source is a single variable (without any logical interference from additional variables), each variable must be compared against it own history (timer).

Recommended path forward would be to build an Alarm function block that handles the majority of the logic here in a consistent manner. An example of this kind of function block is below:

fb_AlarmMonitor

VAR_INPUT
  monitor             : ARRAY [ 0..7 ] OF POINTER TO BOOL;
  duration            : TIME;
END_VAR
VAR_OUTPUT
  alarm               : BOOL;
END_VAR
VAR
  timers              : ARRAY [ 0..7 ] OF TON;
END_VAR
VAR_TEMP
  i                   : UDINT;          
END_VAR


alarm := false;
FOR i := 0 TO 7 BY 1 DO
  //  Only run process if linked boolean
  IF monitor[ i ] <> 0 THEN 
    timers[ i ](    in  := monitor[ i ]^,
                    pt  := duration );
    alarm := timers[ i ].Q OR alarm;
  END_IF
END_FOR

Implementation

VAR
//  Alarm flags
flag_1, flag_2, flag_3
            :   BOOL;

//  Pointers to alarm flags
flag_array  :   ARRAY [ 0..7 ] OF POINTER TO BOOL
                :=[ ADR( flag_1 ),
                    ADR( flag_2 ),
                    ADR( flag_3 ) ];

//  Monitoring flags                        
monitor     : fb_AlarmMonitor
                :=( monitor     := flag_array,
                    duration    := T#10S );   
END_VAR

monitor();

Upvotes: 1

Related Questions