Monday, March 10, 2014

Input, Output, and Indirect - Oh My!

This will be a little confusing, but if you try to take in small bytes, and just ignore some of the details, you should get through it.

NOTE: anything in parenthesis is an example value, replace with a value needed for your project.

  1. Define a variable in your IEC program. A floating point (SP), not retained, Input.
  2. Create (if not already) an IO channel, Float, Input (i.e., #1)
  3. Modify the Parameters of the selected Input(#1) of the channel to 255.253.1 i.e., Indirect
  4. Map the IO channel to the IEC variable (SP)
  5. Set the global variable NUMREGISTERS to  (1).
Now you have several different registers defined in you program.
VariableNameValueParameter
ISaGRAF VariableSP13.57User:9012
IO channeltx.ySP255.253.1
Totalflow Register91.9.1813.57
Indirect91.253.17.4.1
IO/AI17.4.113.57

Warning: Do not use Get/Set (DINIT, REAL, STRING, UDINT) in the main loop. Only use them for on demand read /writes.  They are CPU intense and 100x slower than the I/O method. (updated 4/21/2014)

Tuesday, February 25, 2014

Retain or not to retain? That is the question.

When should a variable be marked retain?

  • A configuration parameter, that you want to remember the user value after a warm start.
  • A event or cycle log value, i.e., Who, What, When, Why, How.
  • An internal state value, if you want to be able to restore the program to the previous state.

When should a variable be NOT retained?

  • Values coming in from the outside every second.
  • Values going out every second. (90%)
  • Internal states, that will be reset by the warm start logic.
  • temporary variables: loop counters, intermediate results.
See the post on Cold/Warm start for code example.

Monday, February 24, 2014

Contract Hour - Once a day summing

Contract Hour How do you do a daily summary, at whatever the user wants for contract hour? Similar code can be used to do Hourly Summing.
FUNCTION CHOURTIME:
  • Returns CHOUR_NOT when you are not in contract hour.
  • Returns CHOUR_JUST when you are in contract hour and need to sum. i.e., first second of hour.
  • Returns CHOUR_DONE when you are in contract hour and have already done a sum.
  • Variables: ContractHourStatus should be a non-retained global and initialized to CHOUR_NOT at warm boot.
  • ContractHour should be a retained global and initialized to a default value at cold boot. Should be called once a second.
NSEC := ANY_TO_DINT(DateTime);
Hour := ANY_TO_UDINT(MOD ( NSEC / 3600, 24) );

IF  (ContractHour = Hour) THEN
    IF (ContractHourStatus = CHOUR_NOT) THEN
        ChourTime:= CHOUR_JUST;
    ELSE 
        ChourTime:= CHOUR_DONE;
    END_IF; 
ELSE 
    ChourTime:= CHOUR_NOT;
END_IF; 

Variables-Local

NameTypeDirAttrRetained
NSECDINTVarR/WNo
HOURUDINTVarR/WNo
ContractHourUDINTVarInputReadNo
ContractHourStatUDINTVarInputR/WNo
ChourTimeUDINTVarOutputWNo

Defined words (#define in C++)


Defined WordsValue
CHOUR_NOT0
CHOUR_JUST1
CHOUR_DONE2

Cold/Warm boot.

Example Main Program

(* setup initial retain values - first time.
   They will be read back from config file after a first time.*) 
 
IF (Cold = FALSE) THEN
    Enable := FALSE;
    HiHiLimit := 100.0;
    HiLimit := 90.0;

    ReturnStatus := Event_Clear();
    ReturnStatus := Event_Log(State_Cold, 0, 0.0);
    Cold := TRUE;
END_IF;

(* setup non-retain variables after a warm start *)
IF (Warm = False) THEN
    (* for each input clear the state and set the output to ON *)
    FOR pos := 1 TO ANY_TO_DINT(Input_Size) DO
        State[pos] := State_OK;
        Output[pos] := 1;
    END_FOR;

    ReturnStatus := Event_Log(State_Warm, 0, 0.0);
    Warm := TRUE;
END_IF;

Variables

NameTypeDirAttrRetained
ColdBoolVarR/WYes
WarmBoolVarR/WNo

Defined words (#define in C++)

Defined WordsValue
State_Unused0
State_OK1
State_HiHi2
State_Cold3
State_Warm4

Log an Event

Here is a function to log a single event:

(* Datetime is a udint (uin32) that reads the system clock 0.9.0 as an ISaGRAF I/O *)
EVENT_LOG FUNCTION (Code, Input, Value)

(* CODE - an integer to describe the event (hosts like integers) *)
(* Input - which input caused the event *)
(* Value - the value of the input that caused the event *)
(* Stamp - Date/Time stamp of when the event occured *)

(* Move all data down by one row – start at bottom, filling from above *)
  FOR pos := ANY_TO_DINT(Event_Log_Size) TO 2 BY -1 DO
    Event_Code[pos]  := Event_Code[pos - 1];
    Event_Input[pos] := Event_Input[pos - 1];
    Event_Value[pos] := Event_Value[pos - 1];
    Event_Stamp[pos] := Event_Stamp[pos - 1];
  END_FOR;

(* place new log in first row *)
  Event_Code[1]  := Code;
  Event_Input[1] := Input;
  Event_Value[1] := Value;
  Event_Stamp[1] := DateTime;
  Event_Log := TRUE;    (* all functions must return a value *)

Here is the initialization code for cold boot

(* ************ at cold boot - clear the log ************** *)
  FOR pos := 1 TO ANY_TO_DINT(Event_Log_Size) DO
    Event_Code[pos]  := 0;
    Event_Input[pos] := 0;
    Event_Value[pos] := 0.0;
    Event_Stamp[pos] := 0;
  END_FOR;

Timers - Pulse a DO and Log cycle time.

Method 1 - Assumes a 1 second cycle

(* init timers to zero at warm start *)
    Valve_Open_Timer = 0;

(* Open a Valve with a Pulse *)
    Valve := 1;
    Valve_Open_Timer = 1;

(* Pulse DO Timer -- if Timer is zero not active *)
IF Valve_Open_Timer <= DO_Time_Delay AND Valve_Open_Timer > 0 THEN
    Valve_Open_Timer := Valve_Open_Timer + 1;
    IF Valve_Open_Timer = DO_Time_Delay THEN
        Valve := 0;

        Valve_Open_Timer := 0;

    END_IF;
END_IF;

Example 2 - use system DateTime (seconds since 1/1/1970)

(* Datetime is a udint (uin32) that reads the system clock 0.9.0 as an ISaGRAF I/O *)
(* INIT clock at start of cycle *)
 StartCycleTime := DateTime;

(* Terminate clock at end of cycle *)
 CycleTime := DateTime - StartCycleTime;

State Machines

What is a State Machine?

A state machine is a why to keep track of where you are in the middle of a process. The example from current G4 is the plunger app. Its states are:
  1. Fail
  2. Start of Closed - waiting for plunger to fall
  3. waiting for the right conditions to open the valve.
  4. Start of Open - waiting for plunger to arrive.
  5. Plunger never arrived - Use blow valve to assist (not longer used due to EPA)
  6. Plunger Arrived - 1 sec state to record values at time of arrival.
  7. Afterflow - waiting for the right conditions to close the valve.

 How do I implement a state machine?

  • An integer variable is usually used to keep track of the current state. Optionally the state can ALSO be keep in a string for ease of displaying on the front panel or by host systems.
  • Expanding on its use in plunger, it is now common to add a Disabled state. This gives the host only one place to look for overall status.
  • Usually the state is not retained, but some processes need to know what happened at power down, so that recovery can be adjusted.
  • In it's simplest form one switch statement chooses which code to execute by the value of the current state.
  • More complex programs can use up to three switch statements
    1. Change of state at the beginning  to initialize a state
    2. Normal operation
    3. Change of state at the end to cleanup a state.

Example: 

IF Old_State <> Curr_State THEN 
    (* When state changes - Initialize new State *)
    CASE Curr_State of
       State_Open: 
            Statusbool := OpenInit();
       State_Close:
            StatusBool := CloseInit();
       State_Disabled:
            (* do nothing *)
       ELSE  (* Bad value reset Close *)
            Curr_State := State_Disabled;
    END_CASE;
    Old_State :=  Curr_State;
END_IF;
    CASE Curr_State of
    State_Open: 
         Statusbool := Open();
    State_Close:
         StatusBool := Close();
END_CASE;
CASE Old_State of     (* When state changes - Terminate new State *)
    State_Open: 
        Statusbool := OpenTerm();
    State_Close:
        StatusBool := CloseTerm();
END_CASE;
(* ********************* *)
OpenInit
    Open_Valve = TRUE;
(* ********************* *)
OPEN
    IF timetoclose THEN
        Curr_State = State_Close;
    END_IF;
(* ********************* *)
OpenTerm
    (* Record open time *)