(* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Buffer Management' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION _BUFFER_CLEAR : BOOL VAR_INPUT PT : POINTER TO BYTE; SIZE : UINT; END_VAR VAR ptw : POINTER TO DWORD; temp: DWORD; end, end32 : DWORD; END_VAR (* version 1.2 31. oct. 2008 programmer hugo tested by oscat this function will initialize a given array of byte with 0. the function needs to be called: _buffer_clear(adr("array"),sizeof("array")); this function will manipulate a given array. the function manipulates the original array, it rerturnes true when finished. because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) (* this routine uses 32 bit access to gain speed *) (* first access bytes till pointer is aligned for 32 bit access *) temp := pt; end := temp + UINT_TO_DWORD(size); end32 := end - 3; WHILE (pt < end) AND ((temp AND 16#00000003) > 0) DO pt^ := 0; pt := pt + 1; temp := temp + 1; END_WHILE; (* pointer is aligned, now copy 32 bits at a time *) ptw := pt; WHILE ptw < end32 DO (* *) ptw^ := 0; ptw := ptw + 4; END_WHILE; (* copy the remaining bytes in byte mode *) pt := ptw; WHILE pt < end DO pt^ := 0; pt := pt + 1; END_WHILE; _BUFFER_CLEAR := TRUE; (* revision History hm 5. mar. 2008 rev 1.0 original version hm 16. mar. 2008 rev 1.1 added type conversion to avoid warnings under codesys 30 chaged type of input size to uint deleted unused variable i hm 31. oct. 2008 rev 1.2 corrected an error while routine would write outside of arrays *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Buffer Management' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION _BUFFER_INIT : BOOL VAR_INPUT PT : POINTER TO BYTE; SIZE : UINT; INIT : BYTE; END_VAR VAR ptw : POINTER TO DWORD; temp : DWORD; end, end32 : DWORD; END_VAR (* version 1.2 31. oct. 2008 programmer hugo tested by oscat this function will initialize a given array of byte with init. the function needs to be called: _buffer_init(adr("array"),sizeof("array"),init); this function will manipulate a given array. the function manipulates the original array, it rerturnes true when finished. because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) (* this routine uses 32 bit access to gain speed *) (* first access bytes till pointer is aligned for 32 bit access *) temp := pt; end := temp + UINT_TO_DWORD(size); end32 := end - 3; WHILE (pt < end) AND ((temp AND 16#00000003) > 0) DO pt^ := init; pt := pt + 1; temp := temp + 1; END_WHILE; (* pointer is aligned, now copy 32 bits at a time *) ptw := pt; temp := SHL(BYTE_TO_DWORD(init),24) OR SHL(BYTE_TO_DWORD(init),16) OR SHL(BYTE_TO_DWORD(init),8) OR BYTE_TO_DWORD(init); WHILE ptw < end32 DO ptw^ := temp; ptw := ptw + 4; END_WHILE; (* copy the remaining bytes in byte mode *) pt := ptw; WHILE pt < end DO pt^ := init; pt := pt + 1; END_WHILE; _BUFFER_INIT := TRUE; (* revision History hm 5. mar. 2008 rev 1.0 original version hm 16. mar. 2008 rev 1.1 added type conversion to avoid warnings under codesys 3.0 chaged type of input size to uint. hm 31. oct. 2008 rev 1.2 corrected an error while routine would write outside of arrays *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Buffer Management' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION _BUFFER_INSERT : INT VAR_INPUT STR : STRING(STRING_LENGTH); POS : INT; PT : POINTER TO ARRAY[0..32767] OF BYTE; SIZE : UINT; END_VAR VAR end : INT; lx: INT; i: INT; END_VAR (* version 1.5 2. jan. 2012 programmer hugo tested by oscat this function will insert a string at a given position in a buffer. the function needs to be called: _buffer_init(adr("array"),sizeof("array"),init); this function will manipulate a given array. the function manipulates the original array, it returnes the next position after the input string when finished. because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) lx := LEN(str); end := pos + lx; (* first move the upper part of the buffer to make space for the string *) FOR i := UINT_TO_INT(size) - 1 TO end BY -1 DO pt^[i] := pt^[i-lx]; END_FOR; (* copy the string *) _BUFFER_INSERT := _STRING_TO_BUFFER(str, pos, PT, size); (* revision History hm 9. mar. 2008 rev 1.0 original version hm 16. mar. 2008 rev 1.1 changed type of input size to uint hm 13. may. 2008 rev 1.2 changed type of pointer to array[1..32767] changed size of string to STRING_LENGTH hm 23. mar. 2009 rev 1.3 avoid writing to input pos hm 12. nov. 2009 rev 1.4 code optimized hm 2. jan 2012 rev 1.5 function now returns the next position after the input string *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Buffer Management' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION _BUFFER_UPPERCASE : BOOL VAR_INPUT PT : POINTER TO ARRAY[0..32000] OF BYTE; SIZE : INT; END_VAR VAR pos: INT; END_VAR (* version 1.0 20. jan. 2011 programmer hugo tested by oscat this function will convert an array of byte into uppercase the function needs to be called: _buffer_clear(adr("array"),sizeof("array")); this function will manipulate a given array. the function manipulates the original array, it rerturnes true when finished. because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) pos := 0; WHILE pos < size DO PT^[pos] := TO_UPPER(pt^[pos]); pos := pos + 1; END_WHILE; _BUFFER_UPPERCASE := TRUE; (* revision History hm 20. jan. 2011 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Buffer Management' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION _STRING_TO_BUFFER : INT VAR_INPUT STR : STRING(STRING_LENGTH); POS : INT; PT : POINTER TO ARRAY[0..32767] OF BYTE; SIZE : UINT; END_VAR VAR ps : POINTER TO BYTE; i: INT; end: INT; END_VAR (* version 1.4 2. jan. 2012 programmer hugo tested by oscat this function will copy a string into an array of byte starting at position pos. the function needs to be called: _String_To_buffer(str, pos, adr("array"),sizeof("array")); this function will manipulate the array directly in memory and returns the position after the input string when finished. because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) ps := ADR(str); end := MIN(pos + LEN(str), UINT_TO_INT(size)); IF end > 0 THEN end := end -1; END_IF; FOR i := pos TO end DO pt^[i] := ps^; ps := ps + 1; END_FOR; _STRING_TO_BUFFER := i; (* revision History hm 5. mar. 2008 rev 1.0 original version hm 16. mar. 2008 rev 1.1 changed type of input size to uint hm 13. may. 2008 rev 1.2 changed type of pointer to array[1..32767] changed size of string to STRING_LENGTH hm 12. nov. 2009 rev 1.3 limit end to size - 1 hm 2. jan 2012 rev 1.4 return the position after the input string when finished *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Buffer Management' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BUFFER_COMP : INT VAR_INPUT PT1 : POINTER TO ARRAY[0..32767] OF BYTE; SIZE1 : INT; PT2 : POINTER TO ARRAY[0..32767] OF BYTE; SIZE2 : INT; START : INT; END_VAR VAR i, j, end : INT; firstbyte: BYTE; END_VAR (* version 1.1 12. nov. 2009 programmer hugo tested by oscat *) (* @END_DECLARATION := '0' *) (* search for first character match *) IF size2 <= size1 THEN end := size1 - size2; firstbyte := PT2^[0]; FOR i := START TO end DO IF PT1^[i] = firstbyte THEN (* first character matches, now compare rest of array *) j := 1; WHILE j < size2 DO IF pt2^[j] <> pt1^[j+i] THEN EXIT; END_IF; j := j + 1; END_WHILE; (* when J > size2 a match was found return the position i in buffer1 *) IF j = size2 THEN BUFFER_COMP := i; RETURN; END_IF; END_IF; END_FOR; END_IF; BUFFER_COMP := -1; (* hm 14. nov. 2008 rev 1.0 original version hm 12. nov. 2009 rev 1.1 performance increase *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Buffer Management' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BUFFER_SEARCH : INT VAR_INPUT PT : POINTER TO ARRAY[0..32767] OF BYTE; SIZE : INT; STR : STRING(STRING_LENGTH); POS : INT; IGN : BOOL; END_VAR VAR ps : POINTER TO ARRAY[0..STRING_LENGTH] OF BYTE; chx : BYTE; i: INT; end: INT; k: INT; lx: INT; END_VAR (* version 1.4 25. jan 2011 programmer hugo tested by oscat this function will search for a string STR in an array of byte starting at position pos. the function needs to be called: buffer_search(adr("array"),sizeof("array"), STR, POS, IGN); because this function works with pointers its very time efficient and it needs no extra memory. The function returns the position of the first character of the string in the array if found. a -1 is returned if the string is not found in the array. when IGN = TRUE, STR must be in capital letters and the search is case insensitv. *) (* @END_DECLARATION := '0' *) ps := ADR(STR); lx := LEN(STR); end := MIN(SIZE - lx, SIZE - 1); lx := lx - 1; FOR i := pos TO end DO FOR k := 0 TO lx DO IF IGN THEN chx := TO_UPPER(pt^[i+k]); ELSE chx := pt^[i+k]; END_IF; IF ps^[k] <> chx THEN EXIT; END_IF; END_FOR; IF k > lx THEN BUFFER_SEARCH := i; RETURN; END_IF; END_FOR; BUFFER_SEARCH := -1; (* revision History hm 5. mar. 2008 rev 1.0 original version hm 16. mar. 2008 rev 1.1 chaged type of input size to uint hm 13. may. 2008 rev 1.2 changed type of pointer to array[1..32767] changed size of string to STRING_LENGTH hm 12. nov. 2009 rev 1.3 limit end to array size hm 25. jan. 2011 rev 1.4 ign = True will now ignore case return -1 if nothing found *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Buffer Management' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BUFFER_TO_STRING : STRING(STRING_LENGTH) VAR_INPUT PT : POINTER TO ARRAY[0..32767] OF BYTE; SIZE : UINT; START : UINT; STOP : UINT; END_VAR VAR ps : POINTER TO BYTE; i : UINT; stp: UINT; sta: UINT; END_VAR (* version 1.5 12. nov. 2009 programmer hugo tested by oscat this function will retrieve a string from an array of byte starting at position start and stop at position stop. the function needs to be called: buffer_TO_String(adr("array"),sizeof("array"), start, stop); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) ps := ADR(BUFFER_TO_STRING); IF size = 0 THEN RETURN; END_IF; sta := MIN(start, size -1); stp := MIN(stop, size -1); (* check for maximum string_length *) IF UINT_TO_INT(stp - sta + 1) >= STRING_LENGTH THEN stp := sta + INT_TO_UINT(STRING_LENGTH) - 1; END_IF; FOR i := sta TO stp DO ps^ := pt^[i]; ps := ps + 1; END_FOR; (* terminate the string *) ps^ := 0; (* revision History hm 5. mar. 2008 rev 1.0 original version hm 16. mar. 2008 rev 1.1 changed type of input size to uint hm 13. may. 2008 rev 1.2 changed type of pointer to array[0..32767] changed size of string to STRING_LENGTH hm 12. jun. 2008 rev 1.3 check for pointer overrun change input start and stop to uint added type conversions to avoid warnings under codesys 3.0 hm 23. mar. 2009 rev 1.4 avoid writing to input stop hm 12. nov. 2009 rev 1.5 limit start and stop to size -1 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK DRIVER_1 VAR_INPUT CONSTANT Toggle_Mode : BOOL; Timeout : TIME; END_VAR VAR_INPUT SET : BOOL; IN : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR off : TON; edge: BOOL; END_VAR (* version 1.0 2 jan 2008 programmer hugo tested by tobias driver_1 is a multi purpose driver. a rising edge on in sets the output high if toggle is flase. while toggle is true, a rising edge on in toggles the output Q. if a timeout is specified the output q will be reset to false automatically after the timeout has elapsed. a asynchronous reset and set will force the output high or low respectively. *) (* @END_DECLARATION := '0' *) IF off.Q THEN Q := FALSE; END_IF; IF rst THEN Q := FALSE; ELSIF set THEN Q := TRUE; ELSIF IN AND NOT edge THEN IF toggle_mode THEN q := NOT Q; ELSE q := TRUE; END_IF; END_IF; edge := in; IF timeout > t#0s THEN off(in := Q, PT := Timeout); END_IF; (* revision history hm 2. jan 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK DRIVER_4 VAR_INPUT CONSTANT Toggle_Mode : BOOL; Timeout : TIME; END_VAR VAR_INPUT SET : BOOL; IN0, IN1, IN2, IN3 : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Q0, Q1, Q2, Q3 : BOOL; END_VAR VAR d0, d1, d2, d3 : DRIVER_1; END_VAR (* version 1.0 2 jan 2008 programmer hugo tested by tobias driver_4 is a 4 channel multi purpose driver. a rising edge on in? sets the output Q? high if toggle_mode is flase. while toggle_mode is true, a rising edge on in? toggles the output Q?. if a timeout is specified the output Q? will be reset to false automatically after the timeout has elapsed. a asynchronous reset and set will force the output high or low respectively. an asynchronous set will force all outputs high simultaneously *) (* @END_DECLARATION := '0' *) D0(Set:=set, in:=in0, rst:=rst, toggle_mode:=toggle_mode, timeout:=timeout); D1(Set:=set, in:=in1, rst:=rst, toggle_mode:=toggle_mode, timeout:=timeout); D2(Set:=set, in:=in2, rst:=rst, toggle_mode:=toggle_mode, timeout:=timeout); D3(Set:=set, in:=in3, rst:=rst, toggle_mode:=toggle_mode, timeout:=timeout); Q0 := D0.Q; Q1 := D1.Q; Q2 := D2.Q; Q3 := D3.Q; (* revision history hm 2. jan 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK DRIVER_4C VAR_INPUT IN : BOOL; RST : BOOL; END_VAR VAR_INPUT CONSTANT Timeout : TIME; SX : ARRAY[1..7] OF BYTE := 1,3,7,15; END_VAR VAR_OUTPUT SN : INT; Q0 : BOOL; Q1 : BOOL; Q2 : BOOL; Q3 : BOOL; END_VAR VAR off : TON; edge: BOOL; END_VAR (* version 1.0 23. mar. 2009 programmer hugo tested by tobias driver_4C is a multi purpose driver. a rising edge on in switches from S0 state S1 and the next edge to state S2 and so on. in state S0 all outputs Q are FALSE. The stet of the Outputs in any state S? is configurable with setup variables. The variables S1..S5 define the states, while the sequence is terminated when a state Variable S? = 0. The lower bits 0..3 of the state vars S? are corresponding to the Outputs Q0..Q3 *) (* @END_DECLARATION := '0' *) IF RST OR off.Q THEN SN := 0; ELSIF IN AND NOT edge THEN SN := SN + 1; IF SN > 7 OR SX[SN] = 0 THEN SN := 0; END_IF; END_IF; edge := in; IF SN > 0 THEN Q0 := SX[SN].0; Q1 := SX[SN].1; Q2 := SX[SN].2; Q3 := SX[SN].3; ELSE Q0 := FALSE; Q1 := FALSE; Q2 := FALSE; Q3 := FALSE; END_IF; (* maximaum timeout *) IF timeout > t#0s THEN off(in := SN > 0, PT := Timeout); END_IF; (* revision history hm 23. mar. 2009 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FLOW_CONTROL VAR_INPUT IN : BOOL; REQ : BOOL; ENQ : BOOL; RST : BOOL; END_VAR VAR_INPUT CONSTANT T_AUTO : TIME := T#1h; T_DELAY : TIME := T#23h; END_VAR VAR_OUTPUT Q : BOOL; STATUS : BYTE; END_VAR VAR timer : TP_1D; END_VAR (* version 1.0 28. jun. 2008 programmer hugo tested by oscat FLOW_CONTROL switches a valves depending on the input in. flow control also limits the maximum ontime of the valve and controls pressure on the output side. *) (* @END_DECLARATION := '0' *) STATUS := 100; IF RST THEN Q := FALSE; timer(rst := TRUE); timer.RST := FALSE; STATUS := 103; ELSIF ENQ THEN IF IN THEN status := 101; END_IF; IF REQ THEN (* timer will generate a timed pulse after TP goes high *) timer.PT1 := T_AUTO; timer.PTD := T_DELAY; timer.IN := TRUE; STATUS := 102; END_IF; END_IF; (* set output and run timer *) timer(); timer.IN := FALSE; Q := (IN AND ENQ) OR timer.Q; (* revision history hm 28. jun. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_Profile VAR_INPUT K : REAL := 1.0; O : REAL; M : REAL := 1.0; E : BOOL; END_VAR VAR_INPUT CONSTANT value_0 : REAL; time_1 : TIME; value_1 : REAL; time_2 : TIME; value_2 : REAL; time_3 : TIME; value_3 : REAL; time_10 : TIME; value_10 : REAL; time_11 : TIME; value_11 : REAL; time_12 : TIME; value_12 : REAL; time_13 : TIME; value_13 : REAL; END_VAR VAR_OUTPUT Y : REAL; RUN : BOOL; ET : TIME; END_VAR VAR tx : TIME; edge : BOOL; state: BYTE; ta: TIME; tb: TIME; t0 : TIME; temp: REAL; va: REAL; vb: REAL; END_VAR (* version 1.1 15 sep 2007 programmer tobias tested by hugo FT_Profile generates an output signal which is defined by values over a time scale. the different values are connected by ramps between the individual values. a rising edge on E starts the output signal generation and E = True can delay time_3 / value_3 as long as it stays true. an additional multiplier K can be used to multiply the output and an offset O can be added to the output dynamically. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := DWORD_TO_TIME(T_PLC_MS()); (* determine start condition *) IF E AND NOT edge THEN RUN := TRUE; ET := t#0s; t0 := tx; ta := tx; tb := multime(time_1, M); va := value_0; vb := value_1; temp := value_0; state := 1; END_IF; edge := E; (* generate startup profile *) IF run THEN CASE state OF 1: IF tx - ta >= tb THEN ta := ta + tb; tb := multime(time_2 - time_1, M); va := value_1; vb := value_2; temp := value_1; state := 2; ELSE temp := ((vb - va) * TIME_TO_REAL(tx - ta) / TIME_TO_REAL(tb) + va); END_IF; 2: IF tx - ta >= tb THEN ta := ta + tb; tb := multime(time_3 - time_2, M); va := value_2; vb := value_3; temp := value_2; state := 3; ELSE temp := ((vb - va) * TIME_TO_REAL(tx - ta) / TIME_TO_REAL(tb) + va); END_IF; 3: IF tx - ta >= tb THEN ta := ta + tb; tb := multime(time_10 - time_3, M); va := value_3; vb := value_10; temp := value_3; state := 4; ELSE temp := ((vb - va) * TIME_TO_REAL(tx - ta) / TIME_TO_REAL(tb) + va); END_IF; 4 : IF tx - ta >= tb THEN ta := ta + tb; tb := multime(time_11 - time_10, M); va := value_10; vb := value_11; temp := value_10; IF E THEN state := 5; ELSE state := 6; END_IF; ELSE temp := ((vb - va) * TIME_TO_REAL(tx - ta) / TIME_TO_REAL(tb) + va); END_IF; 5: (* extend the signal while E is true *) IF E THEN ta := tx; ELSE state := 6; END_IF; 6: IF tx - ta >= tb THEN ta := ta + tb; tb := multime(time_12 - time_11, M); va := value_11; vb := value_12; temp := value_11; state := 7; ELSE temp := ((vb - va) * TIME_TO_REAL(tx - ta) / TIME_TO_REAL(tb) + va); END_IF; 7: IF tx - ta >= tb THEN ta := ta + tb; tb := multime(time_13 - time_12, M); va := value_12; vb := value_13; temp := value_12; state := 8; ELSE temp := ((vb - va) * TIME_TO_REAL(tx - ta) / TIME_TO_REAL(tb) + va); END_IF; 8: IF tx - ta >= tb THEN temp := value_13; run := FALSE; ELSE temp := ((vb - va) * TIME_TO_REAL(tx - ta) / TIME_TO_REAL(tb) + va); END_IF; END_CASE; Y := temp * K + O; ET := tx - t0; END_IF; (* revision history hm 27 feb 2007 rev 1.0 original version hm 15. sep2007 rev 1.1 replaced Time() with T_PLC_MS for compatibility and performance reasons *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK INC_DEC VAR_INPUT CHa : BOOL; CHb : BOOL; RST : BOOL; END_VAR VAR_OUTPUT dir : BOOL; cnt : INT; END_VAR VAR edgea : BOOL; clk: BOOL; clka: BOOL; clkb: BOOL; edgeb: BOOL; axb: BOOL; END_VAR (* version 1.0 4 aug 2006 programmer oscat tested BY oscat incremental decoder with quadruple accuracy. 2 pulses for each channel are created for each directional pulse. *) (* @END_DECLARATION := '0' *) axb := cha XOR chb; (* create pulses for channel a *) clka := cha XOR edgea; edgea := cha; clkb := chb XOR edgeb; edgeb := chb; (* create pulses for both channels *) clk := clka OR clkb; (* set the direction output *) IF axb AND clka THEN dir := TRUE; END_IF; IF axb AND clkb THEN dir := FALSE; END_IF; (* increment or decrement the counter *) IF clk AND dir THEN cnt := cnt + 1; END_IF; IF clk AND NOT dir THEN cnt := cnt -1; END_IF; (* reset the counter if rst active *) IF rst THEN cnt := 0; END_IF; END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK INTERLOCK VAR_INPUT I1, I2 : BOOL; TL : TIME; END_VAR VAR_OUTPUT Q1, Q2 : BOOL; END_VAR VAR T1, T2 : TOF; END_VAR (* version 1.0 28 sep 2007 programmer hugo tested by tobias INTERLOCK has two inputs I1 and I2 which drive the corresponding outputs Q1 and Q2. the inputs signals lock each other out and therfore I1 can only drive Q1 when I2 is Low and vice versa. The input TL specifies a dead time between two outputs can become active. an output can only become active when the other output was not active for the time TL. *) (* @END_DECLARATION := '0' *) (* the input signal have a run delay to lockout the other input *) T1(IN := I1, PT := TL); T2(IN := I2, PT := TL); Q1 := I1 AND NOT t2.Q; Q2 := I2 AND NOT t1.Q; (* revision history hm 28 sep 2007 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK INTERLOCK_4 VAR_INPUT CONSTANT END_VAR VAR_INPUT I0 : BOOL; I1 : BOOL; I2 : BOOL; I3 : BOOL; E : BOOL; MODE : INT; END_VAR VAR_OUTPUT OUT : BYTE; TP : BOOL; END_VAR VAR in: BYTE; last: BYTE; old: BYTE; lmode: INT; END_VAR (* version 1.1 14. mar. 2009 programmer hugo tested by oscat INTERLOCK_4 detects one of 4 switches and delivers the number of the switch pressed on the output out a output tp is true for one cycle if the output has changed. a setup variable MODE selects between 3 different modes: MODE = 0, any input active will disable all other inputs MODE = 1, the input with the highest number will be acepted mode = 2, the input last pressed will disable all others *) (* @END_DECLARATION := '0' *) (* check if enable is active *) IF E THEN (* reset all vars when there is a mode change on thy fly *) IF mode <> lmode THEN out := 0; last := 0; old := 0; lmode := mode; END_IF; (* load inputs into in *) in.0 := I0; in.1 := I1; in.2 := I2; in.3 := I3; (* only execute when there is any change *) IF in <> last THEN (* only execute when inputs have chages state *) CASE mode OF 0: (* output directly display inputs as bits in byte out *) out := in; 1: (* the input with the highest number will be acepted *) IF in.3 THEN out := 8; ELSIF in.2 THEN out := 4; ELSIF in.1 THEN out := 2; ELSE out := in; END_IF; 2: (* input last pressed will be displayed only *) last := ((in XOR last) AND in); IF last.3 THEN out := 8; ELSIF last.2 THEN out := 4; ELSIF last.1 THEN out := 2; ELSE out := last; END_IF; 3: (* any input active will disable all other inputs *) IF (out AND in) = 0 THEN IF in.3 THEN out := 8; ELSIF in.2 THEN out := 4; ELSIF in.1 THEN out := 2; ELSE out := in; END_IF; END_IF; END_CASE; last := in; END_IF; tp := out <> old; old := out; ELSE out := 0; last := 0; old := 0; lmode := 0; tp := FALSE; END_IF; (* revision history hm 24. oct 2008 rev 1.0 original version hm 14. mar. 2009 rev 1.1 replaced double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MANUAL : BOOL VAR_INPUT IN : BOOL; ON : BOOL; OFF : BOOL; END_VAR (* version 1.0 21. nov. 2008 programmer hugo tested by oscat MANUAL is a manual override for digital signals. when on and off = FALSE, the output follows IN. ON = TRUE forces the output high, and OFF = TRUE forces the output low. *) (* @END_DECLARATION := '0' *) MANUAL := NOT OFF AND (IN OR ON); (* revision history hm 21. nov 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK MANUAL_1 VAR_INPUT IN : BOOL; MAN : BOOL; M_I : BOOL; SET : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Q : BOOL; STATUS : BYTE; END_VAR VAR S_edge : BOOL; r_edge : BOOL; edge: BOOL; END_VAR (* version 1.2 14. mar. 2009 programmer hugo tested by oscat MANUAL_1 is a manual override for digital signals. When MAN = FALSE, the output follows IN and when MAN = TRUE the Output follows M_I. *) (* @END_DECLARATION := '0' *) IF NOT man THEN Q := IN; STATUS := 100; edge := FALSE; ELSIF NOT s_edge AND set THEN Q := TRUE; edge := TRUE; status := 101; ELSIF NOT r_edge AND rst THEN Q := FALSE; edge := TRUE; status := 102; ELSIF NOT edge THEN Q := M_I; status := 103; END_IF; (* remember levels of manual signals *) s_edge := SET; r_edge := RST; (* revision history hm 17. jun 2008 rev 1.0 original version hm 17. oct 2008 rev 1.1 deleted unnecessary variable m_edge hm 14. mar. 2009 rev 1.2 replaced double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK MANUAL_2 VAR_INPUT IN : BOOL; ENA : BOOL; ON : BOOL; OFF : BOOL; MAN : BOOL; END_VAR VAR_OUTPUT Q : BOOL; STATUS : BYTE; END_VAR (* version 1.0 22. sep. 2008 programmer hugo tested by oscat MANUAL_2 is a manual override for boolean signals. it has static force high and low as well as a manual input. *) (* @END_DECLARATION := '0' *) IF ena THEN IF NOT ON AND NOT OFF THEN Q := IN; STATUS := 100; ELSIF on AND NOT off THEN Q := TRUE; STATUS := 101; ELSIF NOT on AND off THEN q := FALSE; STATUS := 102; ELSE Q := MAN; STATUS := 103; END_IF; ELSE Q := FALSE; STATUS := 104; END_IF; (* revision history hm 22. sep. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK MANUAL_4 VAR_INPUT I0, I1, I2, I3 : BOOL; MAN : BOOL; STP : BOOL; M0, M1, M2, M3 : BOOL; END_VAR VAR_OUTPUT Q0, Q1, Q2, Q3 : BOOL; STATUS : BYTE; END_VAR VAR edge: BOOL; pos : INT; tog: BOOL; END_VAR (* version 1.0 17. jun 2008 programmer hugo tested by oscat MANUAL_4 is a manual override for digital signals. When MAN = FALSE, the output follows IN and when MAN = TRUE the Output follows M_I. *) (* @END_DECLARATION := '0' *) IF man THEN IF NOT TOG THEN Q0 := M0; Q1 := M1; Q2 := M2; Q3 := M3; STATUS := 101; END_IF; IF STP AND NOT edge THEN tog := TRUE; CASE pos OF 0: Q0 := TRUE; Q1 := FALSE; Q2 := FALSE; Q3 := FALSE; STATUS := 110; 1: Q0 := FALSE; Q1 := TRUE; Q2 := FALSE; Q3 := FALSE; STATUS := 111; 2: Q0 := FALSE; Q1 := FALSE; Q2 := TRUE; Q3 := FALSE; STATUS := 112; 3: Q0 := FALSE; Q1 := FALSE; Q2 := FALSE; Q3 := TRUE; STATUS := 113; END_CASE; pos := INC(pos,1,3); END_IF; ELSE Q0 := I0; Q1 := I1; Q2 := I2; Q3 := I3; STATUS := 100; tog := FALSE; pos := 0; END_IF; (* remember status of stp *) edge := STP; (* revision history hm 17. jun 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK PARSET VAR_INPUT A0, A1 : BOOL; END_VAR VAR_INPUT CONSTANT X01, X02, X03, X04 : REAL; X11, X12, X13, X14 : REAL; X21, X22, X23, X24 : REAL; X31, X32, X33, X34 : REAL; TC : TIME; END_VAR VAR_OUTPUT P1, P2, P3, P4 : REAL; END_VAR VAR X : ARRAY[0..3,1..4] OF REAL; S1, S2, S3, S4 : REAL; tx, last : DWORD; start : BOOL; set : BYTE; init: BOOL; END_VAR (* version 1.1 16. mar. 2008 programmer hugo tested by tobias parset selects on of 4 parameter sets adressed by the inputs A0 and A1. if TC is specified, the change of the outputs is ramped by the time tc *) (* @END_DECLARATION := '0' *) (* read system_time *) tx := T_PLC_MS(); (* init sequence *) IF NOT init THEN set.0 := NOT A0; init := TRUE; X[0,1] := X01; X[0,2] := X02; X[0,3] := X03; X[0,4] := X04; X[1,1] := X11; X[1,2] := X12; X[1,3] := X13; X[1,4] := X14; X[2,1] := X21; X[2,2] := X22; X[2,3] := X23; X[2,4] := X24; X[3,1] := X31; X[3,2] := X32; X[3,3] := X33; X[3,4] := X34; P1 := X01; P2 := X02; P3 := X03; P4 := X04; END_IF; (* check for input change *) IF (A0 XOR set.0) OR (A1 XOR set.1) THEN (* a new set is selected *) set.0 := A0; set.1 := A1; IF tc > t#0s THEN start := TRUE; last := tx; (* save the step speed for the output changes in S *) S1 := (X[set,1] - P1)/DWORD_TO_REAL(TIME_TO_DWORD(tc)); S2 := (X[set,2] - P2)/DWORD_TO_REAL(TIME_TO_DWORD(tc)); S3 := (X[set,3] - P3)/DWORD_TO_REAL(TIME_TO_DWORD(tc)); S4 := (X[set,4] - P4)/DWORD_TO_REAL(TIME_TO_DWORD(tc)); END_IF; ELSIF start AND tx - last < TIME_TO_DWORD(tc) THEN (* ramp the outputs to the new value *) P1 := X[set,1] - S1 * DWORD_TO_REAL(TIME_TO_DWORD(Tc) - tx + last); P2 := X[set,2] - S2 * DWORD_TO_REAL(TIME_TO_DWORD(Tc) - tx + last); P3 := X[set,3] - S3 * DWORD_TO_REAL(TIME_TO_DWORD(Tc) - tx + last); P4 := X[set,4] - S4 * DWORD_TO_REAL(TIME_TO_DWORD(Tc) - tx + last); ELSE (* make sure outputs match the correct set values *) start := FALSE; P1 := X[set,1]; P2 := X[set,2]; P3 := X[set,3]; P4 := X[set,4]; END_IF; (* revision history hm 2.11.2007 rev 1.0 original version hm 16. mar. 2008 rev 1.1 added type conversions to avoid warnings under codesys 3.0 *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK PARSET2 VAR_INPUT X : REAL; END_VAR VAR_INPUT CONSTANT X01, X02, X03, X04 : REAL; X11, X12, X13, X14 : REAL; X21, X22, X23, X24 : REAL; X31, X32, X33, X34 : REAL; L1, L2, L3 : REAL; TC : TIME; END_VAR VAR_OUTPUT P1,P2,P3,P4 : REAL; END_VAR VAR Pset : parset; init: BOOL; END_VAR (* version 1.0 3 nov 2007 programmer hugo tested by tobias parset2 selects on of 4 parameter sets depending on the value of X. if TC is specified, the change of the outputs is ramped by the time TC *) (* @END_DECLARATION := '0' *) (* init sequence *) IF NOT init THEN init := TRUE; pset(TC:=TC, X01:=X01, X02:=X02, X03:=X03, X04:=X04, X11:=X11, X12:=X12, X13:=X13, X14:=X14, X21:=X21, X22:=X22, X23:=X23, X24:=X24, X31:=X31, X32:=X32, X33:=X33, X34:=X34); END_IF; IF ABS(X) < L1 THEN pset(A0 := FALSE, A1 := FALSE); ELSIF ABS(X) < L2 THEN pset(A0 := TRUE, A1 := FALSE); ELSIF ABS(x) < L3 THEN pset(A0 := FALSE, A1 := TRUE); ELSE pset(A0 := TRUE, A1 := TRUE); END_IF; P1 := pset.P1; P2 := pset.P2; P3 := pset.P3; P4 := pset.P4; (* revision history hm 3.11.2007 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SIGNAL VAR_INPUT IN : BOOL; SIG : BYTE; TS : TIME; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR tx: DWORD; step: BYTE; END_VAR VAR CONSTANT one : BYTE := 1; END_VAR (* version 1.0 13 dec 2007 programmer hugo tested by tobias this function generates an output signal according to a bit pattern SIG. the patter is shifted to the output in time steps of TS. ts defaults to 128 ms if not specified. *) (* @END_DECLARATION := '0' *) IF in THEN (* an alarm is present read system time first *) tx := T_PLC_MS(); (* calculate the step counter which is the lowest 3 bits of (time / ts) *) IF ts > t#0s THEN step := DWORD_TO_BYTE(tx / TIME_TO_DWORD(ts) AND 16#0000_0007); ELSE step := DWORD_TO_BYTE(SHR(tx,7) AND 16#0000_0007); END_IF; (* convert the value 0-7 in step into one bit only (bit 0-7) *) step := SHL(one,step); (* generate the output signal *) Q := (step AND sig) > 0; ELSE Q := FALSE; END_IF; (* revision history hm 13.12.2007 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SIGNAL_4 VAR_INPUT IN1 : BOOL; IN2 : BOOL; IN3 : BOOL; IN4 : BOOL; TS : TIME; END_VAR VAR_INPUT CONSTANT S1 : BYTE := 2#1111_1111; S2 : BYTE := 2#1111_0000; S3 : BYTE := 2#1010_1010; S4 : BYTE := 2#1010_0000; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR sig : SIGNAL; END_VAR (* version 1.0 13 dec 2007 programmer hugo tested by tobias this function generates one out of 4 signals specified by bit patterns S1 .. S4. the selected pattern will be shifted with the step time TS. In1 has higher priority then In2 which has higher priority then IN3 and in4 has the lowest priority. ts defaults to 128 ms if not specified. *) (* @END_DECLARATION := '0' *) (* an alarm is present read system time first *) (* check if an alarm is present if yes set sig to the alarm pattern otherwise exit the routine *) IF in1 THEN sig(in := TRUE, sig := s1, TS := TS); ELSIF in2 THEN sig(in := TRUE, sig := s2, TS := TS); ELSIF in3 THEN sig(in := TRUE, sig := s3, TS := TS); ELSIF in4 THEN sig(in := TRUE, sig := s4, TS := TS); ELSE sig(in := FALSE); END_IF; (* set the output *) Q := sig.Q; (* revision history hm 13.12.2007 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTIONBLOCK SRAMP VAR_INPUT X : REAL; A_UP : REAL; A_DN :REAL; VU_MAX : REAL; VD_MAX : REAL; LIMIT_HIGH : REAL; LIMIT_LOW : REAL; RST : BOOL; END_VAR VAR_OUTPUT Y : REAL; V : REAL; END_VAR VAR cycle_time : TC_S; init: BOOL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by tobias RAMPS generates output signal which are slew rate and acceleration controlled. *) (* @END_DECLARATION := '0' *) (* read the system_time in microseconds *) cycle_time(); (* assure range of inputs *) A_up := MAX(0.0, A_UP); A_dn := MIN(0.0, A_dn); VU_max := MAX(0.0, VU_max); VD_max := MIN(0.0, VD_MAX); (* calculate the output offset *) IF rst OR NOT init THEN init := TRUE; Y := 0.0; V := 0.0; ELSIF X = Y THEN v := 0.0; ELSIF X > Y THEN (* output is too low >> ramp up and brake at the end *) (* accelerate the speed and limit to vu_max *) v := MIN(v + A_UP * cycle_time.TC, vu_max); (* calculate the max speed to be able to brake and select the lowest *) v := MIN(SQRT((Y-X) * 2.0 * A_DN), v); (* calculate the output and obey limits *) y := LIMIT(limit_low, y + MIN(v * cycle_time.TC, X-Y), limit_high); ELSIF X < Y THEN (* output is too high >> ramp dn and brake at the end *) (* accelerate the speed and limit to vd_max *) v := MAX(v + A_DN * cycle_time.TC, vd_max); (* calculate the max speed to be able to brake and select the lowest *) v := MAX(-SQRT((Y-X) * 2.0 * A_UP), v); (* calculate the output and obey limits *) y := LIMIT(limit_low, y + MAX(v * cycle_time.TC, X-Y), limit_high); END_IF; (* revision history hm 13. mar. 2008 rev 1.0 original version hm 11. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK TUNE VAR_INPUT SET : BOOL; SU, SD : BOOL; RST : BOOL; END_VAR VAR_INPUT CONSTANT SS : REAL := 0.1; Limit_L : REAL; LIMIT_H : REAL := 100.0; RST_val : REAL; SET_val : REAL := 100.0; T1 : TIME := T#500ms; T2 : TIME := T#2s; S1 : REAL := 2.0; S2 : REAL := 10.0; END_VAR VAR_OUTPUT Y : REAL; END_VAR VAR tx : DWORD; start, start2 : DWORD; state : INT; in : BOOL; step : REAL; SPEED : REAL; Y_start : REAL; Y_start2: REAL; END_VAR (* version 1.2 11. mar. 2009 programmer hugo tested by tobias tune generates an output signal which is set by input switches. up to 4 switsches can be used to tune the signal up or down. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := T_PLC_MS(); IF rst THEN Y := RST_val; state := 0; ELSIF set THEN Y := SET_val; state := 0; ELSIF state > 0 THEN (* key has been pushed state machine operating *) (* first read the correct input *) IF state = 1 THEN (* step up *) in := su; ELSE (* step down *) in := sd; END_IF; (* check for single step operation *) IF NOT in AND tx - start <= TIME_TO_DWORD(T1) THEN Y := Y_start + step; state := 0; (* check if fast ramp needs to be generated *) ELSIF in AND tx - start >= TIME_TO_DWORD(T2) THEN Y := Y_start2 + DWORD_TO_REAL(tx - start2) * s2 / speed; (* check if slow ramp needs to be generated *) ELSIF in AND tx - start >= TIME_TO_DWORD(T1) THEN Y := Y_start + DWORD_TO_REAL(tx - start - TIME_TO_DWORD(T1)) * S1 / speed; start2 := tx; Y_start2 := Y; ELSIF NOT in THEN state := 0; END_IF; ELSIF su THEN (* slow step up *) state := 1; start := tx; step := ss; speed := 1000.0; Y_start := Y; ELSIF sd THEN (* slow step down *) state := 2; start := tx; step := -ss; speed := -1000.0; Y_start := Y; END_IF; (* make sure output does not exceed limits *) Y := LIMIT(LIMIT_L, Y, LIMIT_H); (* revision history hm 3.11.2007 rev 1.0 original version hm 16. mar. 2008 rev 1.1 added type conversions to avoid warnings under codesys 3.0 hm 11. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/automation' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK TUNE2 VAR_INPUT SET : BOOL; SU, SD : BOOL; FU, FD : BOOL; RST : BOOL; END_VAR VAR_INPUT CONSTANT SS : REAL := 0.1; FS : REAL := 5.0; Limit_L : REAL; LIMIT_H : REAL := 100.0; RST_val : REAL; SET_val : REAL := 100.0; TR : TIME := T#500ms; S1 : REAL := 2.0; S2 : REAL := 10.0; END_VAR VAR_OUTPUT Y : REAL; END_VAR VAR tx : DWORD; start : DWORD; state : INT; in : BOOL; step : REAL; SPEED : REAL; Y_start : REAL; END_VAR (* version 1.2 11. mar. 2009 programmer hugo tested by tobias tune2 generates an output signal which is set by input switches. up to 4 switsches can be used to tune the signal up or down. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := T_PLC_MS(); IF rst THEN Y := RST_val; state := 0; ELSIF set THEN Y := SET_val; state := 0; ELSIF state > 0 THEN (* key has been pushed state machine operating *) (* first read the correct input *) CASE state OF 1 : (* slow up *) in := su; 2 : (* slow down *) in := sd; 3 : (* fast up *) in := fu; 4 : (* fast down *) in := fd; END_CASE; (* check for single step operation *) IF NOT in AND tx - start <= TIME_TO_DWORD(TR) THEN Y := Y_start + step; state := 0; (* check if ramp needs to be generated *) ELSIF in AND tx - start >= TIME_TO_DWORD(TR) THEN Y := Y_start + DWORD_TO_REAL(tx - start - TIME_TO_DWORD(TR)) * speed; ELSIF NOT in THEN state := 0; END_IF; ELSIF su THEN (* slow step up *) state := 1; start := tx; step := ss; speed := s1 * 1.0E-3; Y_start := Y; ELSIF sd THEN (* slow step down *) state := 2; start := tx; step := -ss; speed := -s1 * 1.0E-3; Y_start := Y; ELSIF fu THEN (* fast step up *) state := 3; start := tx; step := fs; speed := s2 * 1.0E-3; y_start := Y; ELSIF fd THEN (* fast step down *) state := 4; start := tx; step := -fs; speed := -s2 * 1.0E-3; y_start := Y; END_IF; (* make sure output does not exceed limits *) Y := LIMIT(LIMIT_L, Y, LIMIT_H); (* revision history hm 3.11.2007 rev 1.0 original version hm 16. 3. 2008 rev 1.1 added type conversions to avoid warnings in codesys 3.0 improved performance hm 11. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BAND_B : BYTE VAR_INPUT X : BYTE; B : BYTE; END_VAR (* version 1.0 21. Nov. 2008 programmer hugo tested by oscat BAND_B will limit X to B <= X <= 255-B. while X < B the resulkt will be 0 and while X > 255-B the output will be 255 otherwise the result = X. *) (* @END_DECLARATION := '0' *) IF X < B THEN BAND_B := 0; ELSIF X > BYTE#255-B THEN BAND_B := 255; ELSE BAND_B := X; END_IF; (* revision history hm 21. nov. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CONTROL_SET1 VAR_INPUT Kt : REAL; Tt : REAL; PI : BOOL; PID : BOOL; END_VAR VAR_INPUT CONSTANT P_K : REAL := 0.5; PI_K : REAL := 0.45; PI_TN : REAL := 0.83; PID_K : REAL := 0.6; PID_TN : REAL := 0.5; PID_TV : REAL := 0.125; END_VAR VAR_OUTPUT KP : REAL; TN : REAL; TV : REAL; KI : REAL; KD : REAL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by tobias takahashi calculates controller parameters for P, PI and PID controllers based on the ziegler nichols method. *) (* @END_DECLARATION := '0' *) IF pi AND PID THEN KP := 0.0; TN := 0.0; TV := 0.0; ELSIF PID THEN KP := PID_K * Kt; TN := PID_TN * Tt; TV := PID_TV * Tt; ELSIF PI THEN KP := PI_K * Kt; TN := PI_TN * Tt; ELSE KP := P_K * Kt; END_IF; (* KI and KD are calculated *) IF tn > 0.0 THEN KI := KP / TN; ELSE KI := 0.0; END_IF; KD := KP * TV; (* revision history hm 4. nov 2007 rev 1.0 original version hm 11. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CONTROL_SET2 VAR_INPUT KS : REAL; TU : REAL; TG : REAL; PI : BOOL; PID : BOOL; END_VAR VAR_INPUT CONSTANT P_K : REAL := 1.0; PI_K : REAL := 0.9; PI_TN : REAL := 3.33; PID_K : REAL := 1.2; PID_TN : REAL := 2.0; PID_TV : REAL := 0.5; END_VAR VAR_OUTPUT KP : REAL; TN : REAL; TV : REAL; KI : REAL; KD : REAL; END_VAR VAR TX : REAL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by tobias takahashi calculates controller parameters for P, PI and PID controllers based on the ziegler nichols method. *) (* @END_DECLARATION := '0' *) IF TU > 0.0 AND KS > 0.0 THEN TX := TG / TU / KS; END_IF; IF pi AND PID THEN KP := 0.0; TN := 0.0; TV := 0.0; ELSIF PID THEN KP := PID_K * TX; TN := PID_TN * TU; TV := PID_TV * TU; ELSIF PI THEN KP := PI_K * TX; TN := PI_TN * TU; ELSE KP := P_K * TX; END_IF; (* KI and KD are calculated *) IF TN > 0.0 THEN KI := KP / TN; ELSE KI := 0.0; END_IF; KD := KP * TV; (* revision history hm 4. nov 2007 rev 1.0 original version hm 11. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CTRL_IN : REAL VAR_INPUT SET_POINT, ACTUAL, NOISE : REAL; END_VAR (* version 1.0 2. jun 2008 programmer hugo tested by tobias *) (* @END_DECLARATION := '0' *) (* calculate the process error DIFF *) CTRL_IN := DEAD_ZONE(SET_POINT - ACTUAL, NOISE); (* revision history hm 2. jun. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CTRL_OUT VAR_INPUT CI, OFFSET, MAN_IN, LIM_L, LIM_H : REAL; MANUAL : BOOL; END_VAR VAR_OUTPUT Y : REAL; LIM : BOOL; END_VAR (* version 1.1 5. nov 2008 programmer hugo tested by oscat *) (* @END_DECLARATION := '0' *) Y := SEL(MANUAL, CI, MAN_IN) + OFFSET; (* Limit the output *) IF Y > LIM_L AND Y < LIM_H THEN LIM := FALSE; ELSE Y := LIMIT(LIM_L, Y, LIM_H); LIM := TRUE; END_IF; (* revision history hm 2. jun. 2008 rev 1.0 original version hm 5. nov. 2008 rev 1.1 optimized code *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CTRL_PI VAR_INPUT ACT, SET, SUP, OFS, M_I : REAL; MAN : BOOL; RST : BOOL; KP : REAL := 1.0; KI : REAL := 1.0; LL : REAL := -1000.0; LH : REAL := 1000.0; END_VAR VAR_OUTPUT Y : REAL; DIFF : REAL; LIM : BOOL; END_VAR VAR pi : FT_PIWL; co : CTRL_OUT; END_VAR (* version 2.0 30. jun 2008 programmer hugo tested by oscat FT_PI is a PI controller with manual functionality. The PID controller works according to the fomula Y = e *(KP+ KI * INTEG(e) ) + offset, while e = set_point - actual. a rst will reset all internal data, while a switch to manual will cause the controller to follow the function Y = manual_in + offset. limit_h and Limit_l set the possible output range of Y. the output flags lim will signal that the output limits are active and overflow will signal that the integrator has reached its limits. since rev 1.1 the "trapezregel is used for more accuracy. rev 1.2 added selective integratin which means the integrative component is only active within a small range of the target value this avoids the integrator to go to limits while an input setpoint change happened and is only causing overshoots. the int_band is by default 100 which means the int is active all the time and if set to for example to 0.1 the integrator is only active while the input is between 0.9 and 1.1 of the set_point value. default values for KP = 1, TN = 1, TV = 1, LIMIT_L = -1000, LIMIT_H = +1000. *) (* @END_DECLARATION := '0' *) DIFF := CTRL_IN(SET, ACT, SUP); pi(in := DIFF, kp := KP, ki := KI, lim_l := LL, lim_h := LH, rst := RST); co(ci := pi.Y, OFFSET := OFS, man_in := M_I, lim_l := LL, lim_h := LH, MANUAL := MAN); Y := co.Y; LIM := co.LIM; (* revision history hm 31.10.2007 rev 1.0 original version hm 3.11.2007 rev 1.1 added noise input to filter noise added output diff set limit output false when output is within limits overfolw was not set correctly hm 5. jan 2008 rev 1.2 improved performance hm 20. jun. 2008 rev 2.0 rewritten using new modular approach *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CTRL_PID VAR_INPUT ACT, SET, SUP, OFS, M_I : REAL; MAN : BOOL; RST : BOOL; KP : REAL := 1.0; TN : REAL := 1.0; TV : REAL := 1.0; LL : REAL := -1000.0; LH : REAL := 1000.0; END_VAR VAR_OUTPUT Y : REAL; DIFF : REAL; LIM : BOOL; END_VAR VAR pid : FT_PIDWL; co : CTRL_OUT; END_VAR (* version 2.0 30. jun. 2008 programmer hugo tested by oscat FT_PI is a PI controller with manual functionality. The PID controller works according to the fomula Y = e *(KP+ KI * INTEG(e) ) + offset, while e = set_point - actual. a rst will reset all internal data, while a switch to manual will cause the controller to follow the function Y = manual_in + offset. limit_h and Limit_l set the possible output range of Y. the output flags lim will signal that the output limits are active. *) (* @END_DECLARATION := '0' *) DIFF := CTRL_IN(SET, ACT, SUP); pid(in := DIFF, kp := KP, tn := TN, tv := TV, lim_l := LL, lim_h := LH, rst := RST); co(ci := pid.Y, OFFSET := OFS, man_in := M_I, lim_l := LL, lim_h := LH, MANUAL := MAN); Y := co.Y; LIM := co.LIM; (* revision history hm 1.12.2006 rev 1.1 changed algorithm to trapezregel for higher accuracy. hm 3.1.2007 rev 1.2 added integ_band to select when the integrator is active. hm 3.3.2007 rev 1.3 added default values to inputs KP, TN, TV, LIMIT_L und LIMIT_H. hm 31.oct 2007 rev 1.4 total rewrite of the module to avoid failures when one of the limits is 0 hm 3.11.2007 rev 1.5 added noise input to filter noise added output diff set limit output false when output is within limits overfolw was not set correctly hm 5. jan 2008 rev 1.6 improved code for better performance *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CTRL_PWM VAR_INPUT CI, MAN_IN : REAL; MANUAL : BOOL; F : REAL; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR PW : PWM_DC; END_VAR (* version 1.1 21. oct. 2008 programmer hugo tested by oscat *) (* @END_DECLARATION := '0' *) PW(F := F, DC := SEL(MANUAL,CI,MAN_IN)); Q := PW.Q; (* revision history hm 3. jun. 2008 rev 1.0 original version hm 21. oct. 2008 rev 1.1 optimized code *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DEAD_BAND : REAL VAR_INPUT X : REAL; L : REAL; END_VAR (* version 1.2 18. jan. 2011 programmer hugo tested by oscat DEAD_BAND ist eine lineare Übertragungsfunktion mit Totzone. Die Funktion verschiebt den positiven Teil der Kurve um +L und den negativen Teil der Kurve um -L. DEAD_BAND = X - L wenn X > L) DEAD_BAND = X + L wenn X < -L DEAD_BAND = 0 wenn Abs(X) <= L *) (* @END_DECLARATION := '0' *) IF X > L THEN DEAD_BAND := X - L; ELSIF X < -L THEN DEAD_BAND := X + L; ELSE DEAD_BAND := 0.0; END_IF; (* revision history hm 2. nov. 2007 rev 1.0 original version hm 14. jun. 2008 rev 1.1 improved performance hm 18. jan. 2011 rev 1.2 assign 0 before return *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK DEAD_BAND_A VAR_INPUT X : REAL; T : TIME; KL : REAL := 1.0; LM : REAL; END_VAR VAR_OUTPUT Y : REAL; L : REAL; END_VAR VAR tp1 : FT_PT1; tp2 : FT_PT1; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by oscat DEAD_BAND ist eine lineare Übertragungsfunktion mit Totzone. Die Funktion verschiebt den positiven Teil der Kurve um -L und den negativen Teil der Kurve um +L. DEAD_BAND = X wenn Abs(X) > L wenn Abs(X) > L DEAD_BAND = 0 wenn Abs(X) <= L Die Breite der Totzone wird automatisch aus dem Signalrauschen errechnet. *) (* @END_DECLARATION := '0' *) (* filter the input signal *) tp1(in := X, T:= T); (* filter the HF portion to generate a stable L *) tp2(in := ABS(tp1.out - X), T := MULTIME(T, 4.0)); (* now we determine L which is half the width of the dead band *) L := MIN(KL * tp2.out, LM); IF X > L THEN Y := X - L; ELSIF X < -L THEN Y := X + L; ELSE Y := 0.0; END_IF; (* revision history hm 14. jun. 2008 rev 1.0 original version hm 11. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DEAD_ZONE : REAL VAR_INPUT X : REAL; L : REAL; END_VAR (* version 1.2 11. mar. 2009 programmer hugo tested by oscat dead_zone2 is a linear transfer function which follows a linear function except for x is close to 0. Y = X if abs(x) > L otherwise its 0. *) (* @END_DECLARATION := '0' *) IF ABS(x) > L THEN dead_zone := X; ELSE DEAD_ZONE := 0.0; END_IF; (* revision history hm 12. feb. 2007 rev 1.0 original version hm 14. jun. 2008 rev 1.1 improved performance hm 11. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK DEAD_ZONE2 VAR_INPUT X : REAL; L : REAL; END_VAR VAR_OUTPUT Y: REAL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by tobias dead_zone2 is a linear transfer function which follows a linear function except for x is close to 0. Y = X if abs(x) > L. for values of 0 +/- L a hysteresis function will hold the output at + or - L. *) (* @END_DECLARATION := '0' *) IF ABS(x) > L THEN Y := X; ELSIF Y > 0.0 THEN Y := L; ELSE Y := -L; END_IF; (* revision history hm 12. feb. 2007 rev 1.0 original version hm 11. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_DERIV VAR_INPUT in : REAL; K : REAL := 1.0; run : BOOL := TRUE; END_VAR VAR_OUTPUT out : REAL; END_VAR VAR old: REAL; tx: DWORD; last: DWORD; init: BOOL; tc: REAL; END_VAR (* version 1.5 11. mar. 2009 programmer hugo tested by oscat FT_deriv calculates the derivate over the signal "in" with Faktor "K". a run input enables or stops the calculation, if left unconnected its true and therfore the calculation is executed. if K is not specified the default is 1. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := T_PLC_US(); tc := DWORD_TO_REAL(tx - last); last := tx; (* init on first startup *) IF NOT init THEN init := TRUE; old := in; ELSIF run AND tc > 0.0 THEN out := (in - old) / tc * 1000000.0 * K; old := in; ELSE out := 0.0; END_IF; (* hm 3.1.2007 rev 1.1 added init code for startup set the default for K to 1 hm 15. sep 2007 rev 1.2 replaced Time() with T_PLC_US for compatibility and performance reasons increased accuracy and work in microseconds internally hm 29 oct 2007 rev 1.3 prohibit calculation when tx - last = 0 to avoid division by 0 and increase accuracy on fast systems hm 6. nov. 2008 rev 1.4 improved performance hm 11. mar. 2009 rev 1.5 inproved code *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_IMP VAR_INPUT in : REAL; T : TIME; K : REAL := 1.0; END_VAR VAR_OUTPUT out : REAL; END_VAR VAR t1 : FT_PT1; END_VAR (* version 1.1 3 Jan 2007 programmer hugo tested by oscat FT_IMP is an impulse filter (high pass filter) with the time T and factor K. *) (* @END_DECLARATION := '0' *) T1(in:= in, T:=T); out := (in - t1.out) * K; (* hm 3.1.2007 rev 1.1 added factor K *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_INT VAR_INPUT IN : REAL; K : REAL := 1; RUN : BOOL := TRUE; RST : BOOL; OUT_MIN : REAL := -1E37; OUT_MAX : REAL := 1E37; END_VAR VAR_OUTPUT OUT : REAL; LIM : BOOL; END_VAR VAR INTeg : INTEGRATE; END_VAR (* version 2.2 10. mar. 2009 programmer hugo tested by oscat FT_int is an integrator with input IN and factor K. integration is only done while run = true, if run is false integration is stopped and the out remains at the last value. if run is left unconnected, run is considered true by the function block. the output ET will display the total integration time from last reset. the out_min and out_max values can be set to ,imit the output value range. the default for K is 1. *) (* @END_DECLARATION := '0' *) IF rst THEN out := 0.0; ELSE integ(X := IN, E := RUN, K := K, Y := out); END_IF; (* limit the outputs *) IF out >= OUT_MAX THEN out := out_max; LIM := TRUE; ELSIF out <= out_min THEN out := out_min; lim := TRUE; ELSE lim := FALSE; END_IF; (* hm 13.12.2006 rev 1.1 changed to "trapezregel" which increases accuracy before out := out + in * time new: out := out + (in + in_last) / 2 * time hm 15.1.2007 rev 1.2 added default for k to be 1. hm 15.9.2007 rev 1.3 replaced time() with T_PLC_US for compatibility and performance reason increased internal accuracy to microseconds hm 29. oct 2007 rev 1.4 changed code so int will not be called is time difference is 0 to increase accuracy on systems with cycle times below 1ms hm 2. dec 2007 rev 1.5 changed code for better performance hm 5. jan 2008 rev 1.6 further improvements in performance hm 8. feb 2008 rev 1.7 deleted limits +/- 1000 now limit is the range of real added variables out1 and out2 to extend the resolution corrected an error with elapsed time deleted unusfull output ET hm 13. mar 2008 rev 1.8 changed preset value out_min to -1e37. stop integrator at the limits. hm 16. mar 2008 rev 1.9 added type conversion to avoid warning under codesys 3.0 hm 2. jun. 2008 rev 2.0 rewritten with clear code limits can now be set without run hm 3. nov. 2008 rev 2.1 optimized code using INTEGRATE and LIMX hm 10. mar. 2009 rev 2.2 removed nested comments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_INT2 VAR_INPUT IN : REAL; K : REAL := 1.0; RUN : BOOL := TRUE; RST : BOOL; OUT_MIN : REAL := -1.0E38; OUT_MAX : REAL := 1.0E38; END_VAR VAR_OUTPUT OUT : REAL; LIM : BOOL; END_VAR VAR integ: INTEGRATE; ix : REAL; val : REAL2; END_VAR (* version 1.2 11. mar. 2009 programmer hugo tested by oscat FT_int2 is an integrator with input IN and factor K. integration is only done while run = true, if run is false integration is stopped and the out remains at the last value. if run is left unconnected, run is considered true by the function block. the out_min and out_max values can be set to ,imit the output value range. the default for K is 1. FT_INT2 has double presision accuracy (14 digits). *) (* @END_DECLARATION := '0' *) IF RST THEN val := R2_SET(0.0); out := 0.0; ELSE integ(X := IN, E := RUN, K := K, Y := ix); val := R2_ADD(val, ix); ix := 0.0; OUT := val.RX; END_IF; (* check output for limits *) IF out > OUT_MIN AND out < OUT_MAX THEN LIM := FALSE; ELSE OUT := LIMIT(OUT_MIN, OUT, OUT_MAX); val := R2_SET(OUT); LIM := TRUE; END_IF; (* revision history hm 2. jun. 2008 rev 1.0 original version hm 5. nov. 2008 rev 1.1 rewritten code using integrate hm 11. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_PD VAR_INPUT IN : REAL; KP : REAL := 1.0; TV : REAL := 1.0; END_VAR VAR_OUTPUT Y : REAL; END_VAR VAR diff : FT_DERIV; END_VAR (* version 1.0 3. jun 2008 programmer hugo tested by tobias FT_PD is a PD controller. The PD controller works according to the fomula Y = KP *(IN + DERIV(e) ). *) (* @END_DECLARATION := '0' *) (* run differentiator *) diff(IN := IN, K := TV); (* combine both values *) Y := KP * (diff.out + IN); (* revision history hm 3. jun. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_PDT1 VAR_INPUT IN : REAL; KP : REAL := 1.0; TV : REAL := 1.0; T1 : REAL := 1.0; END_VAR VAR_OUTPUT Y : REAL; END_VAR VAR diff : FT_DERIV; TP : FT_PT1; END_VAR (* version 1.0 3. jun 2008 programmer hugo tested by tobias FT_PD is a PD controller. The PD controller works according to the fomula Y = KP *(IN + DERIV(e) ). *) (* @END_DECLARATION := '0' *) (* run differentiator *) diff(IN := IN, K := TV); (* Run PT1 filter *) tp(in := diff.out, T := REAL_TO_TIME(T1)); (* combine both values *) Y := KP * (tp.out + IN); (* revision history hm 3. jun. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_PI VAR_INPUT IN : REAL; KP : REAL := 1.0; KI : REAL := 1.0; ILIM_L : REAL := -1E38; ILIM_H : REAL := 1E38; IEN : BOOL := TRUE; RST : BOOL; END_VAR VAR_OUTPUT Y : REAL; LIM : BOOL; END_VAR VAR integ : FT_INT; END_VAR (* version 2.0 3. jun 2008 programmer hugo tested by tobias FT_PI is a PI controller. The PID controller works according to the fomula Y = IN *(KP+ KI * INTEG(e) ). a rst will reset the integrator to 0 ilim_h and iLim_l set the possible output range of the internal integrator. the output flags lim will signal that the output limits are active. default values for KP = 1, KI = 1, ILIM_L = -1E37, iLIM_H = +1E38. *) (* @END_DECLARATION := '0' *) (* run integrator *) integ(IN := IN, K := KI, RUN := IEN, RST := RST, OUT_MIN := ILIM_L, OUT_MAX := ILIM_H); (* check if integrator has reached its limits and set overflow *) LIM := integ.LIM; Y := KP * IN + integ.Out; (* revision history hm 3. jun. 2008 rev 2.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_PID VAR_INPUT IN : REAL; KP : REAL := 1.0; TN : REAL := 1.0; TV : REAL := 1.0; ILIM_L : REAL := -1.0E38; ILIM_H : REAL := 1.0E38; IEN : BOOL := TRUE; RST : BOOL; END_VAR VAR_OUTPUT Y : REAL; LIM : BOOL; END_VAR VAR integ : FT_INT; diff : FT_DERIV; END_VAR (* version 2.1 11. mar. 2009 programmer hugo tested by tobias FT_PI is a PI controller. The PID controller works according to the fomula Y = IN *(KP+ KI * INTEG(e) ). a rst will reset the integrator to 0 ilim_h and iLim_l set the possible output range of the internal integrator. the output flags lim will signal that the output limits are active. default values for KP = 1, KI = 1, ILIM_L = -1E37, iLIM_H = +1E38. *) (* @END_DECLARATION := '0' *) (* run integrator only if TN > 0 *) IF TN > 0.0 THEN integ(IN := IN, K := 1.0 / TN, RUN := IEN, RST := RST, OUT_MIN := ILIM_L, OUT_MAX := ILIM_H); ELSE integ(RST := FALSE); END_IF; (* run differentiator *) diff(IN := IN, K := TV); (* combine both values *) Y := KP * (integ.Out + diff.out + IN); (* check if integrator has reached its limits and set overflow *) LIM := integ.LIM; (* revision history hm 3. jun. 2008 rev 2.0 original version hm 11. mar. 2009 rev 2.1 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_PIDW VAR_INPUT IN : REAL; KP : REAL := 1.0; TN : REAL := 1.0; TV : REAL := 1.0; LIM_L : REAL := -1.0E38; LIM_H : REAL := 1.0E38; RST : BOOL; END_VAR VAR_OUTPUT Y : REAL; LIM : BOOL; END_VAR VAR integ : INTEGRATE; diff : FT_DERIV; YI: REAL; END_VAR (* version 1.2 11. mar. 2009 programmer hugo tested by oscat FT_PIDW is a PID controller with dynamic wind_up reset. The PID controller works according to the fomula Y = KP *(IN + KI * INTEG(e) + DERIV(e) ). a rst will reset the integrator to 0 lim_h and Lim_l set the possible output range of the internal integrator. the output flags lim will signal that the output limits are active. default values for KP = 1, KI = 1, ILIM_L = -1E37, iLIM_H = +1E38. *) (* @END_DECLARATION := '0' *) (* run the integrator *) IF tn = 0.0 OR rst THEN integ(E := FALSE, Y := YI); YI := 0.0; ELSE integ(X := IN, K := 1.0 / TN, E := NOT LIM, Y := YI); END_IF; (* add up integrator and linear part *) Y := KP * (IN + YI); (* run differentiator *) diff(IN := IN, K := TV); (* set lim before differentiator is added to stop integrator if necessary *) IF Y > LIM_L AND Y < LIM_H THEN LIM := FALSE; ELSE LIM := TRUE; END_IF; (* add differential part and limit output Y *) Y := LIMIT(LIM_L, Y + KP * diff.out, LIM_H); (* revision history hm 3. jun. 2008 rev 1.0 original version hm 5. nov. 2008 rev 1.1 changed code to use integrate hm 11. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_PIDWL VAR_INPUT IN : REAL; KP : REAL := 1.0; TN : REAL := 1.0; TV : REAL := 1.0; LIM_L : REAL := -1.0E38; LIM_H : REAL := 1.0E38; RST : BOOL; END_VAR VAR_OUTPUT Y : REAL; LIM : BOOL; END_VAR VAR piwl : FT_PIWL; diff : FT_DERIV; END_VAR (* version 1.3 13. nov. 2009 programmer hugo tested by tobias FT_PIDWL is a PID controller with dynamic wind_up reset. The PID controller works according to the fomula Y = KP *(IN + KI * INTEG(e) + DERIV(e) ). a rst will reset the integrator to 0 lim_h and Lim_l set the possible output range of the internal integrator. the output flags lim will signal that the output limits are active. default values for KP = 1, KI = 1, ILIM_L = -1E37, iLIM_H = +1E38. *) (* @END_DECLARATION := '0' *) (* if rst then *) IF rst THEN piwl(rst := TRUE); piwl.RST := FALSE; ELSE (* run PIWL controller first *) (* we need to check if TN = 0 and do alternative calls *) IF TN = 0.0 THEN piwl(in := IN * KP, KP := 1.0, KI := 0.0, LIM_L := LIM_L, LIM_H := LIM_H); ELSE piwl(in := IN * KP, KP := 1.0, KI := 1.0 / TN, LIM_L := LIM_L, LIM_H := LIM_H); END_IF; (* run differentiator and add_to_output *) diff(IN := IN, K := KP * TV); Y := piwl.Y + diff.out; (* limit the output *) IF Y < LIM_L THEN LIM := TRUE; Y := LIM_L; ELSIF Y > LIM_H THEN LIM := TRUE; Y := LIM_H; ELSE LIM := FALSE; END_IF; END_IF; (* revision history hm 13. jun. 2008 rev 1.0 original version hm 25. jan 2008 rev 1.1 multiply differential part with KP hm 11. mar. 2009 rev 1.2 real constants updated to new systax using dot hm 13. nov. 2009 rev 1.3 fixed code for negative KP *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_PIW VAR_INPUT IN : REAL; KP : REAL := 1.0; KI : REAL := 1.0; LIM_L : REAL := -1E38; LIM_H : REAL := 1E38; RST : BOOL; END_VAR VAR_OUTPUT Y : REAL; LIM : BOOL; END_VAR VAR integ : FT_INT; END_VAR (* version 1.0 3. jun 2008 programmer hugo tested by tobias FT_PIW is a PI controller. The PID controller works according to the fomula Y = IN *(KP+ KI * INTEG(e) ). a rst will reset the integrator to 0 ilim_h and iLim_l set the possible output range of the internal integrator. the output flag lim will signal that the output limits are active. the controller is equipped with anti wind_up circuitry that stops the integrator when lim_h or lim_l is reached default values for KP = 1, KI = 1, ILIM_L = -1E37, iLIM_H = +1E38. *) (* @END_DECLARATION := '0' *) (* run integrator *) integ(IN := IN, K := KI, RUN := NOT LIM, RST := RST); (* set output value *) Y := KP * IN + integ.Out; (* check for limits and set integrator for anti wind up *) IF Y < LIM_L THEN Y := LIM_L; LIM := TRUE; ELSIF Y > LIM_H THEN Y := LIM_H; LIM := TRUE; ELSE LIM := FALSE; END_IF; (* revision history hm 3. jun. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_PIWL VAR_INPUT IN : REAL; KP : REAL := 1.0; KI : REAL := 1.0; LIM_L : REAL := -1.0E38; LIM_H : REAL := 1.0E38; RST : BOOL; END_VAR VAR_OUTPUT Y : REAL; LIM : BOOL; END_VAR VAR init: BOOL; tx: DWORD; tc : REAL; t_last: DWORD; in_last : REAL; i: REAL; p: REAL; END_VAR (* version 1.3 11. mar. 2009 programmer hugo tested by oscat FT_PIWL is a PI controller. The PID controller works according to the fomula Y = IN *(KP+ KI * INTEG(e) ). a rst will reset the integrator to 0 lim_h and Lim_l set the possible output range of the controller. the output flag lim will signal that the output limits are active. the integrator ist equipped with anti wind-up circuitry which limits trhe total output ranke to lim_l and lim_h default values for KP = 1, KI = 1, ILIM_L = -1E37, iLIM_H = +1E38. *) (* @END_DECLARATION := '0' *) (* initialize at power_up *) IF NOT init OR RST THEN init := TRUE; in_last := in; t_last := T_PLC_US(); i := 0.0; tc := 0.0; ELSE (* read last cycle time in Microseconds *) tx := T_PLC_US(); tc := DWORD_TO_REAL(tx - t_last); t_last := tx; (* calculate proportional part *) p := KP * IN; (* run integrator *) i := (IN + in_last) * 5.0E-7 * KI * tc + i; in_last := IN; (* calculate output Y *) Y := p + i; (* check output for limits *) IF Y >= LIM_H THEN Y := LIM_H; IF ki <> 0.0 THEN i := LIM_H - p; ELSE i := 0.0; END_IF; LIM := TRUE; ELSIF Y <= LIM_L THEN Y := LIM_L; IF ki <> 0.0 THEN i := LIM_L - p; ELSE i := 0.0; END_IF; LIM := TRUE; ELSE LIM := FALSE; END_IF; END_IF; (* revision history hm 13. jun. 2008 rev 1.0 original version hm 27. oct. 2008 rev 1.1 integrator will not be adjusted when ki = 0 hm 25. jan 2009 rev 1.2 module will also work with negative K hm 11. mar. 2009 rev 1.3 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_PT1 VAR_INPUT in : REAL; T : TIME; K : REAL := 1.0; END_VAR VAR_OUTPUT out : REAL; END_VAR VAR last : DWORD; tx: DWORD; init: BOOL; END_VAR (* version 1.11 18. jan. 2011 programmer hugo tested by oscat FT_PT1 is an low pass filter with a programmable time T and faktor K. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := T_PLC_US(); (* startup initialisation *) IF NOT init OR T = t#0s THEN init := TRUE; out := K * in; ELSE out := out + (in * K - out) * DWORD_TO_REAL(Tx - last) / TIME_TO_REAL(T) * 1.0E-3; IF ABS(out) < 1.0E-20 THEN out := 0.0; END_IF; END_IF; last := tx; (* hm 1.1.2007 rev 1.1 corrected error while startup value was not correct for very small time values real output would run out of range. hm 3.1.2007 rev 1.2 corrected an error for falling edge failures. added output faktor K. hm 27. 2. 2007 rev 1.3 output will be input during init for definitive startup condition. hm 15.9.2007 rev 1.4 changed time() to T_PLC_US() for compatibilitxy resons increased internal accuracy to Microseconds instead of Milliseconds hm 23. oct 2007 rev 1.5 added out := in to the init statements hm 30. nov 2007 rev 1.6 changed out to be K * in during initialization hm 5. jan 2008 rev 1.7 improved code for better performance hm 16. mar. 2008 rev 1.8 added type conversion to avoid warning under codesys 3.0 hm 14. jun. 2008 rev 1.9 improved code hm 11. mar. 2009 rev 1.10 real constants updated to new systax using dot hm 18. jan. 2011 rev 1.11 avoid underrun of out *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_PT2 VAR_INPUT in : REAL; T : TIME; D : REAL; K : REAL := 1.0; END_VAR VAR_OUTPUT out : REAL; END_VAR VAR init: BOOL; int1 : INTEGRATE; int2 : INTEGRATE; tn: REAL; I1, I2 : REAL; tn2: REAL; END_VAR (* version 1.5 11. mar. 2009 programmer hugo tested by oscat FT_PT2 is a 2nd grade filter with programmable times T, D and faktor K. *) (* @END_DECLARATION := '0' *) (* startup initialisation *) IF NOT init OR T = T#0s THEN init := TRUE; out := K * in; I2 := out; ELSE TN := TIME_TO_REAL(T) * 1.0E-3; tn2 := TN * TN; int1(X := in * K / tn2 - I1 * 0.5 * D / TN - I2 / TN2, Y := I1); int2(X := I1,Y := I2); out := I2; END_IF; (* revision history 15.1.2007 hm rev 1.1 changed formula to new more acurate formula hm 15.9.2007 rev 1.2 deleted unused code for init system time reading tx hm 30.11.2007 rev 1.3 changed out to be K * in during initialization avoind divide by 0 if tn = 0 hm 3. nov. 2008 rev 1.4 optimized code and fixed a problem with init hm 11. mar. 2009 rev 1.5 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_TN16 VAR_INPUT in : REAL; T : TIME; END_VAR VAR_OUTPUT out : REAL; trig : BOOL; END_VAR VAR length : INT := 16; X : ARRAY[0..15] OF REAL; cnt : INT; last : TIME; tx: TIME; init: BOOL; END_VAR (* version 1.1 16 sep 2007 programmer hugo tested by tobias FT_TN16 is delay function, it will delay a signal by a specified time : T and will store 16 values of in before they are put thru to out. for higher or lower resolution please use FT_TN8 or FT_TN64 instead. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := DWORD_TO_TIME(T_PLC_MS()); trig := FALSE; IF NOT init THEN x[cnt] := in; init := TRUE; last := tx; ELSIF tx - last >= T / length THEN IF cnt = length - 1 THEN cnt := 0; ELSE cnt := cnt + 1; END_IF; Out := X[cnt]; x[cnt] := in; last := tx; Trig := TRUE; END_IF; (* revision history hm 1. jan 2007 rev 1.0 original version hm 16. sep 2007 rev 1.1 changes time() to T_plc_ms() for compatibility reasons *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_TN64 VAR_INPUT in : REAL; T : TIME; END_VAR VAR_OUTPUT out : REAL; trig: BOOL; END_VAR VAR length : INT := 64; X : ARRAY[0..63] OF REAL; cnt : INT; last : TIME; tx: TIME; init: BOOL; END_VAR (* version 1.1 15 sep 2007 programmer hugo tested by tobias FT_TN7 is delay function, it will delay a signal by a specified time : T and will store 64 values of in before they are put thru to out. if lower resolution is needed, pls use FT_TN8 or FT_TN16 instead. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := DWORD_TO_TIME(T_PLC_MS()); trig := FALSE; IF NOT init THEN x[cnt] := in; init := TRUE; last := tx; ELSIF tx - last >= T / length THEN IF cnt = length - 1 THEN cnt := 0; ELSE cnt := cnt + 1; END_IF; Out := X[cnt]; x[cnt] := in; last := tx; trig := TRUE; END_IF; (* revision history hm 1. jan 2007 rev 1.0 original version hm 16. sep 2007 rev 1.1 changes time() to T_plc_ms() for compatibility reasons *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_TN8 VAR_INPUT in : REAL; T : TIME; END_VAR VAR_OUTPUT out : REAL; trig: BOOL; END_VAR VAR length : INT := 8; X : ARRAY[0..7] OF REAL; cnt : INT; last : TIME; tx: TIME; init: BOOL; END_VAR (* version 1.1 15 Sep 2007 programmer hugo tested by tobias FT_TN8 is delay function, it will delay a signal by a specified time : T and will store 8 values of in before they are put thru to out. if higher resolution is needed, pls use FT_TN16 or FT_TN64 instead. *) (* @END_DECLARATION := '0' *) tx := DWORD_TO_TIME(T_PLC_MS()); trig := FALSE; IF NOT init THEN x[cnt] := in; init := TRUE; last := tx; ELSIF tx - last >= T / length THEN IF cnt = length - 1 THEN cnt := 0; ELSE cnt := cnt + 1; END_IF; Out := X[cnt]; x[cnt] := in; last := tx; trig := TRUE; END_IF; (* revision history hm 1. jan 2007 rev 1.0 original version hm 16. sep 2007 rev 1.1 changes time() to T_plc_ms() for compatibility reasons *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK HYST VAR_INPUT In : REAL; ON : REAL; OFF : REAL; END_VAR VAR_OUTPUT Q : BOOL; win : BOOL; END_VAR (* version 1.0 2. jun. 2008 programmer hugo tested BY oscat This Hystereses function has two modes: 1. if on > off then Q will be switched high when in > on and switched low when in < off. 2. if on < off then Q will be switched high when in < on and switched low when in > off. the output win will be high when in is between low and high. *) (* @END_DECLARATION := '0' *) IF ON >= OFF THEN IF IN < OFF THEN Q := FALSE; WIN := FALSE; ELSIF IN > ON THEN Q := TRUE; WIN := FALSE; ELSE WIN := TRUE; END_IF; ELSE IF IN > OFF THEN Q := FALSE; WIN := FALSE; ELSIF IN < ON THEN Q := TRUE; WIN := FALSE; ELSE WIN := TRUE; END_IF; END_IF; (* revision history hm 2. jun 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK HYST_1 VAR_INPUT In : REAL; high : REAL; low : REAL; END_VAR VAR_OUTPUT Q : BOOL; win : BOOL; END_VAR (* version 1.1 2. jun. 2008 programmer hugo tested BY oscat this hysteresis function switches the output high if the input signal reaches obove high and will switch to low when the input falls back below low value. a separate output mid is set if the input stays between low and high value. *) (* @END_DECLARATION := '0' *) IF in < low THEN Q := FALSE; win := FALSE; ELSIF in > high THEN Q := TRUE; win := FALSE; ELSE win := TRUE; END_IF; (* revision history hm 4. aug. 2006 rev 1.0 original version hm 2. jun. 2008 rev 1.1 improved performance *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK HYST_2 VAR_INPUT IN : REAL; VAL : REAL; HYS : REAL; END_VAR VAR_OUTPUT Q : BOOL; WIN: BOOL; END_VAR VAR tmp: REAL; END_VAR (* version 1.2 2. jun. 2008 programmer oscat tested BY oscat this hysteresis function switches the output high if the input signal reaches obove val + hys/2 and will switch to low when the input falls back below val - hys/2 value. a separate output mid is set if the input stays between low and high value. *) (* @END_DECLARATION := '0' *) tmp := val - hys * 0.5; IF in < tmp THEN Q := FALSE; win := FALSE; ELSIF in > tmp + hys THEN Q := TRUE; win := FALSE; ELSE win := TRUE; END_IF; (* revision history hm 4. aug 2006 rev 1.0 original version hm 5. jan 2008 rev 1.1 improved code for better performance hm 2. jun. 2008 rev 1.2 improved performance *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK HYST_3 VAR_INPUT in : REAL; hyst : REAL; val1 : REAL; val2 : REAL; END_VAR VAR_OUTPUT Q1 : BOOL; Q2 : BOOL; END_VAR VAR X: REAL; END_VAR (* version 1.2 5 jan 2008 programmer oscat tested BY oscat this is a double hysteresis function. Out1 follows a hysteresis function defined by val1and hyst, while out 2 follows val2 and hyst. if the input signal is between the two hysteresis switches (val1 and val2) then non of the outputs is active. *) (* @END_DECLARATION := '0' *) X := hyst * 0.5; IF in < val1 - X THEN q1 := TRUE; ELSIF in > val1 + X THEN q1 := FALSE; END_IF; IF in < val2 - X THEN q2 := FALSE; ELSIF in > val2 + X THEN q2 := TRUE; END_IF; (* revision history hm 22. jan 2007 rev 1.0 original version hm 27. dec 2007 rev 1.1 changed code for better performance hm 5. jan 2008 rev 1.2 further performance iprovements *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/control' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK INTEGRATE VAR_INPUT E : BOOL := TRUE; X : REAL; K : REAL := 1.0; END_VAR VAR_IN_OUT Y : REAL; END_VAR VAR X_last : REAL; init: BOOL; last: DWORD; tx: DWORD; END_VAR (* version 1.0 3. nov. 2008 programmer hugo tested by oscat integrate is a plain integrator with I/O for out. *) (* @END_DECLARATION := '0' *) (*read system time *) tx := T_PLC_MS(); IF NOT init THEN init := TRUE; X_last := X; ELSIF E THEN Y := (X + X_LAST) * 0.5E-3 * DWORD_TO_REAL(tx-last) * K + Y; X_last := X; END_IF; last := tx; (* hm 3. nov. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK ASTRO VAR_INPUT m : REAL; AE : REAL; PC : REAL; LJ : REAL; END_VAR VAR_OUTPUT Ym : REAL; YAE : REAL; YPC : REAL; YLJ : REAL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by oscat this function converts different length units any unused input can simply be left open. different inputs connected at the same time will be added up. *) (* @END_DECLARATION := '0' *) YAE := AE + m * 6.6845871535E-012 + PC * 206265.0 + LJ * 63240.0; Ym := YAE * 149.597870E9; YPC := YAE * 4.8481322570E-006; YLJ := YAE * 1.5812776724E-005; (* Länge Meter m SI-Basiseinheit Astronomische Einheit* AE 1 AE = 149,597 870 · E9 m Parsec pc 1 pc = 206265 AE = 30,857 · E15 m Lichtjahr Lj 1 Lj = 9,460 530 · E15 m = 63240 AE = 0,306 59 pc Ångström Å 1 Å = E–l0 m typograph. Punkt p 1 p = 0,376 065 mm • im Druckereigewerbe inch** in 1 in = 2,54 · E–2 m = 25,4 mm*** foot ft 1 ft = 0,3048 m = 30,48 cm yard yd 1 yd = 0,9144 m mile mile 1 mile = 1609,344 m Internat. Seemeile sm 1 sm = 1852 m Fathom fm 1 fm = 1,829 m • in der Seeschifffahrt *) (* revision history hm 27. mar. 2007 rev 1.0 original version hm 11. mar. 2009 rev 1.1 improved code *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BFT_TO_MS : REAL VAR_INPUT BFT : INT; END_VAR (* version 1.0 12. jun 2008 programmer hugo tested by oscdat this function converts wind speed from beaufort to m/s *) (* @END_DECLARATION := '0' *) BFT_TO_MS := EXPT(BFT, 1.5) * 0.836; (* revision history hm 12. 6. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION C_TO_F : REAL VAR_INPUT celsius : REAL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by tobias this function converts celsius to fahrenheit *) (* @END_DECLARATION := '0' *) C_TO_F := celsius * 1.8 + 32.0; (* revision history hm 4. aug. 2006 rev 1.0 original version hm 11. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION C_TO_K : REAL VAR_INPUT Celsius : REAL; END_VAR (* version 1.1 19. aug 2009 programmer hugo tested by tobias this function converts celsius to kelvin *) (* @END_DECLARATION := '0' *) C_TO_K := Celsius - phys.T0; (* revision history hm 4. aug 2006 rev 1.0 original version hm 19. aug 2009 rev 1.1 fixed calculation error *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DEG_TO_DIR : STRING(3) VAR_INPUT DEG : INT; N : INT; L : INT; END_VAR VAR ly: INT; END_VAR (* version 1.1 22. oct. 2008 programmer hugo tested by oscat this function converts degrees in compass direction. the function supports output in english (L=0) and german (L=1). *) (* @END_DECLARATION := '0' *) IF L = 0 THEN ly := LANGUAGE.DEFAULT; ELSE ly := MIN(L, LANGUAGE.LMAX); END_IF; DEG_TO_DIR := LANGUAGE.DIRS[ly, ((SHL(DEG,N-1) + 45) / 90) MOD SHL(INT#2,N)*SHR(INT#8,N)]; (* DIR := ((SHL(DEG,N-1) + 45) / 90) MOD SHL(INT#2,N); explanation : DIR is calculated BY the following formula: DIR := ((DIR + 45) / 90) MOD 4 if N = 1 digit North = 0, East = 1 .... DIR := ((DIR + 22,5) / 45) MOD 8 if N = 2 digit convert to integer calculation DIR := ((DIR*2 + 45) / 90) MOD 8 N = 0, NE = 1 .... ther above formula replaces 2^N with shift for performance *) (* revision histroy hm 11. jun. 2008 rev 1.0 original release hm 22. oct. 2008 rev 1.1 changed size of string variables to 30 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DIR_TO_DEG : INT VAR_INPUT DIR : STRING(3); L : INT; END_VAR VAR ly: INT; i : INT; END_VAR (* version 1.1 22. oct. 2008 programmer hugo tested by oscat this function converts compass directions to degrees it will recognize up to 3 letter directions in english and german writing. *) (* @END_DECLARATION := '0' *) IF L = 0 THEN ly := LANGUAGE.DEFAULT; ELSE ly := MIN(L, LANGUAGE.LMAX); END_IF; FOR i := 0 TO 15 DO IF language.DIRS[ly, i] = DIR THEN EXIT; END_IF; END_FOR; DIR_TO_DEG := SHR(i * 45 + 1, 1); (* revision histroy hm 22. oct. 2008 rev 1.1 original release *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK ENERGY VAR_INPUT J : REAL; C : REAL; Wh : REAL; END_VAR VAR_OUTPUT YJ : REAL; YC : REAL; YWh : REAL; END_VAR (* version 1.2 16. jan. 2010 programmer hugo tested by oscat this function converts different energy units any unused input can simply be left open. different inputs connected at the same time will be added up. *) (* @END_DECLARATION := '0' *) YJ := J + Wh * 3600.0 + C * 4.1868; YC := YJ * 0.238845896627496; YWh := YJ * 2.7777777778E-004; (* Arbeit, Energie, Joule* J 1 J = 1 N · m = 1 W · s = (1/3,6) E–6 kW · h = 1 kg · m2/s2 Wärmemenge Kilowattstunde kW · h 1 kW · h = 3,6 MJ = 860 kcal Elektronvolt eV 1 eV = 160,218 92 E–21 J Erg erg 1 erg = 1E–7 J Kalorie calorie 1 calalorie = 4,1868 J = 1,163 E–3 W · h Therm therm 1 therm = 105,50 · 106 J *) (* revision history hm 27. mar. 2007 rev 1.0 original version hm 11. mar. 2009 rev 1.1 improved code hm 16. jan 2010 rev 1.2 avoid the string cal in comments for codesys import bug *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION F_TO_C : REAL VAR_INPUT fahrenheit : REAL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested BY tobias this FUNCTION converts fahrenheit TO celsius *) (* @END_DECLARATION := '0' *) F_TO_C := (fahrenheit - 32.0) * 0.5555555555555; (* revision history hm 4. aug 2006 rev 1.0 original version hm 11. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION F_TO_OM : REAL VAR_INPUT F : REAL; END_VAR (* version 1.1 18. oct. 2008 programmer hugo tested by oscat this function converts frequency to Omega F Omega = 2*PI*F *) (* @END_DECLARATION := '0' *) F_TO_OM := math.PI2 * F; (* revision history hm 22. jan. 2007 rev 1.0 original version hm 18. oct. 2008 rev 1.1 unsing math constants *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION F_TO_PT : TIME VAR_INPUT F : REAL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by tobias this function converts frequency to periode time *) (* @END_DECLARATION := '0' *) F_TO_PT := DWORD_TO_TIME(REAL_TO_DWORD(1.0 / F * 1000.0)); (* revision history hm 4. aug. 2006 rev 1.0 original version hm 11. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION GEO_TO_DEG : REAL VAR_INPUT D : INT; M : INT; SEC : REAL; END_VAR (* version 1.0 22. jan. 2009 programmer hugo tested by oscat this function converts degrees, minutes seconds to decimal degrees. *) (* @END_DECLARATION := '0' *) GEO_TO_DEG := INT_TO_REAL(D) + INT_TO_REAL(M) * 0.016666666666667 + sec * 0.00027777777777778; (* revision histroy hm 22. jan. 2009 rev 1.0 original release *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION K_TO_C : REAL VAR_INPUT Kelvin : REAL; END_VAR (* version 1.1 19. aug 2009 programmer hugo tested by tobias this function converts kelvin to celsius *) (* @END_DECLARATION := '0' *) K_TO_C := Kelvin + phys.T0; (* revision history hm 4. aug 2006 rev 1.0 original version hm 19. aug 2009 rev 1.1 fixed calculation error *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION KMH_TO_MS : REAL VAR_INPUT kmh : REAL; END_VAR (* version 1.1 6 jan 2007 programmer hugo tested by tobias this function converts velocities from Kilometers / hour to Meters / Second *) (* @END_DECLARATION := '0' *) KMH_TO_MS := kmh * 0.2777777777777; (* revision history hm 4. feb 2007 rev 1.0 original version hm 6. jan 2008 rev 1.1 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK LENGTH VAR_INPUT m : REAL; p : REAL; in : REAL; ft : REAL; yd : REAL; mile : REAL; sm : REAL; fm : REAL; END_VAR VAR_OUTPUT Ym : REAL; Yp : REAL; Yin : REAL; Yft : REAL; Yyd : REAL; Ymile : REAL; Ysm : REAL; Yfm : REAL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by oscat this function converts different length units any unused input can simply be left open. different inputs connected at the same time will be added up. *) (* @END_DECLARATION := '0' *) Ym := m + p * 0.000376065 + in * 0.0254 + ft * 0.3048 + yd * 0.9144 + mile * 1609.344 + sm * 1852.0 + fm * 1.829; Yp := Ym * 2659.11478068951; Yin := Ym * 39.37007874016; Yft := Ym * 3.28083989501; Yyd := Ym * 1.09361329834; Ymile := Ym * 0.00062137119; Ysm := Ym * 0.00053995680; Yfm := Ym * 0.54674685621; (* Länge Meter m SI-Basiseinheit Astronomische Einheit* AE 1 AE = 149,597 870 · E9 m Parsec pc 1 pc = 206265 AE = 30,857 · E15 m Lichtjahr Lj 1 Lj = 9,460 530 · E15 m = 63240 AE = 0,306 59 pc Ångström Å 1 Å = E–l0 m typograph. Punkt p 1 p = 0,376 065 mm • im Druckereigewerbe inch** in 1 in = 2,54 · E–2 m = 25,4 mm*** foot ft 1 ft = 0,3048 m = 30,48 cm yard yd 1 yd = 0,9144 m mile mile 1 mile = 1609,344 m Internat. Seemeile sm 1 sm = 1852 m Fathom fm 1 fm = 1,829 m • in der Seeschifffahrt *) (* revision history hm 27. mar. 2007 rev 1.0 original version hm 11. mar. 2009 rev 1.1 improved code *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MS_TO_BFT : INT VAR_INPUT MS : REAL; END_VAR (* version 1.0 12. jun 2008 programmer hugo tested by oscdat this function converts wind speed from M/s to beaufort *) (* @END_DECLARATION := '0' *) MS_TO_BFT := REAL_TO_INT(EXPT(MS * 1.196172, 0.666667)); (* revision history hm 12. 6. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MS_TO_KMH : REAL VAR_INPUT ms : REAL; END_VAR (* version 1.0 4 Feb 2007 programmer hugo tested by tobias this function converts velocities from Meters / Second to Kilometers / hour. *) (* @END_DECLARATION := '0' *) MS_TO_KMH := ms * 3.6; END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION OM_TO_F : REAL VAR_INPUT OM : REAL; END_VAR (* version 1.1 18. oct. 2008 programmer hugo tested by tobias this function converts Omega F to frequency F = OM / (2*PI) *) (* @END_DECLARATION := '0' *) OM_TO_F := OM / math.PI2; (* revision history hm 22. jan. 2007 rev 1.0 original version hm 18. oct. 2008 rev 1.1 using math constants *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK PRESSURE VAR_INPUT mws : REAL; torr : REAL; att : REAL; atm : REAL; pa : REAL; bar : REAL; END_VAR VAR_OUTPUT Ymws : REAL; Ytorr : REAL; Yatt : REAL; Yatm : REAL; Ypa : REAL; Ybar : REAL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by oscat this function converts different pressure units any unused input can simply be left open. different inputs connected at the same time will be added up. *) (* @END_DECLARATION := '0' *) Ybar := bar + pa * 1.0E-5 + 0.980665 * att + 1.01325 * atm + 0.001333224 * torr + 0.0980665 * mws; Ymws := ybar * 10.1971621297793; Ytorr := ybar * 750.0615050434140; Yatt := ybar * 1.0197162129779; yatm := ybar * 0.9869232667160; Ypa := ybar * 100000.0; (* Druck, Pascal Pa 1 Pa = 1 N/m2 = 1 kg/(s2 E m) . 0,75 E 10.2 mmHg mechanische 1 MPa = 1 N/mm2 . fur Festigkeitsangaben Spannung Bar bar 1 bar = 105 Pa = 103 mbar = 105 kg/(s2 E m) Millimeter- mmHg 1 mmHg = 133,322 Pa = 1,333 22 mbar Quecksilbersaule . nur in Heilkunde zulassig physik. Atmosphare atm 1 atm = 1,013 25 bar techn. Atmosphare at 1 at = 1 kp/cm2 = 0,980665 bar Torr Torr 1 Torr = (101325/760) Pa = 1,333224 mbar Meter-Wassersaule mWS 1 mWS = 9806,65 Pa = 98,0665 mbar psi lbf/in2 1 lbf/in2 = 68,947 57 mbar = 6894,757 Pa *) (* revision history hm 27. mar. 2007 rev 1.0 original version hm 11. mar. 2009 rev 1.1 improved code *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION PT_TO_F : REAL VAR_INPUT PT : TIME; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by tobias this function converts periode time to frequency *) (* @END_DECLARATION := '0' *) PT_TO_F := 1000.0 / DWORD_TO_REAL(TIME_TO_DWORD(PT)); (* revision history hm 4. aug. 2006 rev 1.0 original version hm 11. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SPEED VAR_INPUT ms : REAL; kmh : REAL; kn : REAL; mh : REAL; END_VAR VAR_OUTPUT Yms : REAL; Ykmh : REAL; Ykn : REAL; Ymh : REAL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by oscat this function converts different speed units any unused input can simply be left open. different inputs connected at the same time will be added up. *) (* @END_DECLARATION := '0' *) Yms := ms + kmh * 0.27777777777778 + kn * 0.5144444 + mh * 0.44704; Ykmh := Yms * 3.6; Ykn := Yms * 1.94384466037535; Ymh := Yms * 2.23693629205440; (* Geschwindigkeit Meter durch Sekunde m/s 1 m/s = 3,6 km/h km durch (pro) Stunde, nicht „Stundenkilometer“ verwenden Knoten kn 1 kn = 1 sm/h = 0,5144 m/s *) (* revision history hm 27. mar. 2009 rev 1.0 original version hm 11. mar. 2009 rev 1.1 improved code *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/conversion' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK TEMPERATURE VAR_INPUT K : REAL; C : REAL := -273.15; F : REAL := -459.67; Re : REAL := -218.52; Ra : REAL; END_VAR VAR_OUTPUT YK : REAL; YC : REAL; YF : REAL; YRe : REAL; YRa : REAL; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function converts different temperature units any unused input can simply be left open. different inputs connected at the same time will be added up. *) (* @END_DECLARATION := '0' *) YK := K + (C + 273.15) + (F + 459.67) * 0.555555555555 + (Re * 1.25 + 273.15) + (Ra * 0.555555555555); YC := YK -273.15; YF := YK * 1.8 - 459.67; YRe := (YK - 273.15) * 0.8; YRa := YK * 1.8; (* revision history hm 21. feb. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK ALARM_2 VAR_INPUT X : REAL; LO_1 : REAL; HI_1 : REAL; LO_2 : REAL; HI_2 : REAL; HYS : REAL; END_VAR VAR_OUTPUT Q1_LO : BOOL; Q1_HI : BOOL; Q2_LO : BOOL; Q2_HI : BOOL; END_VAR VAR tmp: REAL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested BY tobias ALARM_2 will check two pairs of limits and signal when the input is above or below a set limit. with the input HYS a hysteresis can be defined for all limits. *) (* @END_DECLARATION := '0' *) tmp := X - Hys * 0.5; IF tmp > LO_1 THEN Q1_LO := FALSE; END_IF; IF tmp > LO_2 THEN Q2_LO := FALSE; END_IF; IF tmp > HI_1 THEN Q1_HI := TRUE; END_IF; IF tmp > HI_2 THEN Q2_HI := TRUE; END_IF; tmp := tmp + hys; IF tmp < LO_1 THEN Q1_LO := TRUE; END_IF; IF tmp < LO_2 THEN Q2_LO := TRUE; END_IF; IF tmp < HI_1 THEN Q1_HI := FALSE; END_IF; IF tmp < HI_2 THEN Q2_HI := FALSE; END_IF; (* revision history hm 19. may. 2008 rev 1.0 original version hm 11. mar. 2009 rev 1.1 improved code *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK BAR_GRAPH VAR_INPUT X : REAL; rst : BOOL; END_VAR VAR_INPUT CONSTANT trigger_Low : REAL; trigger_High : REAL; Alarm_low : BOOL; Alarm_high : BOOL; log_scale : BOOL; END_VAR VAR_OUTPUT LOW, Q1, Q2, Q3, Q4, Q5, Q6, HIGH : BOOL; Alarm : BOOL; Status : BYTE; END_VAR VAR init : BOOL; T1, T2, T3, T4, T5 : REAL; END_VAR VAR_TEMP temp : REAL; END_VAR (* version 1.2 6 jan 2008 programmer hugo tested BY hans bar graph is a muti window comparator which displays an analog input signal on 8 digital outputs. only one output is active a any given time depending on the value of the input signal. the output can be of linear or logarithmic scale. *) (* @END_DECLARATION := '0' *) IF NOT init THEN init := TRUE; IF log_scale THEN temp := EXP(LN(Trigger_high / Trigger_low) * 0.166666666666666666666); T1 := trigger_low * temp; T2 := T1 * temp; T3 := T2 * temp; T4 := T3 * temp; T5 := T4 * temp; ELSE temp := (trigger_high - trigger_low) * 0.142857142; T1 := trigger_low + temp; T2 := T1 + temp; T3 := T2 + temp; T4 := T3 + temp; T5 := T4 + temp; END_IF; END_IF; (* clear outputs before checking *) Q1 := FALSE; Q2 := FALSE; Q3 := FALSE; Q4 := FALSE; Q5 := FALSE; Q6 := FALSE; status := 110; (* low, high and alarm can only be cleared with rst depending on alarm_low and alarm_high *) IF NOT alarm_low THEN low := FALSE; END_IF; IF NOT alarm_high THEN high := FALSE; END_IF; IF rst THEN alarm := FALSE; low := FALSE; high := FALSE; END_IF; (* check and set outputs *) IF X < trigger_low THEN Low := TRUE; status := 111; IF alarm_low THEN alarm := TRUE; status := 1; END_IF; ELSIF X < T1 THEN Q1 := TRUE; ELSIF x < t2 THEN Q2 := TRUE; ELSIF x < t3 THEN Q3 := TRUE; ELSIF x < T4 THEN Q4 := TRUE; ELSIF x < T5 THEN q5 := TRUE; ELSIF x < trigger_high THEN q6 := TRUE; ELSE high := TRUE; status := 112; IF alarm_high THEN alarm := TRUE; status := 2; END_IF; END_IF; (* revision history hm 22. feb 2007 rev 1.0 original version hm 2. dec 2007 rev 1.1 chaged code for better performance hm 6. jan 2008 rev 1.2 further performance improvement *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CALIBRATE VAR_INPUT X : REAL; CO : BOOL; CS : BOOL; END_VAR VAR_INPUT CONSTANT Y_Offset : REAL; Y_Scale : REAL; END_VAR VAR_OUTPUT Y : REAL; END_VAR VAR RETAIN offset : REAL; Scale : REAL := 1.0; END_VAR (* version 1.3 11. mar. 2009 programmer hugo tested BY hans Calibrate allows for offset and scale calibration of an analog input. offset is calibrated to a stored reference Y_offset while CO is true. after the offset is calibrated, scale can be calibrated to a reference value Y_scale while CS is true. *) (* @END_DECLARATION := '0' *) (* check for calibration *) IF CO THEN OFFSET := Y_Offset - X; ELSIF CS THEN SCALE := Y_scale / (X + OFFSET); END_IF; (* calculate output *) Y := (X + OFFSET) * SCALE; (* revision history hm 22.2.2007 rev 1.2 changed VAR RETAIN PERSISTENT to VAR RETAIN for better compatibility wago lon contollers do not support persisitent hm 11. mar. 2009 rev 1.3 changed real constants to use dot syntax *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CYCLE_TIME VAR_INPUT RST : BOOL; END_VAR VAR_OUTPUT ct_min : TIME; ct_max : TIME; ct_last : TIME; systime : TIME; sysdays : INT; cycles : DWORD; END_VAR VAR last_cycle : TIME; tx: TIME; init: BOOL; END_VAR (* version 1.2 16 sep 2007 programmer hugo tested BY hans this function block measures the cycle time and displays the last, min and max cycle time of the current task. the resolution is 1ms. the cycles output is a dword counter which counts the cycles. a rst pulse on the input will reset all data. *) (* @END_DECLARATION := '0' *) tx := DWORD_TO_TIME(T_PLC_MS()) - last_cycle; IF rst THEN ct_min := t#10h; ct_max := t#0ms; cycles := 0; ELSIF last_cycle > t#0s THEN IF tx < ct_min THEN ct_min := tx; ELSIF tx > ct_max THEN ct_max := tx; END_IF; ct_last := tx; ELSIF ct_min = t#0s THEN ct_min := t#0s - t#1ms; END_IF; IF init THEN systime := systime + tx; IF systime >= t#1d THEN systime := systime - t#1d; sysdays := sysdays + 1; END_IF; END_IF; init := TRUE; last_cycle := last_cycle + tx; cycles := cycles + 1; (* revision history hm 12.12.2006 rev 1.1 added cycles output, a dword cycle counter. hm 10.3.2007 rev 1.2 changed initialization of ct_min to t#10h for compatibility with siemens s7 hm 16.9.2007 rev 1.2 changed Time() in T_PLC_MS() for compatibility resons *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK DT_SIMU VAR_INPUT START : DT; SPEED : REAL := 1.0; END_VAR VAR_OUTPUT DTS : DT; END_VAR VAR tc : DWORD; init: BOOL; last: DWORD; tx: DWORD; td: DWORD; END_VAR (* version 1.2 8. mar. 2009 programmer hugo tested by oscat DT_SIMU simulates a real time clock and can be adjusted to different speeds it can also be used in simulation to simulate a real time clock. the peed of the clock can be increased or decreased to debug timers. with the input start a start date-time can be specified. *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := T_PLC_MS(); tc := REAL_TO_DWORD(DWORD_TO_REAL(tx - last) * speed); IF NOT init THEN init := TRUE; DTS := Start; tc := 0; last := tx; ELSIF SPEED = 0.0 THEN DTS := DWORD_TO_DT(DT_TO_DWORD(DTS) + 1); ELSIF tc >= 1000 THEN td := (tc / 1000) * 1000; DTS := DTS + DWORD_TO_TIME(td); last := last + REAL_TO_DWORD(DWORD_TO_REAL(td) / speed); END_IF; (* revision history hm 11. sep. 2008 rev 1.0 original version hm 16. nov 2008 rev 1.1 added type conversions for compatibility reasons hm 8. mar. 2009 rev 1.2 added increment by cycle mode *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FLOW_METER VAR_INPUT VX : REAL; E : BOOL; RST : BOOL; END_VAR VAR_INPUT CONSTANT PULSE_MODE : BOOL; UPDATE_TIME : TIME := t#1s; END_VAR VAR_OUTPUT F : REAL; END_VAR VAR_IN_OUT X : REAL; Y : UDINT; END_VAR VAR tx, tl : TIME; int1 : INTEGRATE; init: BOOL; e_last : BOOL; tmp: INT; x_last : REAL; y_last : UDINT; END_VAR (* version 1.0 23. jan. 2011 programmer hugo tested by oscat Flow meter measures flow according to gated time or pulses. *) (* @END_DECLARATION := '0' *) IF NOT init THEN (* init on power up *) init := TRUE; tl := tx; x_last := X; y_last := Y; int1.K := 2.7777777777777777E-4; END_IF; (* run integrator *) int1(E := NOT (RST OR PULSE_MODE) AND E, X := VX, Y := X); (* gated operation *) IF RST THEN (* reset *) X := 0.0; Y := 0; tl := tx; x_last := 0.0; y_last := 0; ELSIF E AND PULSE_MODE THEN (* check for pulse mode *) IF NOT e_last THEN X := X + VX; END_IF; END_IF; e_last := E; (* reduce X to be less than 1 and increase Y respectively *) IF X > 1.0 THEN tmp := FLOOR(X); Y := Y + INT_TO_UDINT(tmp); X := X - INT_TO_REAL(tmp); END_IF; (* calculate the current flow *) tx := DWORD_TO_TIME(T_PLC_MS()); IF tx - tl >= UPDATE_TIME AND UPDATE_TIME > t#0s THEN F := (UDINT_TO_REAL(Y - y_last) + X - x_last) / TIME_TO_REAL(tx - tl) * 3.6E6; y_last := Y; x_last := X; tl := tx; END_IF; (* revision history hm 23. jan. 2011 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK M_D VAR_INPUT start : BOOL; stop : BOOL; tmax : TIME := t#10d; rst: BOOL; END_VAR VAR_OUTPUT PT : TIME; ET : TIME; run : BOOL; END_VAR VAR edge : BOOL; T0 : TIME; tx: TIME; startup: BOOL; END_VAR (* version 1.2 27. feb. 2009 programmer hugo tested BY oscat m_d measures the time between a rising edge on start to a rising edge on stop and returs the last measured value on the output PT. a second output ET is starting from 0 at the rising edge of start and is counting up until the rising edge of stop occurs. the asynchronous input rst can reset the outputs at any time. tmax defines a timeout wich is the maximum measurable time between start and stop, if this time is exceeded the outputs will stay at 0. *) (* @END_DECLARATION := '0' *) (* check for rst input *) IF rst OR et >= tmax THEN pt := t#0ms; et := t#0ms; startup := FALSE; run := FALSE; END_IF; (* avoid timers to start when input is true at startup *) IF NOT startup THEN edge := start; startup := TRUE; END_IF; (* read system timer *) tx := DWORD_TO_TIME(T_PLC_MS()); (* detect rising edge on start *) IF start AND NOT edge AND NOT stop THEN t0 := tx; run := TRUE; pt := t#0s; ELSIF stop AND run THEN pt := et; run := FALSE; END_IF; edge := start; IF run THEN et := tx - t0; END_IF; (* revision history hm 2.5.2007 rev 1.0 original version hm 16.9.2007 rev 1.1 changes time() to T_plc_ms() for compatibility reasons hm 27. feb 2009 rev 1.2 deleted unnecessary init with 0 *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK M_T VAR_INPUT IN : BOOL; TMAX : TIME := t#10d; RST : BOOL; END_VAR VAR_OUTPUT PT : TIME; ET: TIME; END_VAR VAR edge : BOOL; start : TIME; tx: TIME; END_VAR (* version 1.4 10. mar. 2009 programmer hugo tested BY oscat m_t measures the with of a high pulse and returs the last measured pulse width on output PT. a second output ET is starting from 0 at the rising edge and counting up until the falling edge occurs and resetts et to 0. the asynchrtonous input rst can reset the outputs at any time. tmax defines a maximum measurable time, if this value is exceeded the outputs will be reset to 0. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := DWORD_TO_TIME(T_PLC_MS()); IF RST OR ET >= TMAX THEN PT := t#0s; ET := t#0s; ELSIF IN THEN IF NOT edge THEN start := tx; END_IF; ET := tx - start; ELSE PT := ET; END_IF; edge := IN; (* revision history hm 4. aug. 2006 rev 1.0 original version hm 2. may. 2007 rev 1.1 added init variable to avoid unreasonable measurement if falling edge occurs first. added et (elapsed time) output to count from 0 at rising edge until a falling edge resets et to 0. added reset input. hm 16. sep. 2007 rev 1.2 changes time() to T_plc_ms() for compatibility reasons hm 17. dec. 2008 rev 1.3 code optimized hm 10. mar. 2009 rev 1.4 removed nested comments removed double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK M_TX VAR_INPUT in: BOOL; tmax : TIME := t#10d; rst : BOOL; END_VAR VAR_OUTPUT TH : TIME; TL : TIME; DC : REAL; F: REAL; ET : TIME; END_VAR VAR edge : BOOL; start: TIME; stop:TIME; tx: TIME; rise : BOOL; fall : BOOL; startup: BOOL; END_VAR (* version 1.4 11. mar. 2009 programmer oscat tested BY oscat this measures the timing of a signal TH is the high pulse width TL is the low pulse width DC is the duty cycle F is the frequency ET will start at 0 with a rising edge and count up till the next rising edge starts from 0 again. the acuracy depends on the cycle time of the sps and is limited to 1ms resolution. an asynchronous reset can reset all outputs to 0 at any time. the input tmax can configure a timeout for ET where the function block will outomatically reset itself. tmx is predefined to 10 days. *) (* @END_DECLARATION := '0' *) (* check FOR rst input *) IF rst OR (et >= tmax) THEN rise := FALSE; fall := FALSE; startup := FALSE; th := t#0ms; tl := t#0ms; DC := 0.0; F := 0.0; ET := t#0s; END_IF; (* avoid timers to start when input is true at startup *) IF NOT startup THEN edge := in; startup := TRUE; END_IF; (* read system timer *) tx := DWORD_TO_TIME(T_PLC_MS()); (* edge trigger rising and falling edge *) IF in XOR edge THEN edge := in; (* rising edge starts measurement *) IF in THEN start := Tx; rise := TRUE; IF fall THEN tl := start - stop; END_IF; IF th > t#0ms AND tl > t#0ms THEN dc := TIME_TO_REAL(th) / TIME_TO_REAL(th+tl); f := 1000.0 / TIME_TO_REAL(th + tl); END_IF; (* falling edge starts second cycle measurement *) ELSE stop := Tx; fall := TRUE; IF rise THEN th := stop - start; END_IF; IF th > t#0ms AND tl > t#0ms THEN dc := TIME_TO_REAL(th) / TIME_TO_REAL(th+tl); f := 1000.0 / TIME_TO_REAL(th + tl); END_IF; END_IF; END_IF; IF rise THEN et := tx - start; END_IF; (* revision history hm 4. aug. 2006 rev 1.0 original version hm 2. mai. 2007 rev 1.1 made sure that no undefined values would be measured at startup conditions added output et added rst input added tmax input hm 16. sep. 2007 rev 1.2 changes time() to T_plc_ms() for compatibility reasons hm 27. feb. 2009 rev 1.3 deleted unnecessary init with 0 hm 11. mar. 2009 rev 1.4 changed real constants to use dot syntax *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK METER VAR_INPUT M1, M2 : REAL; I1, I2 : BOOL; D : REAL := 1.0; RST : BOOL; END_VAR VAR_IN_OUT MX : REAL; END_VAR VAR MR : REAL2; MX1, MX2 : REAL; (* current consumption value on M1 and M2 *) tx: DWORD; last: DWORD; tc: REAL; init: BOOL; END_VAR (* version 1.4 8. mar. 2009 programmer oscat tested BY oscat METER measures usage of power or similar values the output MX is the sum of the inputs over time. MX := sum over time of (I1 * P1 + I2 * P2)/D. a simple example is the consumption counter for a 2 stage oil burner where stage 1 is 10KW and stage 2 is 20KW. I1 and I2 decide which value is accounted for, I1 = True only counts P1, I2 is True counts P2 and I1 and I2 is true counts P1 and P2. the meter can be used for all kind of consumption meters. P1 and P2 can change on the fly. *) (* @END_DECLARATION := '0' *) (* measure the last cycle time and make sure we onle execute once every millisecond *) tx := T_PLC_MS(); IF NOT init THEN init := TRUE; last := tx; mr.RX := mx; mr.R1 := 0.0; ELSIF tx = last THEN RETURN; ELSE tc := DWORD_TO_REAL(tx - last) * 0.001; END_IF; last := tx; (* reset *) IF rst THEN mr.R1 := 0.0; mr.RX := 0.0; ELSE (* current consumption measurement *) IF I1 THEN MX1 := M1; ELSE MX1 := 0.0; END_IF; IF I2 THEN MX2 := M2; ELSE MX2 := 0.0; END_IF; (* add up the current values in a double real *) MR := R2_ADD(MR,(SEL(I1,0.0,mx1) + SEL(I2, 0.0, mx2)) / D * TC); (* set the current output value *) MX := mr.RX; END_IF; (* revision history hm 16. sep.2007 rev 1.0 original version hm 7. feb 2008 rev 1.1 use new version of ft_int to avoid a counting stop at high values deleted unnecessary limits hm 24. mar. 2008 rev 1.2 use data_type real2 to extend accuracy to 15 digits total do not use ft_int which adds unnecessary overhead hm 8. feb. 2009 rev 1.3 changed mx to be I/O make sure calculation works for cycle times < 1 ms hm 8. mar. 2009 rev 1.4 last was not updated code improvements *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK METER_STAT VAR_INPUT IN : REAL; DI : DATE; RST : BOOL; END_VAR VAR_IN_OUT Last_Day : REAL; Current_Day : REAL; Last_Week : REAL; Current_Week : REAL; Last_Month : REAL; Current_Month : REAL; Last_Year : REAL; Current_Year : REAL; END_VAR VAR RETAIN Year_Start : REAL; Month_Start : REAL; Week_Start : REAL; Day_Start : REAL; last_run: DATE; END_VAR (* version 1.3 18. jul. 2009 programmer oscat tested BY oscat METER_STAT runs statistics of a metered value, it calculates the current week, day, month and year consumption and stores the corresponding last day, week, month and year value. *) (* @END_DECLARATION := '0' *) IF rst THEN Last_Day := 0.0; Current_Day := 0.0; Day_start := IN; Last_week := 0.0; Current_week := 0.0; Week_start := in; Last_month := 0.0; Current_month := 0.0; month_start := in; last_year := 0.0; current_year := 0.0; year_start := in; ELSE Current_Day := IN - Day_Start; Current_Week := In - Week_Start; Current_Month := IN - Month_Start; Current_Year := IN - Year_Start; END_IF; IF YEAR_OF_DATE(DI) > YEAR_OF_DATE(last_run) THEN (* a new year has started *) last_year := current_year; current_year := 0.0; year_start := in; last_month := current_month; current_month := 0.0; month_start := in; last_day := current_day; current_day := 0.0; day_start := in; ELSIF MONTH_OF_DATE(DI) > MONTH_OF_DATE(last_run) THEN (* a new month has started, january is alrerady done by year change *) last_month := current_month; current_month := 0.0; month_start := in; last_day := current_day; current_day := 0.0; day_start := in; ELSIF DAY_OF_YEAR(di) > DAY_OF_YEAR(last_run) THEN (* day has chaged, first day of year and first day of month has already been taken care of *) last_day := current_day; current_day := 0.0; day_start := in; END_IF; IF DAY_OF_WEEK(DI) < DAY_OF_WEEK(last_run) THEN (* a new week has started *) last_week := current_week; current_week := 0.0; week_start := in; END_IF; last_run := di; (* revision history hm 16. sep. 2007 rev 1.0 original version hm 7. oct. 2008 rev 1.1 changed function year_to_year_of_date changed function month to month_of_date changed function weekday to day_of_week hm 11. mar. 2009 rev 1.2 changed real constants to use dot syntax hm 18. jul. 2009 rev 1.3 changes all outputs to be I/O *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK ONTIME VAR_INPUT IN : BOOL; RST : BOOL; END_VAR VAR_IN_OUT SECONDS : UDINT; CYCLES : UDINT; END_VAR VAR tx: DWORD; last : DWORD; edge : BOOL; init: BOOL; ms: DWORD; END_VAR (* version 2.5 18. mar. 2011 programmer oscat tested by tobias ONTIME measures the ontime of a signal in seconds. the output SECONDS is of type DWORD which results in a total measurement range of 1 second up to 136 years. internally ontime works with a resolution of milliseconds *) (* @END_DECLARATION := '0' *) (* read system time *) tx := T_PLC_MS(); (* make sure the first cycle works correctly *) IF NOT init THEN init := TRUE; last := tx; ms := 0; END_IF; IF RST THEN SECONDS := 0; CYCLES := 0; last := tx; ms := 0; ELSIF IN THEN (* add the current milliseconds *) ms := (tx - last) + ms; IF ms >= 1000 THEN seconds := seconds + 1; ms := ms - 1000; END_IF; cycles := cycles + BOOL_TO_UINT(NOT edge); END_IF; last := tx; edge := in; (* revision history hm 22.2.2007 rev 1.1 changed VAR RETAIN PERSISTENT to VAR RETAIN for better compatibility wago lon contollers do not support persisitent hm 2.8.2007 rev 1.2 adding time up in a real number will automatically lead to the point where small time scales like the cycle time will be below the resolution of real and therefore ontime would not increase in small steps as necessary the time is now measured internally in two dwords and be converted to real only for output purposes. deleted the variable power because it was unnecessary hm 16.9.2007 rev 1.3 changes time() to T_plc_ms() for compatibility reasons hm 2. dec. 2007 rev 1.4 chaged code for better performance hm 16. mar. 2008 rev 1.5 added type conversions to avoid warnings under codesys 3.0 hm 21. oct. 2008 rev 2.0 changed module for much better performance and allow for external result storage hm 10. nov. 2008 rev 2.1 increased internal resolution to milliseconds hm 16. nov. 2008 rev 2.2 changed typecast to avoid warnings hm 17. dec. 2008 rev 2.3 fixed an error when in would be true for more then 49 days hm 17. jan 2011 rev 2.4 init will not clear seconds and cycles, only rst clears these values hm 18. mar. 2011 rev 2.5 reset was not working *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION T_PLC_MS : DWORD VAR CONSTANT debug : BOOL := 0; N : INT := 0; offset : DWORD := 0; END_VAR VAR tx : TIME; END_VAR (* version 1.2 16. nov 2008 programmer hugo tested by oscat T_PLC_MS reads the internal PLC timer and return the time, it has the advantage to be able to set a debug mode and speed up the counter to test the plc timer overrun which occurs every 50 days respectively 25 days at siemens S7 this routine also allows to correct the behavior of s7 where the internal plc counter will not count all 32 bits. *) (* @END_DECLARATION := '0' *) tx := TIME(); T_PLC_MS := TIME_TO_DWORD(Tx); (* hier muss die korrektur für step7 stattfinden plctime muss den vollen wertebereich von time ausnutzen: wenn bei step7 time -24tage bis plus 24 tage ist dann muss der timer auch beim überlauf auf -24tage springen und auf keinen fall auf 0 !!!! für siemens muss ein weiterer fb im main eingebunden werden der sicherstellt das alle 32 bits durchgezählt werden. es kann nur ein fb sein den er muss sich das oberste (32te) bit merken. oder etwa spring s7 bei überlauf auf -24 tage????? dann wäre keine korrektur nötig. *) IF debug THEN T_PLC_MS := (SHL(T_PLC_MS,N) OR SHL(DWORD#1,N)-1) + OFFSET; END_IF; (* revision history hm 14.9.2007 rev 1.0 original version hm 12. nov 2007 rev 1.1 added temporaray variable tx because some compilers could not handle time() as an argument hm 16. nov. 2008 rev 1.2 initialized constants with 0 for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION T_PLC_US : DWORD VAR CONSTANT debug : BOOL := 0; N : INT := 0; offset : DWORD := 0; END_VAR VAR tx : TIME; END_VAR (* version 1.2 16 nov 2008 programmer hugo tested by tobias T_PLC_US reads the internal PLC timer and return the time, it has the advantage to be able to set a debug mode and speed up the counter to test the plc timer overrun which occurs every 50 days respectively 25 days at siemens S7 this routine also allows to correct the behavior of s7 where the internal plc counter will not count all 32 bits. *) (* @END_DECLARATION := '0' *) tx := TIME(); T_PLC_US := TIME_TO_DWORD(Tx)*1000; (* hier muss die korrektur für step7 stattfinden plctime muss den vollen wertebereich von time ausnutzen: wenn bei step7 time -24tage bis plus 24 tage ist dann muss der timer auch beim überlauf auf -24tage springen und auf keinen fall auf 0 !!!! für siemens muss ein weiterer fb im main eingebunden werden der sicherstellt das alle 32 bits durchgezählt werden. es kann nur ein fb sein den er muss sich das oberste (32te) bit merken. oder etwa spring s7 bei überlauf auf -24 tage????? dann wäre keine korrektur nötig. *) IF debug THEN T_PLC_US := (SHL(T_PLC_US,N) OR SHL(DWORD#1,N)-1) + OFFSET; END_IF; (* revision history hm 14.9.2007 rev 1.0 original version hm 12. nov 2007 rev 1.1 added temporaray variable tx because some compilers could not handle time() as an argument hm 16. nov. 2008 rev 1.2 initialized constants with 0 for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK TC_MS VAR_OUTPUT TC : DWORD; END_VAR VAR init: BOOL; tx: DWORD; last: DWORD; END_VAR (* version 1.0 13 mar 2008 programmer hugo tested by tobias TC_MS delivers the time it was last called on the output TC in Milliseconds. *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := T_PLC_MS(); IF NOT init THEN init := TRUE; TC := 0; ELSE tc := tx - last; END_IF; last := tx; (* revision history hm 13. mar. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK TC_S VAR_OUTPUT TC : REAL; END_VAR VAR init: BOOL; tx: DWORD; last: DWORD; END_VAR (* version 1.2 11. mar. 2009 programmer hugo tested by tobias TC_S delivers the time it was last called on the output TC in seconds. *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := T_PLC_US(); IF NOT init THEN init := TRUE; TC := 0.0; ELSE tc := DWORD_TO_REAL(tx - last)*1.0E-6; END_IF; last := tx; (* revision history hm 13. mar. 2008 rev 1.0 original version hm 16. mar 2008 rev 1.1 added type conversion to avoid warnings under codesys 3.0 hm 11. mar. 2009 rev 1.2 changed real constants to use dot syntax *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/measurements' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK TC_US VAR_OUTPUT TC : DWORD; END_VAR VAR init: BOOL; tx: DWORD; last: DWORD; END_VAR (* version 1.0 13 mar 2008 programmer hugo tested by tobias TC_US delivers the time it was last called on the output TC in Microseconds. *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := T_PLC_US(); IF NOT init THEN init := TRUE; TC := 0; ELSE tc := tx - last; END_IF; last := tx; (* revision history hm 13. mar. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/sensor' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MULTI_IN : REAL VAR_INPUT in_1 : REAL; in_2 : REAL; in_3 : REAL; default : REAL; in_min : REAL; in_max : REAL; mode : BYTE; END_VAR VAR count: INT; F1: BOOL; F2: BOOL; F3: BOOL; END_VAR (* version 1.4 18. jul. 2009 programmer oscat tested by oscat multi_in is a signal conditioning function which can be configured in 8 different ways. it is used to read multiple sensors for the same value and protect the user from broken sensors or invalid data from sensors. multi_in can be configured in different ways: mode = 0 means the the avg of the 3 inputs is used, this mode is the default mode. mode = 1 means in_1 is used. mode = 2 means in_2 is used. mode = 3 means in_3 is used. mode = 4 means default is used. mode = 5 means the lowest of the 3 external temperatures is used. mode = 6 means the higest externnal temperature is used. mode = 7 means the midlle input is used, if there are only two working, the lowest input is used. mode > 7 ,eans output is 0 at all times. in any config mode, an input higher then in_max or lower then in_min is ignored to prevent values from broken sensors or wires. if all inputs are higher then in_max or lower then in_min, a default value (default) is used. *) (* @END_DECLARATION := '0' *) F1 := in_1 > in_min AND in_1 < in_max; F2 := in_2 > in_min AND in_2 < in_max; F3 := in_3 > in_min AND in_3 < in_max; CASE mode OF 0: count := 0; IF F1 THEN count := count + 1; MULTI_IN := in_1; ELSE MULTI_IN := 0.0; END_IF; IF F2 THEN count := count + 1; MULTI_IN := MULTI_IN + in_2; END_IF; IF F3 THEN count := count + 1; MULTI_IN := MULTI_IN + in_3; END_IF; MULTI_IN := SEL(count = 0, MULTI_IN / INT_TO_REAL(count), default); 1: MULTI_IN := SEL(F1, default, IN_1); 2: MULTI_IN := SEL(F2, default, IN_2); 3: MULTI_IN := SEL(F3, default, IN_3); 4: MULTI_IN := default; 5: MULTI_IN := SEL(F1, in_max, IN_1); IF F2 AND in_2 < MULTI_IN THEN MULTI_IN := in_2; END_IF; IF F3 AND in_3 < MULTI_IN THEN MULTI_IN := in_3; END_IF; IF MULTI_IN = in_max THEN MULTI_IN := default; END_IF; 6: MULTI_IN := SEL(F1, in_min, IN_1); IF F2 AND in_2 > MULTI_IN THEN MULTI_IN := in_2; END_IF; IF F3 AND in_3 > MULTI_IN THEN MULTI_IN := in_3; END_IF; IF MULTI_IN = in_min THEN MULTI_IN := default; END_IF; 7: IF F1 AND F2 AND F3 THEN MULTI_IN := MID3(in_1, in_2, in_3); ELSIF F1 AND F2 THEN MULTI_IN := MIN(in_1, in_2); ELSIF F1 AND F3 THEN MULTI_IN := MIN(in_1, in_3); ELSIF F2 AND F3 THEN MULTI_IN := MIN(in_2, in_3); ELSIF F1 THEN MULTI_IN := in_1; ELSIF F2 THEN MULTI_IN := in_2; ELSIF F3 THEN MULTI_IN := in_3; ELSE MULTI_IN := default; END_IF; END_CASE; (* hm 1.1.2007 rev 1.1 changed midr to mid3 function hm 14. 10. 2008 rev 1.2 corrected an error for in_3 overrange detection improved performance hm 11. mar. 2009 rev 1.3 changed real constants to use dot syntax hm 18. jul. 2009 rev 1.4 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/sensor' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION RES_NI : REAL VAR_INPUT T : REAL; R0 : REAL; END_VAR VAR CONSTANT A : REAL := 0.5485; B : REAL := 0.665E-3; C : REAL := 2.805E-9; END_VAR VAR T2: REAL; END_VAR (* version 1.2 2 dec 2007 programmer hugo tested by tobias this function returs the resistance for a nickel sensor for a temperature range from -60..+180 °C *) (* @END_DECLARATION := '0' *) T2 := T * T; RES_NI := R0 + A * T + B * T2 + C * T2 * T2; (* revision history hm 4.8.2006 rev 1.0 original version hm 13.9.2007 rev 1.1 changed coding for better performance hm 2. dec. 2007 rev 1.2 changed code for better performance *) (* Auszug aus DIN 43760 für Ni100 °C R °C R °C R °C R °C R -60 69,5 -10 94,6 40 123,0 90 154,9 140 190,9 -55 71,9 -5 97,3 45 126,0 95 158,3 145 194,8 -50 74,3 0 100,0 50 129,1 100 161,8 150 198,7 -45 76,7 5 102,8 55 132,2 105 165,3 155 202,6 -40 79,1 10 105,6 60 135,3 110 168,8 160 206,6 -35 81,6 15 108,4 65 138,5 115 172,4 165 210,7 -30 84,2 20 111,2 70 141,7 120 176,0 170 214,8 -25 86,7 25 114,1 75 145,0 125 179,6 175 219,0 -20 89,3 30 117,1 80 148,3 130 183,3 180 223,2 -15 91,9 35 120,0 85 151,6 135 187,1 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/sensor' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION RES_NTC : REAL VAR_INPUT T : REAL; RN : REAL; B : REAL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by tobias this function returs the resistance for a NTC sensor for a given temperature in °C. RN is the resistance at 25 °C and B is a constant for the given sensor. *) (* @END_DECLARATION := '0' *) RES_NTC := RN * EXP(B * (1.0 / (T+273.15) - 0.00335401643468053)); (* revision history hm 30. dec. 2008 rev 1.0 original version hm 11. mar. 2009 rev 1.1 changed real constants to use dot syntax *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/sensor' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION RES_PT : REAL VAR_INPUT T : REAL; R0 : REAL; END_VAR VAR CONSTANT A : REAL := 3.90802E-3 ; B : REAL := -5.802E-7; C : REAL := -4.27350E-12; END_VAR VAR T2: REAL; END_VAR (* version 1.3 11. mar. 2009 programmer hugo tested by oscat this function returs the resistance for a platinum sensor for a temperature range from -200..+850 °C *) (* @END_DECLARATION := '0' *) T2 := T * T; IF T >= 0.0 THEN RES_PT := R0 * ( 1.0 + A * T + B * T2); ELSE RES_PT := R0 * ( 1.0 + A * T + B * T2 + C * (T - 100.0) * T2 * T); END_IF; (* revision history hm 4. aug. 2006 rev 1.1 original version hm 2. dec 2007 rev 1.2 changed code for better performance hm 11. mar. 2009 rev 1.3 changed real constants to use dot syntax *) (* Auszug aus DIN 43760 für Pt100 °C R °C R °C R °C R °C R °C R -200 18,49 -100 60,25 0 100,00 100 138,50 200 175,84 300 212,02 -195 20,65 -95 62,28 5 101,95 105 140,39 205 177,68 305 213,80 -190 22,80 -90 64,30 10 103,90 110 142,29 210 179,51 310 215,57 -185 24,94 -85 66,31 15 105,85 115 144,17 215 181,34 315 217,35 -180 27,08 -80 68,33 20 107,79 120 146,06 220 183,17 320 219,12 -175 29,20 -75 70,33 25 109,73 125 147,94 225 184,99 325 220,88 -170 31,32 -70 72,33 30 111,67 130 149,82 230 186,82 330 222,65 -165 33,43 -65 74,33 35 113,61 135 151,70 235 188,63 335 224,41 -160 35,33 -60 76,33 40 115,54 140 153,58 240 190,45 340 226,17 -155 37,63 -55 78,32 45 117,47 145 155,45 245 192,26 345 227,92 -150 39,71 -50 80,31 50 119,40 150 157,31 250 194,07 350 229,67 -145 41,79 -45 82,29 55 121,32 155 159,18 255 195,88 355 231,42 -140 43,87 -40 84,27 60 123,24 160 161,04 260 197,69 360 233,17 -135 45,94 -35 86,25 65 125,16 165 162,90 265 199,49 365 234,91 -130 48,00 -30 88,22 70 127,07 170 164,76 270 201,29 370 236,65 -125 50,06 -25 90,19 75 128,98 175 166,61 275 203,08 375 238,39 -120 52,11 -20 92,16 80 130,89 180 168,46 280 204,88 380 240,13 -115 54,15 -15 94,12 85 132,80 185 170,31 285 206,67 385 241,86 -110 56,19 -10 96,09 90 134,70 190 172,16 290 208,45 390 243,59 -105 58,22 -5 98,04 95 136,60 195 174,00 295 210,24 395 245,31 *) (* revision history *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/sensor' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION RES_SI : REAL VAR_INPUT T : REAL; RS : REAL; TS : REAL; END_VAR VAR CONSTANT A : REAL := 7.64E-3; B : REAL := 1.66E-5; END_VAR VAR TX: REAL; END_VAR (* version 1.2 11. mar. 2009 programmer hugo tested by tobias this function returs the resistance for a silicon sensor for a temperature range from -50..+150 °C for example KTY10 normaly Rs at 25 °C *) (* @END_DECLARATION := '0' *) TX := T - TS; RES_SI := RS * ( 1.0 + A * TX + B * TX * TX); (* revision history hm 4. aug 2006 rev 1.0 original version hm 2. dec 2007 rev 1.1 changed code for better performance hm 11. mar. 2009 rev 1.2 changed real constants to use dot syntax *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/sensor' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SENSOR_INT : REAL VAR_INPUT Voltage : REAL; Current : REAL; RP : REAL; RS : REAL; END_VAR VAR RG: REAL; END_VAR (* version 1.3 11. mar. 2009 programmer hugo tested by tobias this function calculates the real resistance of a sensor RX given a parasitic resistor in parallel to the sensor and an additional serial resistor. for example a parasitic parallel resistor for a PT100 can be ignored but a series resistor is the cable to the sensor. for a humidity sensor, the parallel resistor is more dramatic and needs to be considered. the function allows to consider a parallel and serial resistor at the same time. with this function and known parasitic serial and parallel resistors the true resistance of the sensor can be calculated. *) (* @END_DECLARATION := '0' *) IF current <> 0.0 THEN RG := voltage / current; SENSOR_INT := RP * ( RG - RS) / ( RP + RS - RG); END_IF; (* revisaion history hm 20.1.2007 rev 1.1 deleted input R0 which was not needed. hm 2. dec 2007 rev 1.2 corrected an error in formula and improoved speed hm 11. mar. 2009 rev 1.3 changed real constants to use dot syntax *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/sensor' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TEMP_NI : REAL VAR_INPUT Res : REAL; R0 : REAL; END_VAR (* version 1.5 10. mar. 2009 programmer hugo tested by tobias this function returns the temperature for a nickel sensor in a range from -60..+180 °C *) (* @END_DECLARATION := '0' *) TEMP_NI := (SQRT(0.30085225 - 2.66E-3 * (R0 - Res)) - 0.5485) * 751.8796992; (* revision history hm 24.1.2007 rev 1.1 deleted unused variable A, B, C hm 10.9.2007 rev 1.2 changed accuracy to 0.02 degrees to improove performace hm 17. 12 2007 rev 1.3 improovements for better performance and higher accuracy hm 6. jan 2008 rev 1.4 further performance improvement hm 10. mar. 2009 rev 1.5 removed nested comments *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/sensor' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TEMP_NTC : REAL VAR_INPUT RES : REAL; RN : REAL; B : REAL; END_VAR (* version 1.1 11. mar. 2009 programmer hugo tested by tobias this function returs the temperature for a NTC sensor for a range from 0..85 °C. RN is the resistance at 25 °C and B is a constant for the given sensor. *) (* @END_DECLARATION := '0' *) IF res > 0.0 THEN TEMP_NTC := B * 298.15 / (B + LN(RES / RN) * 298.15) -273.15; END_IF; (* revision history hm 30. dec. 2008 rev 1.0 original version hm 11. mar. 2009 rev 1.1 changed real constants to use dot syntax *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/sensor' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TEMP_PT : REAL VAR_INPUT Res : REAL; R0 : REAL; END_VAR VAR CONSTANT A : REAL := 3.9083E-3 ; B : REAL := -5.775E-7; accuracy : REAL := 0.01; END_VAR VAR step: REAL := 50.0; X: REAL; Y: REAL; t1: REAL; pt : POINTER TO DWORD; END_VAR (* version 1.7 11. mar. 2009 programmer hugo tested by oscat this function returs the temperature for a platinum sensor for a range from -200..+850 °C *) (* @END_DECLARATION := '0' *) X := A * R0; Y := B * R0; IF Res >= R0 THEN t1 := X * X - 4.0 * Y * (R0 - Res); IF t1 < 0.0 THEN TEMP_PT := 10000.0; ELSE TEMP_PT := (-X + SQRT(t1)) / (2.0 * Y); END_IF; ELSE pt := ADR(step); (* since the formula cannot be solved this is a successive approximation *) TEMP_PT := -100.0; WHILE step > accuracy DO (* test if result greater or less *) IF RES_PT(TEMP_PT, R0) < res THEN TEMP_PT := TEMP_PT + step; ELSE TEMP_PT := TEMP_PT - step; END_IF; pt^ := pt^ - 8388608; (* this is a super fast divide by 2 method for non floating point CPUs *) (* the alternative code: step := step * 0.5; *) END_WHILE; END_IF; (* revision history rev 1.1 hm 24.1.2007 deleted unused variable C rev 1.2 hm 10.9.2007 reduced accuracy to 0.02 to shorten execution time rev 1.3 hm 2. dec 2007 changed code for better performance rev 1.4 hm 23. dec 2007 avoid a negative square root if input values are wrong rev 1.5 hm 5. jan 2008 replaced / 2 with * 0.5 for better performance hm 31. oct. 2008 rev 1.6 improved performance hm 11. mar. 2009 rev 1.7 changed real constants to use dot syntax *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/sensor' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TEMP_SI : REAL VAR_INPUT Res : REAL; RS : REAL; TS : REAL; END_VAR (* version 1.5 10. mar. 2009 programmer hugo tested by tobias this function returs the temperature for a silicon sensor for a range from -60..+180 °C for example KTY10 RS at TS normally 25 °C *) (* @END_DECLARATION := '0' *) TEMP_SI := (-7.64E-3 + SQRT(Res / RS * 6.64E-5 - 0.803E-5)) * 30120.48193 + TS; (* revision history hm 24.1.2007 rev 1.1 the function would only work for TS = 25, now it works for other values of ts. hm 10.9.2007 rev 1.2 changed the function to use successive approcimation to avoid an error generated by a sqrt from a negative result. changed accuracy to 0.02 degrees to shorten execution time hm 17. 12 2007 rev 1.3 improovements for better performance hm 6. jan 2008 rev 1.4 further performance improvement hm 10. mar. 2009 rev 1.5 removed nested comments *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK _RMP_B VAR_INPUT DIR : BOOL; (* true = up *) E : BOOL := TRUE; TR : TIME; END_VAR VAR_IN_OUT RMP : BYTE; END_VAR VAR tx, tl, tn : TIME; init : BOOL; last_dir : BOOL; start : BYTE; END_VAR (* version 1.2 19. feb. 2011 programmer hugo tested by oscat _RMP_B generates a ramp on an external var of type byte tr is the total ramp time, E is an enable and dir the direction of the ramp *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := DWORD_TO_TIME(T_PLC_MS()); IF E AND init AND (dir = last_dir) AND (RMP <> SEL(DIR, 0, 255)) AND TR = tn THEN RMP := FRMP_B(start, DIR, tx - tl, TR); ELSE init := TRUE; tl := tx; tn := tr; start := RMP; END_IF; last_dir := dir; (* revison history hm 22. oct. 2008 rev 1.0 original version hm 20. nov. 2008 rev 1.1 set default for E to be TRUE added init hm 19. nov. 2011 rev 1.2 new code *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK _RMP_NEXT VAR_INPUT E : BOOL := TRUE; IN : BYTE; TR : TIME; TF : TIME; TL : TIME; END_VAR VAR_OUTPUT DIR : BOOL; (* upwards = TRUE *) UP : BOOL; DN : BOOL; END_VAR VAR_IN_OUT OUT : BYTE; END_VAR VAR rmx : _RMP_B; dirx : TREND_DW; t_lock :TP; xen: BOOL; xdir: BOOL; END_VAR (* version 1.4 18. jul. 2009 programmer hugo tested by oscat _RMP_NEXT will generate a ramp output following the input IN. on a rising ramp the output will stop as soon as it has surpassed the input in, and on a falling ramp it will stop when out is smaller than in. a lockout time T_L will be added between up and down direction. *) (* @END_DECLARATION := '0' *) dirx(X := in); t_lock(in := FALSE, pt := TL); IF dirx.TU AND (OUT < IN) THEN IF NOT xdir AND xen THEN t_lock(in := TRUE); END_IF; xen := TRUE; xdir := TRUE; ELSIF dirx.TD AND (OUT > IN) THEN IF xdir AND xen THEN t_lock(in := TRUE); END_IF; xen := TRUE; xdir := FALSE; ELSIF xen THEN IF (xdir AND (out >= in)) OR (NOT xdir AND (out <= in)) THEN xen := FALSE; IF tl > t#0s THEN t_lock(IN := TRUE); END_IF; END_IF; END_IF; IF NOT t_lock.Q AND xen THEN UP := XDIR; DIR := XDIR; DN := NOT XDIR; ELSE UP := FALSE; DN := FALSE; END_IF; rmx(rmp := OUT, E := E AND (UP OR DN) , dir := DIR, tr := SEL(dir, TF, TR)); (* revison history hm 23. nov. 2008 rev 1.0 original version hm 24. jan. 2009 rev 1.1 deleted unused vars tmp1, tmp2 hm 20. feb. 2009 rev 1.2 improved algorithm added TL hm 9. mar. 2009 rev 1.3 input E was ignored removed double assignments hm 18. jul. 2009 rev 1.4 improved performance *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK _RMP_W VAR_INPUT DIR : BOOL; E : BOOL := TRUE; TR : TIME; END_VAR VAR_IN_OUT RMP : WORD; END_VAR VAR tx, tl : DWORD; step : DINT; init: BOOL; last_dir : BOOL; END_VAR (* version 1.1 20. nov. 2008 programmer hugo tested by oscat _RMP_B generates a ramp on an external var of type byte tr is the total ramp time, E is an enable and dir the direction of the ramp *) (* @END_DECLARATION := '0' *) tx := T_PLC_MS(); IF E AND init THEN (* we need to set tl = tx when direction changes *) IF dir XOR last_dir THEN tl := tx; last_dir := dir; END_IF; (* check_elapsed time and calculate only if necessary *) IF tr > t#0s THEN step := DWORD_TO_DINT(SHL(tx-tl, 16) / TIME_TO_DWORD(TR)); ELSE step := 65535; END_IF; IF step > 0 THEN (* perform the step on the ramp *) tl := tx; (* calculate the step response *) IF NOT dir THEN step := - step; END_IF; rmp := DINT_TO_WORD(LIMIT(0, WORD_TO_DINT(rmp) + step, 65535)); END_IF; ELSE tl := tx; init := TRUE; END_IF; (* revison history hm 22. oct. 2008 rev 1.0 original version hm 20. nov. 2008 rev 1.1 set default for E to be TRUE added init *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK GEN_PULSE VAR_INPUT ENQ : BOOL := TRUE; PTH : TIME; PTL : TIME; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR tx: TIME; tn: TIME; init: BOOL; END_VAR (* version 1.5 8. apr. 2011 programmer hugo tested by oscat GEN_PULSE uses the internal sps timer to generate a continuous output waveform with programmable high and low time. the accuracy of gen_pulse is depending on the system timer. when time is 0 the high and low times are exactly one cycle. ENQ = TRUE will start and ENQ = FALSE will stop the generator. *) (* @END_DECLARATION := '0' *) IF enq THEN tx := DWORD_TO_TIME(T_PLC_MS()); IF NOT init THEN init := TRUE; tn := tx; END_IF; IF tx - tn >= SEL(Q, PTL, PTH) THEN tn := tn + SEL(Q, PTL, PTH); Q := NOT Q; END_IF; ELSE Q := FALSE; init := FALSE; END_IF; (* revision history hm 29. jun. 2008 rev 1.0 original version hm 23. nov. 2008 rev 1.1 set default for enq to be true hm 18. jul. 2009 rev 1.2 improved performance hm 13. nov. 2009 rev 1.3 corrected error hm 16. feb. 2011 rev 1.4 corrected an error when timer overflows hm 8. apr. 2011 rev 1.5 ptl and pth was exchanged *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK GEN_PW2 VAR_INPUT ENQ : BOOL; TH1 : TIME; TL1 : TIME; TH2 : TIME; TL2 : TIME; TS : BOOL; END_VAR VAR_OUTPUT Q : BOOL; TH : TIME; TL : TIME; END_VAR VAR t_high : TIME; t_low : TIME; tx : TIME; start : TIME; init : BOOL; et : TIME; END_VAR (* version 1.1 14. mar. 2009 programmer hugo tested by oscat GEN_PW2 generates a time TH? followed by a time TL?. the input ts selects between 2 sets of timings for the operation. *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := DWORD_TO_TIME(T_PLC_MS()); (* startup initialization *) IF NOT init THEN start := tx; init := TRUE; TH := T#0s; TL := T#0s; END_IF; (* timing selection *) IF TS THEN t_high := TH2; t_low := TL2; ELSE t_high := TH1; t_low := TL1; END_IF; (* normal operation *) IF ENQ THEN et := tx - start; IF NOT Q THEN IF et >= t_low THEN Q := TRUE; start := tx; TL := T#0s; ELSE TL := et; END_IF; ELSE IF et >= t_high THEN Q := FALSE; start := tx; TH := T#0s; ELSE TH := et; END_IF; END_IF; ELSE Q := FALSE; TH := T#0s; TL := T#0s; start := tx; END_IF; (* revision history hm 26. sep. 2008 rev 1.0 original version hm 14. mar. 2009 rev 1.1 removed double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK GEN_RDM VAR_INPUT PT : TIME; AM : REAL := 1; OS : REAL; END_VAR VAR_OUTPUT Q : BOOL; Out : REAL; END_VAR VAR tx : TIME; last : TIME; init : BOOL; END_VAR (* version 1.1 16 sep 2007 programmer oscat tested BY oscat this signal generator generates a random output. The signal is defined by period time (PT), amplitude (AM), offset (OS). The Output waveform will have its max peak at AM/2 + OS and its minimum peak at -AM/2 + OS. The period time PT defines how often the output signal will jump to a new randow value. The Output Q will be true for one cycle anytime the output OUT has changed *) (* @END_DECLARATION := '0' *) (* read system time and prepare input data *) tx := DWORD_TO_TIME(T_PLC_MS()) - last; (* init section *) IF NOT init THEN init := TRUE; last := tx; tx := t#0s; END_IF; (* add last if one cycle is finished *) IF tx >= pt THEN last := last + pt; tx := tx - pt; (* generate output signal *) out := am * (RDM(0) - 0.5) + os; q := TRUE; ELSE q := FALSE; END_IF; (* revision history hm 7.2.2007 rev 1.0 original version hm 16.9.2007 rev 1.1 changes time() to T_plc_ms() for compatibility reasons *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK GEN_RDT VAR_INPUT Enable : BOOL := TRUE ; Min_Time_ms : TIME := t#1s ; (* min Taktzeit *) Max_Time_ms : TIME := t#1.2s ; (* Max Taktzeit *) TP_Q : TIME := t#100ms ; (* Zeit Ausgang auf TRUE *) END_VAR VAR_OUTPUT xQ : BOOL ; END_VAR VAR (* Taktgenerator Simulation *) tonRDMTimer : TON ; (* Zeitbaustein Taktgenerator *) tof_xQ : TOF ; (* Ausschaltverzögerung Taktgenerator *) tRDMTime : TIME ; (* Sollzeit *) rRDMTime : REAL ; (* Zufalswert Timer *) END_VAR (* version 1.1 16 mar. 2008 programmer J. Schohaus tested by Hugo RDMT generates a defined pulse with pulse width TP_Q at random times. the random time will be defined with an minimum and maximum time. *) (* FUNCTION_BLOCK RDMTimer ############################################################################### Ersteller / author : J.Schohaus Datum / date: 13.07.2007 ############################################################################### Änderungen / Datum / Ersteller : moditication / date / author : ############################################################################### Verwendete Bibliotheken ( * werden im Baustein nicht benötigt ) Oscat.lib ############################################################################### Beschreibung: *) (* @END_DECLARATION := '0' *) tonRDMTimer ( IN:= Enable , PT:= tRDMTime ); tof_xQ ( IN:= tonRDMTimer.Q , PT:= TP_Q ); XQ := tof_xq.Q; IF tonRDMTimer.Q THEN xQ := TRUE ; rRDMTime := RDM ( last:= rRDMTime ) ; tRDMTime := REAL_TO_TIME ( rRDMTime * DINT_TO_REAL(TIME_TO_DINT( Max_Time_ms - Min_Time_ms ) + TIME_TO_DINT(Min_Time_ms ))) ; tonRDMTimer ( IN:= FALSE ); END_IF; (* revision history J. Schohaus 19. nov 2007 rev 1.0 origial version hm 16. mar. 2008 rev 1.1 added type conversion to avoid warnings under codesys 3.0 *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK GEN_RMP VAR_INPUT PT : TIME := t#1s; AM : REAL := 1.0; OS : REAL; DL : REAL; END_VAR VAR_OUTPUT Q : BOOL; OUT : REAL; END_VAR VAR tx : TIME; last : TIME; init : BOOL; temp : REAL; ltemp: REAL; END_VAR (* version 1.4 10. mar. 2009 programmer oscat tested BY oscat this signal generator generates a ramp wave output. The ramp wave signal is defined by period time (PT), amplitude (AM), offset (OS) and a specific delay for the output signal (DL). The Output waveform will have its minimum peak at OS and its maximum peak at AM + OS. The delay input can delay a signal up to PT, this can be useful to synchronize different generators and generate interleaving signals. in addition to the analog output Out there is a second boolean output Q with is true for one cycle when the ramp starts. *) (* @END_DECLARATION := '0' *) (* read system time and prepare input data *) tx := DWORD_TO_TIME(T_PLC_MS()) - last; DL := MODR(dl,1.0); IF dl < 0.0 THEN dl := 1.0 - dl; END_IF; (* init section *) IF NOT init THEN init := TRUE; last := tx; tx := t#0s; END_IF; (* add last if one cycle is finished *) IF tx >= pt THEN last := last + pt; tx := tx - pt; END_IF; (* generate sine wave *) ltemp := temp; IF pt > t#0s THEN temp := FRACT(TIME_TO_REAL(tx + MULTIME(pt, dl)) / TIME_TO_REAL(pt)); END_IF; out := am * temp + os; (* boolean output Q *) Q := temp < ltemp; (* revision history hm 3. mar 2007 rev 1.0 original version hm 17 sep 2007 rev 1.1 replaced time() with t_plc_ms for compatibilitx reasons hm 27. nov 2007 rev 1.2 avoid divide by 0 when pt = 0 ks 26. oct. 2008 rev 1.3 code optimization hm 10. mar. 2009 rev 1.4 changed real constants to use dot syntax *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK GEN_SIN VAR_INPUT PT : TIME; AM : REAL := 1.0; OS : REAL; DL : REAL; END_VAR VAR_OUTPUT Q : BOOL; Out : REAL; END_VAR VAR tx : TIME; last : TIME; init : BOOL; temp : REAL; END_VAR (* version 1.6 11. mar. 2009 programmer oscat tested BY oscat this signal generator generates a sine wave output. The sine wave signal is defined by period time (PT), amplitude (AM), offset (OS) and a specific delay for the output signal (DL). The Output waveform will have its max peak at AM/2 + OS and its minimum peak at -AM/2 + OS. The delay input can delay a signal up to PT, this can be useful to synchronize different generators and generate interleaving signals. A Cos wave can be generated accordingly. in addition to a analog output Out there is a second boolean output Q with the corresponding binary signal. *) (* @END_DECLARATION := '0' *) (* read system time and prepare input data *) tx := DWORD_TO_TIME(T_PLC_MS()) - last; DL := MODR(dl,1.0); IF dl < 0.0 THEN dl := 1.0 - dl; END_IF; (* init section *) IF NOT init THEN init := TRUE; last := tx; tx := t#0s; END_IF; (* add last if one cycle is finished *) IF tx >= pt THEN last := last + pt; tx := tx - pt; END_IF; (* generate sine wave *) IF pt > t#0s THEN temp := SIN(math.PI2 * DWORD_TO_REAL(TIME_TO_DWORD(tx + MULTIME(pt, dl))) / DWORD_TO_REAL(TIME_TO_DWORD(pt))); END_IF; out := am * 0.5 * temp + os; (* boolean output Q *) q := NOT SIGN_R(temp); (* revision history hm 22. jan 2007 rev 1.0 original version hm 17 sep 2007 rev 1.1 replaced time() with t_plc_ms for compatibilitx reasons hm 27. nov 2007 rev 1.2 avoid divide by 0 when pt = 0 hm 6. jan 2008 rev 1.3 improved performance hm 16. mar. 2008 rev 1.4 added type conversion to avoid warnings under codesys 3.0 hm 18. oct. 2008 rev 1.5 using math constants hm 11. mar. 2009 rev 1.6 changed real constants to use dot syntax *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK GEN_SQR VAR_INPUT PT : TIME; AM : REAL := 1.0; OS : REAL; DC : REAL := 0.5; DL : REAL; END_VAR VAR_OUTPUT Q : BOOL; Out : REAL; END_VAR VAR tx : TIME; last : TIME; init: BOOL; END_VAR (* version 1.4 11. mar. 2009 programmer oscat tested BY oscat this signal generator generates a square wave output. The square wave signal is defined by period time (PT), amplitude (AM), offset (OS), duty cycle (DC) and a specific delay for the output signal (DL). The Output waveform will switch between AM/2 + OS and -AM/2 + OS. The DC input specifies ther duty cycle, DC = 0 means output is low at all times and 1 means output is high at all times. The delay input can delay a signal up to PT, this can be useful to synchronize different generators and generate interleaving signals. in addition to a analog output Out there is a second boolean output Q. *) (* @END_DECLARATION := '0' *) (* check dc = 1 or 0 *) IF dc = 0.0 THEN out := -am * 0.5 + os; Q := FALSE; RETURN; ELSIF dc = 1.0 THEN out := am * 0.5 + os; Q := TRUE; RETURN; END_IF; (* read system time and prepare input data *) tx := DWORD_TO_TIME(T_PLC_MS()) - last; DL := MODR(dl,1.0); IF dl < 0.0 THEN dl := 1.0 - dl; END_IF; dc := MODR(dc,1.0); IF dc < 0.0 THEN dc := 1.0 - dc; END_IF; (* init section *) IF NOT init THEN init := TRUE; last := tx; tx := t#0s; END_IF; (* add last if one cycle is finished *) IF tx >= pt THEN last := last + pt; tx := tx - pt; END_IF; (* check if falling or rising edge first *) IF MULTIME(pt, dl + dc) >= pt THEN (* generate falling edge *) IF tx >= MULTIME(pt, dl + dc - 1) THEN out := -am * 0.5 + os; Q := FALSE; END_IF; (* generate rising edge *) IF tx >= MULTIME(pt, dl) THEN out := am * 0.5 + os; Q := TRUE; END_IF; ELSE (* generate rising edge first *) IF tx >= MULTIME(pt, dl) THEN out := am * 0.5 + os; Q := TRUE; END_IF; (* generate falling edge *) IF tx >= MULTIME(pt, dl + dc) THEN out := -am * 0.5 +os; Q := FALSE; END_IF; END_IF; (* revision history hm 12. feb 2007 rev 1.1 added default value for dc = 0.5 hm 17 sep 2007 rev 1.2 replaced time() with t_plc_ms for compatibilitx reasons hm 6. jan 2008 rev 1.3 improved performance hm 11. mar. 2009 rev 1.4 changed real constants to use dot syntax set default amplitude to 1.0 *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK PWM_DC VAR_INPUT F : REAL; DC : REAL; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR clk: CLK_PRG; pulse: TP_X; tmp: REAL; END_VAR (* version 1.4 11. mar. 2009 programmer oscat tested BY oscat this signal generator generates a square wave signal which is specified by the frequency and the duty cycle *) (* @END_DECLARATION := '0' *) IF F > 0.0 THEN tmp := 1000.0 / F; CLK(PT := REAL_TO_TIME(tmp)); Pulse(in := clk.Q, pt := REAL_TO_TIME(tmp * DC)); Q := pulse.Q; END_IF; (* revision history hm 25. feb 2007 rev 1.1 recoded in st for better performance and better portability hm 27. nov 2007 rev 1.2 avoid divide by 0 when F = 0 hm 19. oct. 2008 rev 1.3 changed type TP_R to TP_X because of name change improved performance hm 11. mar. 2009 rev 1.4 changed real constants to use dot syntax *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK PWM_PW VAR_INPUT F : REAL; PW : TIME; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR clk: CLK_PRG; pulse: TP_X; END_VAR (* version 1.5 11. mar. 2009 programmer oscat tested BY oscat this signal generator generates a square wave signal which is specified by the frequency and the pulse width. *) (* @END_DECLARATION := '0' *) IF F > 0.0 THEN CLK(PT := REAL_TO_TIME(1000.0 / F)); Pulse(in := clk.Q, pt := pw); Q := pulse.Q; END_IF; (* revision history hm 25. feb 2007 rev 1.1 recoded in st for better performance and better portability hm 27. nov 2007 rev 1.2 avoid divide by 0 when F = 0 hm 9. dec 2007 rev 1.3 corrected an error with F = 0 hm 19. oct. 2008 rev 1.4 changed type tp_r to TP_x brecause of name change hm 11. mar. 2009 rev 1.5 changed real constants to use dot syntax *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK RMP_B VAR_INPUT SET : BOOL; PT : TIME; E : BOOL := TRUE; UP : BOOL := TRUE; RST : BOOL; END_VAR VAR_OUTPUT OUT : BYTE; BUSY: BOOL; HIGH : BOOL; LOW : BOOL; END_VAR VAR rmp : _RMP_B; END_VAR (* version 2.0 23. oct. 2008 programmer oscat tested BY oscat this ramp generator generates a byte wide ramp with 255 steps the generator has an asynchronous set and reset the ramp is controlled by PT which is the total ramp time for a full sweep from 0..255 an UD input controlls the direction Up or Down and the E input enables the ramp generation a busy output indicates that the ramp is running, busy false means output is stable. the outputs low and high will be true when the output has reached its max or min value. *) (* @END_DECLARATION := '0' *) (* generate ramp *) rmp(dir := UP, E := E, TR := PT, rmp := out); (* set or reset operation *) IF RST THEN out := 0; ELSIF SET THEN out := 255; END_IF; (* checks for outputs stable and reset or set busy flag *) low := out = 0; high := out = 255; busy := NOT (low OR high) AND E; (* revision history hm 5.10.2006 rev 1.1 added busy output hm 17.1.2007 rev 1.2 renamed input ud to up for better usability deleted unused variable step hm 17.9.2007 rev 1.3 replaced time() with t_plc_ms() for compatibility reasons hm 28. sep 2007 rev 1.4 added outputs on and off busy is now disabled while en is false new coding for higher accuracy and performance hm 5. oct 2007 rev 1.5 added statements to allow for PT to be t#0s output jumps to max or min immediately hm 25. dec 2007 rev 1.6 improved code for better performance hm 16. oct. 2008 rev 1.7 improved code for better performance hm 18. oct. 2008 rev 1.8 added type conversion to avoid warnings changed input en to e for compatibility reasons hm 23. oct. 2008 REV 2.0 new code using _RMP_B *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK RMP_SOFT VAR_INPUT IN : BOOL; VAL : BYTE; END_VAR VAR_INPUT CONSTANT PT_ON: TIME; PT_OFF : TIME; END_VAR VAR_OUTPUT OUT : BYTE; END_VAR VAR rmp : _RMP_B; tmp: BYTE; END_VAR (* version 2.0 26. oct. 2008 programmer oscat tested BY oscat this soft on/off ramp generator generates a soft on and soft off ramp while the max on value is set by the input the time for a full ramp can be set by config variables for up and down ramp. *) (* @END_DECLARATION := '0' *) tmp := SEL(in, 0, val); IF tmp > out THEN (* we need to ramp down *) rmp(dir := TRUE, E := TRUE, TR := PT_ON, rmp := out); (* make sure out does not surpass tmp *) out := MIN(out, tmp); ELSIF tmp < out THEN (* we need to ramp up *) rmp(dir := FALSE, E := TRUE, TR := PT_OFF, rmp := out); (* make sure out does not surpass tmp *) out := MAX(out, tmp); ELSE (* no ramp necessary *) rmp(E := FALSE, rmp := out); END_IF; (* revision history hm 22.1.2007 rev 1.1 deleted unused variables X1 and I, X2 and X3 hm 17.9.2007 rev 1.2 replaced time() with t_plc_ms() for compatibility reasons hm 26. oct. 2008 2.0 new code using _rmp_b *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK RMP_W VAR_INPUT SET : BOOL; PT : TIME; E : BOOL := TRUE; UP : BOOL := TRUE; RST : BOOL; END_VAR VAR_OUTPUT out : WORD; busy : BOOL; high : BOOL; low : BOOL; END_VAR VAR rmp : _RMP_W; END_VAR (* version 2.0 23. oct. 2008 programmer oscat tested BY oscat this ramp generator generates a Word wide ramp with 65535 steps the generator has an asynchronous set and reset the ramp is controlled by PT which is the total ramp time for a full sweep from 0..65535 an UD input controlls the direction Up or Down and the E input enables the ramp generation a busy output indicates that the ramp is running, busy false means output is stable. the outputs low and high will be true when the output has reached its max or min value. *) (* @END_DECLARATION := '0' *) (* generate ramp *) rmp(dir := UP, E := E, TR := PT, rmp := out); (* set or reset operation *) IF RST THEN out := 0; ELSIF SET THEN out := 65535; END_IF; (* checks for outputs stable and reset or set busy flag *) low := out = 0; high := out = 65535; busy := NOT (low OR high) AND E; (* revision history: hm 4.10.2006 rev 1.1 added the busy output which signals that the ramp is running. hm 22.1.2007 rev 1.2 deleted unused variable step hm 17.9.2007 rev 1.3 replaced time() with t_plc_ms() for compatibility reasons hm 28. sep 2007 rev 1.4 added outputs on and off busy is now disabled while en is false new coding for higher accuracy and performance hm 5. oct 2007 rev 1.5 added statements to allow for PT to be t#0s output jumps to max or min immediately hm 2. dec 2007 rev 1.6 corrected an error in calculation of step response hm 25. dec 2007 rev 1.7 corrected an error while step response was too slow for fast rise times hm 16. oct. 2008 rev 1.8 improved performance hm 18. oct. 2008 rev 1.9 renamed inout EN to E for compatibility reasons hm 23. oct. 2008 rev 2.0 new code using _RMP_W *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION AIN : REAL VAR_INPUT in : DWORD; END_VAR VAR_INPUT CONSTANT Bits : BYTE; sign : BYTE := 255; low : REAL; high : REAL := 10.0; END_VAR VAR CONSTANT ff : DWORD := 16#FFFFFFFF; END_VAR VAR temp1 : DWORD; temp2 : DWORD; sx : BOOL; END_VAR (* version 1.5 16 mar 2008 programmer oscat tested by tobias Ain converts signals from A/D converters or other digital sources to an internal real value the lowest number of bits are extracted from the input word and the sign will be extracted if available separately. the output signal is then conditioned to range from low to high values for a 0 to max value on the analog inputs: for example a 15bit input (bits := 12) with sign at bit 15 (0..15) will deliver 0.0 (low value at 0) for an input value of 2#0000_0000_0000 an input value of 2#1111_1111_1111 will deliver 10.0 on the output (high value set to 10). *) (* @END_DECLARATION := '0' *) (* extract the sign bit *) IF sign < 32 THEN temp1 := SHR(in,sign); sx := temp1.0; ELSE sx := FALSE; END_IF; temp1 := SHR(ff, 32-bits); temp2 := in AND temp1; AIN := (high - low) * DWORD_TO_REAL(temp2) / DWORD_TO_REAL(temp1) + low; IF sx THEN AIN := -AIN; END_IF; (* revision history hm 18.8.2006 rev 1.1 fixed an error with low value negative and high value 0. hm 19.1.2007 rev 1.2 fixed an error with sign bit. hm 13.9.2007 rev 1.3 changed code to avoid warning under codesys 2.8.1 hm 2. dec 2007 rev 1.4 changed code for better performance hm 16. mar 2008 rev 1.5 added type conversions to avoid warnings under codesys 30 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK AIN1 VAR_INPUT in : DWORD; END_VAR VAR_INPUT CONSTANT sign_bit : INT := 255; error_bit : INT := 255; error_code_en : BOOL; error_code : DWORD; overflow_bit : INT := 255; overflow_code_en : BOOL; overflow_code : DWORD; Bit_0 : INT; Bit_N : INT := 31; out_min : REAL; out_max : REAL := 10.0; code_min : DWORD; code_max : DWORD := 16#FFFFFFFF; error_output : REAL; overflow_output : REAL := 10.0; END_VAR VAR_OUTPUT out : REAL; sign : BOOL; error : BOOL; overflow : BOOL; END_VAR VAR tB: DWORD; END_VAR (* version 1.3 10. mar. 2009 programmer oscat tested by tobias Ain1 converts signals from A/D converters or other digital sources to an internal real value. *) (* @END_DECLARATION := '0' *) (* extract error bit *) error := ((SHR(in,error_bit) AND 16#0000_0001) = 1) OR (error_code_en AND error_code = in); IF error THEN out := error_output; RETURN; END_IF; (* strip off the data input *) tb := SHR(SHL(in, 31 - bit_N), 31 - bit_N + Bit_0); (* extract overflow bit *) overflow := ((SHR(in,overflow_bit) AND 16#0000_0001) = 1) OR (overflow_code_en AND overflow_code = in) OR (tb < code_min OR tb > code_max); IF overflow THEN out := overflow_output; RETURN; END_IF; (* extract sign bit *) sign := (SHR(in,sign_bit) AND 16#0000_0001) = 1; (* convert in to out *) out := (DWORD_TO_REAL(tb - code_min) * (out_max - out_min) / DWORD_TO_REAL(code_max - code_min) + out_min); IF sign THEN out := out * -1.0; END_IF; (* revision history hm 23. feb 2008 rev 1.0 original version hm 16. mar 2008 rev 1.1 added type conversions to avoid warnngs under codesys 30 hm 22. apr. 2008 rev 1.2 corrected error in formula when code_min was set corrected error when sign bit was used optimized code for better performance hm 10. mar. 2009 rev 1.3 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION AOUT : DWORD VAR_INPUT in : REAL; END_VAR VAR_INPUT CONSTANT Bits : BYTE; sign : BYTE := 255; low : REAL; high : REAL := 10.0; END_VAR VAR CONSTANT ff : DWORD := 2#1; END_VAR VAR in2 : REAL; sx : BOOL; END_VAR (* version 1.4 23 feb 2008 programmer oscat tested by tobias this module conditions an internal real value for D/A converter. Aout converts and internal real value to a word for an D/A converter or other output devices. The input value is converted to a n-bit wide output and a sign bit is set separately as specified. the outout min value is set for the specified min input value and the max output is set for the max input value. an input higher or lower then the max or min value will set the respective max or min value or the output. *) (* @END_DECLARATION := '0' *) (* if sign bit is defined *) IF sign < 32 THEN sx := sign_R(in); in2 := ABS(in); ELSE in2 := in; END_IF; (* begrenze in auf die zulässigen werte *) in2 := LIMIT(low, in2, high); (* Berechne den Ausgangswert *) Aout := REAL_TO_DWORD((in2 - low) / (high - low) * DWORD_TO_REAL(SHL(ff,bits) -1)); IF sx THEN Aout := SHL(ff,sign) OR Aout; END_IF; (* revision history hm 18.1.2007 rev 1.1 renamed Modul to aout. changed Output to 32 Bit max. corrected error with sign bit. hm 13.9.2007 rev 1.2 changed code to avoid warning under codesys 2.8.1 hm 2. dec 2007 rev 1.3 changed code for better performance hm 23. feb 2008 rev 1.4 changed code for better performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION AOUT1 : DWORD VAR_INPUT in : REAL; END_VAR VAR_INPUT CONSTANT Bit_0 : INT; Bit_N : INT := 31; sign : INT := 255; low : REAL; high : REAL := 10.0; END_VAR VAR CONSTANT ff : DWORD := 2#1; END_VAR VAR sx: BOOL; in2 : REAL; END_VAR (* version 1.0 23 feb 2008 programmer oscat tested by tobias this module conditions an internal real value for a D/A converter. *) (* @END_DECLARATION := '0' *) (* if sign bit is defined *) IF sign < 32 THEN sx := SIGN_R(in); in2 := ABS(in); ELSE in2 := in; END_IF; (* begrenze in auf die zulässigen werte *) in2 := LIMIT(low, in2, high); (* Berechne den Ausgangswert *) AOUT1 := SHL(REAL_TO_DWORD((in2 - low) / (high - low) * DWORD_TO_REAL(SHL(ff,bit_n - Bit_0 + 1) -1)),Bit_0); IF sx THEN AOUT1 := SHL(ff,sign) OR AOUT1; END_IF; (* revision history hm 23. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BYTE_TO_RANGE : REAL VAR_INPUT X : BYTE; low : REAL; high : REAL; END_VAR (* version 1.0 9. jan 2008 programmer hugo tested by tobias Byte_to_Range converts a Byte into a real between low and high. *) (* @END_DECLARATION := '0' *) BYTE_TO_RANGE := (high - low) * X / 255.0 + low; (* revision history hm 9. jan 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK DELAY VAR_INPUT IN : REAL; N : INT; RST : BOOL; END_VAR VAR_OUTPUT OUT : REAL; END_VAR VAR buf : ARRAY[0..31] OF REAL; i : INT; init: BOOL; stop: INT; END_VAR (* version 1.5 23. mar. 2009 programmer hugo tested by oscat this function block delays input values by each programm cycle after N+1 cycles the in value has shifted to the out. N can be any alue from 0 .. 32 if n = 0 the input will be present on the output without a delay. f N > 32 then the output will be delayed by 32 cycles. any high on rst will load the buffer with in. *) (* @END_DECLARATION := '0' *) stop := LIMIT(0,N,32) - 1; IF rst OR NOT init THEN init := TRUE; FOR i := 0 TO stop DO buf[i] := in; END_FOR; out := in; i := 0; ELSIF stop < 0 THEN out := in; ELSE out := buf[i]; buf[i] := in; i := INC1(i, N); END_IF; (* revision history hm 1.10.2006 rev 1.1 corrected error in buffer management hm 19.1.2007 rev 1.2 changed reset to load the value of in instead of 0 hm 27. oct. 2008 rev 1.3 improved performance hm 23. feb.2009 rev 1.4 corrected an index problem hm 23. mar. 2009 rev 1.5 corrected non standard write to input N *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK DELAY_4 VAR_INPUT in : REAL; END_VAR VAR_OUTPUT out1 : REAL; out2 : REAL; out3 : REAL; out4 : REAL; END_VAR VAR temp: REAL; END_VAR (* version 1.1 19 jan 2007 programmer hugo tested by tobias this function block delays input values by each programm cycle after 4 cycles the in value has shifted to the out 4 and will be discarded after the next cycle the blocks can be cascaded. *) (* @END_DECLARATION := '0' *) out4 := out3; out3 := out2; out2 := out1; out1 := temp; temp := in; (* revision history hm 19.1.2007 rev 1.1 added variable temp to add 1 delay for q1 *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FADE VAR_INPUT IN1 : REAL; IN2 : REAL; F : BOOL; TF : TIME; rst : BOOL; END_VAR VAR_OUTPUT Y : REAL; END_VAR VAR rmx : RMP_W; END_VAR (* version 1.3 24. jan. 2009 programmer oscat tested BY oscat FADE is used to crossfade between the inputs IN1 and IN2. The fade_over time is specified with TF. When F = TRUE then Y = IN2 after the time TF, and when F = FALSE then Y = IN1. *) (* @END_DECLARATION := '0' *) rmx(rst := rst AND NOT F, set := rst AND F, pt := TF, up := F); Y := (in2 - In1) / 65535.0 * WORD_TO_REAL(rmx.out) + in1; (* code for rev 1.1 IF rst THEN rmx(set := F, rst := NOT F); ELSIF F AND (NOT rmx.high) THEN rmx(PT := TF, UP := TRUE, e := TRUE, rst := FALSE, set := FALSE); ELSIF (NOT F) AND (NOT rmx.low) THEN rmx(PT := TF, UP := FALSE, e := TRUE, rst := FALSE, set := FALSE); ELSE rmx(e := FALSE, rst := FALSE, set := FALSE); END_IF; Y := (WORD_TO_REAL(rmx.out) * in1 + WORD_TO_REAL(FF - rmx.out) * IN2) / FF; *) (* revision history hm 26. dec 2007 rev 1.0 original version hm 18. oct. 2008 rev 1.1 improved performance changed calls for rmp_w because rmp_w has chaged hm 17. dec. 2008 rev 1.2 function of input f was inverted hm 24. jan. 2009 rev 1.3 delted unused var FF *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FILTER_DW VAR_INPUT X : DWORD; T : TIME; END_VAR VAR_OUTPUT Y : DWORD; END_VAR VAR last : DWORD; tx: DWORD; init: BOOL; Yi : REAL; END_VAR (* version 1.1 3. nov. 2008 programmer hugo tested by oscat FILTER_DW is an low pass filter with a programmable time T used for DWORD format. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := T_PLC_MS(); (* startup initialisation *) IF NOT init OR T = t#0s THEN init := TRUE; Yi := DWORD_TO_REAL(X); ELSE Yi := Yi + (DWORD_TO_REAL(X) - DWORD_TO_REAL(Y)) * DWORD_TO_REAL(tx - last) / TIME_TO_REAL(T); END_IF; last := tx; Y := REAL_TO_DWORD(Yi); (* hm 10. oct. 2008 rev 1.0 original version hm 3. nov. 2008 REV 1.1 corrected an overflow problem *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FILTER_I VAR_INPUT X : INT; T : TIME; END_VAR VAR_OUTPUT Y : INT; END_VAR VAR Yi : DINT; last : DWORD; tx: DWORD; init: BOOL; END_VAR (* version 1.0 8. nov. 2008 programmer hugo tested by oscat FILTER_I is a low pass filter with a programmable time T used for INT format. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := T_PLC_MS(); (* startup initialisation *) IF NOT init OR T = t#0s THEN init := TRUE; Yi := INT_TO_DINT(X) * 1000; ELSE (* to increase accuracy of the filter we calculate internal Yi wich is Y * 1000 *) Yi := Yi + INT_TO_DINT(X - Y) * DWORD_TO_DINT(tx - last) * 1000 / TIME_TO_DINT(T); END_IF; last := tx; Y := DINT_TO_INT(yi / 1000); (* hm 8. nov. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FILTER_MAV_DW VAR_INPUT X : DWORD; N : UINT; RST : BOOL; END_VAR VAR_OUTPUT Y : DWORD; END_VAR VAR init: BOOL; buffer : ARRAY[0..31] OF DWORD; i: INT; END_VAR VAR_TEMP tmp: INT; END_VAR (* version 1.3 23. feb. 2009 programmer hugo tested by oscat FILTER_MAV_DW is a moving average filter with programmable length N for DWORD Data. *) (* @END_DECLARATION := '0' *) (* limit N to size of buffer *) N := MIN(N, 32); (* startup initialisation *) IF NOT init OR rst OR N = 0 THEN init := TRUE; tmp := UINT_TO_INT(N)-1; FOR i := 0 TO tmp DO buffer[i] := X; END_FOR; Y := X; ELSE tmp := UINT_TO_INT(N); i := INC1(i, tmp); Y := Y + (X - buffer[i]) / N; buffer[i] := X; END_IF; (* hm 13. oct. 2008 rev 1.0 original version hm 27. oct. 2008 rev 1.1 added typecast to avoid warnings hm 24. nov. 2008 rev 1.2 added typecasts to avoid warnings avoid divide by 0 if N = 0 hm 23. feb. 2009 rev 1.3 limit N to max array size *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FILTER_MAV_W VAR_INPUT X : WORD; N : UINT; RST : BOOL; END_VAR VAR_OUTPUT Y : WORD; END_VAR VAR init: BOOL; buffer : ARRAY[1..32] OF WORD; i: INT; sum : DWORD; END_VAR VAR_TEMP tmp : INT; END_VAR (* version 1.4 26. MAR. 2011 programmer hugo tested by oscat FILTER_MAV_W is a moving average filter with programmable length N for WORD Data. *) (* @END_DECLARATION := '0' *) (* limit N to size of buffer *) N := MIN(N, 32); (* startup initialisation *) IF NOT init OR rst OR N = 0 THEN init := TRUE; tmp := UINT_TO_INT(N) - 1; FOR i := 1 TO tmp DO buffer[i] := X; END_FOR; sum := Y * N; Y := X; ELSE tmp := UINT_TO_INT(N); i := INC1(i, tmp); sum := sum + X - buffer[i]; Y := DWORD_TO_WORD(sum / N); buffer[i] := X; END_IF; (* hm 13. oct. 2008 rev 1.0 original version hm 18. oct. 2008 rev 1.1 added typecast to avoid warnings hm 24. nov. 2008 rev 1.2 added typecasts to avoid warnings avoid devide by 0 if N = 0 hm 23. feb. 2009 rev 1.3 limit N to max array size hm 26. mar. 2011 rev 1.4 corrected error in calculation *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FILTER_W VAR_INPUT X : WORD; T : TIME; END_VAR VAR_OUTPUT Y : WORD; END_VAR VAR last : DWORD; tx: DWORD; init: BOOL; tmp: DWORD; END_VAR (* version 1.2 25. jan. 2011 programmer hugo tested by oscat FILTER_W is an low pass filter with a programmable time T used for WORD format. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := T_PLC_MS(); (* startup initialisation *) IF NOT init OR T = T#0s THEN init := TRUE; last := tx; Y := X; ELSIF Y = X THEN last := tx; ELSE tmp := WORD_TO_DWORD(X - Y) * (tx - last) / TIME_TO_DWORD(T); IF tmp <> 0 THEN Y := DINT_TO_WORD(WORD_TO_DINT(Y) + DWORD_TO_DINT(tmp)); last := tx; END_IF; END_IF; (* hm 10. oct. 2008 rev 1.0 original version hm 3. nov. 2008 rev 1.1 fixed overflow problem in formula hm 25. jan. 2011 rev 1.2 fixed error in formula *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FILTER_WAV VAR_INPUT X : REAL; W : ARRAY[0..15] OF REAL; RST : BOOL; END_VAR VAR_OUTPUT Y : REAL; END_VAR VAR init: BOOL; buffer : ARRAY[0..15] OF REAL; i: INT; n: INT; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by oscat FILTER_WAV is a moving average filter with programmable length N for DWORD Data. *) (* @END_DECLARATION := '0' *) (* startup initialisation *) IF NOT init OR rst THEN init := TRUE; FOR i := 0 TO 15 DO buffer[i] := X; END_FOR; i := 15; Y := X; ELSE i := INC1(i, 16); buffer[i] := X; END_IF; (* calculate the weighted average *) Y := 0.0; FOR n := 0 TO 15 DO Y := buffer[i] * W[n] + Y; i := DEC1(i, 16); END_FOR; (* hm 14. oct. 2008 rev 1.0 original version hm 27. oct. 2008 rev 1.1 changed _DEC and _INC to DEC1 and INC1 hm 10. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MIX : REAL VAR_INPUT A, B : REAL; M : REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias MIX is an analog Mixer. The Output is (1-M)*A + M*B. *) (* @END_DECLARATION := '0' *) MIX := (1.0 - M) * A + M * B; (* revision history hm 19. Nov 2007 rev 1.0 original version hm 10. mar 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MUX_R2 : REAL VAR_INPUT IN0 : REAL; IN1 : REAL; A : BOOL; END_VAR (* version 1.1 16. oct. 2008 programmer oscat tested by oscat MUX_R2 is an analog Multiplexer. one of 2 real inputs are selected and put through to out. The Adress input A selects between 2 Real inputs IN0 and IN1, A=0 > In0, A=1 > in1. *) (* @END_DECLARATION := '0' *) MUX_R2 := SEL(A, IN0, IN1); (* revision history hm 19. jan. 2007 rev 1.0 original version hm 16. oct. 2008 rev 1.1 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MUX_R4 : REAL VAR_INPUT IN0 : REAL; IN1 : REAL; IN2 : REAL; IN3 : REAL; A0 : BOOL; A1 : BOOL; END_VAR (* version 1.1 16. oct. 2008 programmer oscat tested by oscat MUX_R4 is an analog Multiplexer. one of 4 real inputs are selected and put through to out. The Adress input A0 and A1 selects between the 4 Real inputs IN0.. IN1. *) (* @END_DECLARATION := '0' *) IF A1 THEN MUX_R4 := SEL(A0, IN2, IN3); ELSE MUX_R4 := SEL(A0, IN0, IN1); END_IF; (* revision history hm 19. jan 2007 rev 1.0 original version hm 16. oct. 2008 rev 1.1 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION OFFSET : REAL VAR_INPUT X : REAL; O1, O2, O3, O4 : BOOL; D : BOOL; END_VAR VAR_INPUT CONSTANT Offset_1 : REAL; Offset_2 : REAL; Offset_3 : REAL; Offset_4 : REAL; default : REAL; END_VAR (* version 1.0 12 jan 2007 programmer oscat tested by oscat The Function offset adds offsets to an analog signal depending on digital inputs. all selected offsets are added at the same time. with the input D a default value instead of the input X can be used. *) (* @END_DECLARATION := '0' *) IF D THEN OFFSET := default; ELSE OFFSET := X; END_IF; IF O1 THEN OFFSET := OFFSET + offset_1; END_IF; IF O2 THEN OFFSET := OFFSET + offset_2; END_IF; IF O3 THEN OFFSET := OFFSET + offset_3; END_IF; IF O4 THEN OFFSET := OFFSET + offset_4; END_IF; (* revision history hm 12. jan 2007 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION OFFSET2 : REAL VAR_INPUT X : REAL; O1, O2, O3, O4 : BOOL; D : BOOL; END_VAR VAR_INPUT CONSTANT Offset_1 : REAL; Offset_2 : REAL; Offset_3 : REAL; Offset_4 : REAL; default : REAL; END_VAR (* version 1.0 12 jan 2007 programmer oscat tested by tobias The Function offset adds offsets to an analog signal depending on digital inputs. one offset can be added at the same time, if more then one input is true, the one with the highest number (o1 .. o4) will be used. The input D will select a default value instead of X for input. *) (* @END_DECLARATION := '0' *) IF D THEN OFFSET2 := default; ELSE OFFSET2 := X; END_IF; IF O4 THEN OFFSET2 := OFFSET2 + offset_4; ELSIF O3 THEN OFFSET2 := OFFSET2 + offset_3; ELSIF O2 THEN OFFSET2 := OFFSET2 + offset_2; ELSIF O1 THEN OFFSET2 := OFFSET2 + offset_1; END_IF; END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION OVERRIDE : REAL VAR_INPUT X1, X2, X3 : REAL; E1 : BOOL; E2 : BOOL; E3 : BOOL; END_VAR (* version 1.0 4 nov 2007 programmer oscat tested by tobias OVERRIDE deliveres the maximum absolute value of the 3 inputs X1, X2 and X3. with the inputs E1, E2 and E3 each of the 3 inputs can be turned on or off. *) (* @END_DECLARATION := '0' *) IF E1 THEN OVERRIDE := X1; END_IF; IF E2 AND ABS(x2) > ABS(OVERRIDE) THEN OVERRIDE := X2; END_IF; IF E3 AND ABS(x3) > ABS(OVERRIDE) THEN OVERRIDE := X3; END_IF; (* revision history hm 4.11.2007 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION RANGE_TO_BYTE : BYTE VAR_INPUT X : REAL; low : REAL; high : REAL; END_VAR (* version 1.0 9. jan 2008 programmer hugo tested by tobias Range_to_byte converts a real value between low and high into a byte *) (* @END_DECLARATION := '0' *) RANGE_TO_BYTE := INT_TO_BYTE(TRUNC((LIMIT(low, X, high) - low) * 255.0 / (high - low))); (* revision history hm 9. jan 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION RANGE_TO_WORD : WORD VAR_INPUT X : REAL; low : REAL; high : REAL; END_VAR (* version 1.0 9. jan 2008 programmer hugo tested by tobias Range_to_word converts a real value between low and high into a byte *) (* @END_DECLARATION := '0' *) RANGE_TO_WORD := DINT_TO_WORD(TRUNC((LIMIT(low,X,high)-low) * 65535.0 / (high - low))); (* revision history hm 9. jan 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SCALE : REAL VAR_INPUT X : REAL; K : REAL; O : REAL; MX : REAL; MN : REAL; END_VAR (* version 1.0 16. may. 2008 programmer hugo tested by tobias Scale is used to translate an input x to output by the formula Y = X*K + O. at the same time the output is limited to MN and MX. *) (* @END_DECLARATION := '0' *) SCALE := LIMIT(MN, X * K + O, MX); (* revision history hm 16. may. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SCALE_B : REAL VAR_INPUT X : BYTE; I_LO : BYTE; I_HI : BYTE; O_LO : REAL; O_HI : REAL; END_VAR (* version 1.1 18. jan. 2011 programmer hugo tested by tobias Scale_B is used to translate and scale a byte input x to a real output. *) (* @END_DECLARATION := '0' *) IF I_HI = I_LO THEN SCALE_B := O_LO; ELSE SCALE_B := (O_HI - O_LO) / BYTE_TO_REAL(I_HI - I_LO) * BYTE_TO_REAL(LIMIT(I_LO, X, I_HI)); END_IF (* revision history hm 18. may. 2008 rev 1.0 original version hm 18. jan 2011 rev 1.1 avoid division by 0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SCALE_B2 : REAL VAR_INPUT in1: BYTE; in2: BYTE; K : REAL; O : REAL; END_VAR VAR_INPUT CONSTANT in1_min: REAL; in1_max: REAL := 1000.0; in2_min: REAL; in2_max: REAL := 1000.0; END_VAR (* version 1.4 3. nov. 2008 programmer hugo tested by oscat this function block can scale up to two inputs. inputs have their min value at 0 and their max value at 255 while the min and max value can be either positive or negative. inputs ramp between min and max values for the respective inputs to be between (0..255). the summed output is then scaled by an scale input K and a offset O can be added to the output. min and max input configurations can be edited in the cfc editor by double clicking the symbol body. *) (* @END_DECLARATION := '0' *) SCALE_B2 := (((in1_max - in1_min)* in1 + (in2_max - in2_min)* in2) * 0.003921569 + in1_min + in2_min) * K + O; (* revision History hm 19.1.2007 rev 1.1 changed outputs to real to avoid overflow of integer added offset for better cascading of scale functions changed from FB to function hm 6. jan 2008 rev 1.2 improved performance hm 26. oct. 2008 rev 1.3 code optimization hm 3. nov. 2008 rev 1.4 used wrong factor in formula *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SCALE_B4 : REAL VAR_INPUT in1: BYTE; in2: BYTE; in3: BYTE; in4: BYTE; K : REAL; O : REAL; END_VAR VAR_INPUT CONSTANT in1_min: REAL; in1_max: REAL := 1000.0; in2_min: REAL; in2_max: REAL := 1000.0; in3_min: REAL; in3_max: REAL := 1000.0; in4_min: REAL; in4_max: REAL := 1000.0; END_VAR (* version 1.4 3. nov. 2008 programmer hugo tested by tobias this functiob block can scale up to 4 inputs. the inputs have their min value at 0 and their max value at 255 while the min and max value can be either positive or negative. the inputs ramp between min and max values for the respective inputs to be between (0..255). the summed output is then scaled by an scale input K and a offset O can be added to the output. the min and max input configurations can be edited in the cfc editor by double clicking the symbol body. *) (* @END_DECLARATION := '0' *) SCALE_B4 := (((in1_max - in1_min)* in1 + (in2_max - in2_min)* in2 + (in3_max - in3_min)* in3 + (in4_max - in4_min)* in4)* 0.003921569 + in1_min + in2_min + in3_min + in4_min) * K + O; (* revision History hm 19.1.2007 rev 1.1 changed outputs to real to avoid overflow of integer added offset for better cascading of scale functions changed from fb to function hm 6. jan 2008 rev 1.2 improved performance hm 26. oct. 2008 rev 1.3 optimized code hm 3. nov. 2008 rev 1.4 used wrong factor in formula *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SCALE_B8 : REAL VAR_INPUT in1: BYTE; in2: BYTE; in3: BYTE; in4: BYTE; in5: BYTE; in6: BYTE; in7: BYTE; in8: BYTE; K : REAL; O : REAL; END_VAR VAR_INPUT CONSTANT in1_min: REAL; in1_max: REAL := 1000.0; in2_min: REAL; in2_max: REAL := 1000.0; in3_min: REAL; in3_max: REAL := 1000.0; in4_min: REAL; in4_max: REAL := 1000.0; in5_min: REAL; in5_max: REAL := 1000.0; in6_min: REAL; in6_max: REAL := 1000.0; in7_min: REAL; in7_max: REAL := 1000.0; in8_min: REAL; in8_max: REAL := 1000.0; END_VAR (* version 1.4 3. nov. 2008 programmer hugo tested by oscat this functiob block can scale up to 8 inputs. the inputs have their min value at 0 and their max value at 255 while the min and max value can be either positive or negative. the inputs ramp between min and max values for the respective inputs to be between (0..255). the summed output is then scaled by an scale input K and a offset O can be added to the output. the min and max input configurations can be edited in the cfc editor by double clicking the symbol body. *) (* @END_DECLARATION := '0' *) SCALE_B8 := (((in1_max - in1_min)* in1 + (in2_max - in2_min)* in2 + (in3_max - in3_min)* in3 + (in4_max - in4_min)* in4 + (in5_max - in5_min)* in5 + (in6_max - in6_min)* in6 + (in7_max - in7_min)* in7 + (in8_max - in8_min)* in8) * 0.003921569 + in1_min + in2_min + in3_min + in4_min + in5_min + in6_min + in7_min + in8_min) * K + O; (* revision History hm 19. jan.2007 rev 1.1 changed outputs to real to avoid overflow of integer added offset for better cascading of scale functions hm 6. jan. 2008 rev 1.2 improved performance hm 26. oct. 2008 rev 1.3 code optimization hm 3. nov. 2008 rev 1.4 used wrong factor in formula *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SCALE_D : REAL VAR_INPUT X : DWORD; I_LO : DWORD; I_HI : DWORD; O_LO : REAL; O_HI : REAL; END_VAR (* version 1.2 11. jan. 2011 programmer hugo tested by oscat Scale_D is used to translate and scale a DWORD input x to a real output. the input is limited to I_LO <= X <= I_HI. *) (* @END_DECLARATION := '0' *) IF I_HI = I_LO THEN SCALE_D := O_LO; ELSE SCALE_D := (O_HI - O_LO) / DWORD_TO_REAL(I_HI - I_LO) * DWORD_TO_REAL(LIMIT(I_LO, X, I_HI) - I_LO) + O_LO; END_IF; (* revision history hm 18. may. 2008 rev 1.0 original version hm 13. nov. 2008 rev 1.1 corrected formula for negative gradient hm 11. jan 2011 rev 1.2 avoid division by 0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SCALE_R : REAL VAR_INPUT X : REAL; I_LO : REAL; I_HI : REAL; O_LO : REAL; O_HI : REAL; END_VAR (* version 1.2 11. jan. 2011 programmer hugo tested by oscat Scale_R is used to translate and scale a REAL input x to a real output. the input is limited to I_LO <= X <= I_HI. *) (* @END_DECLARATION := '0' *) IF I_LO = I_HI THEN SCALE_R := O_LO; ELSE SCALE_R := (O_HI - O_LO) / (I_HI - I_LO) * (LIMIT(I_LO, X, I_HI) - I_LO) + O_LO; END_IF; (* revision history hm 18. may. 2008 rev 1.0 original version hm 13. nov. 2008 rev 1.1 corrected formula for negative gradient hm 11. jan 2011 rev 1.2 avoid division by 0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SCALE_X2 : REAL VAR_INPUT IN1: BOOL; IN2: BOOL; K : REAL; O : REAL; END_VAR VAR_INPUT CONSTANT IN1_MIN: REAL; IN1_MAX: REAL := 1000.0; IN2_MIN: REAL; IN2_MAX: REAL := 1000.0; END_VAR (* version 1.1 26. oct. 2008 programmer hugo tested by tobias this functiob block can scale up to two inputs. the input can select between two values with true or false. the summed output is then scaled by an scale input K and a offset O can be added to the output. the min and max input configurations can be edited in the cfc editor by double clicking the symbol body. *) (* @END_DECLARATION := '0' *) SCALE_X2 := (SEL(IN1, IN1_MIN, IN1_MAX)+ SEL(IN2, IN2_MIN, IN2_MAX)) * k + o; (* revision history hm 19. jan, 2007 rev 1.0 original version hm 26. oct. 2008 rev 1.1 code optimized *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SCALE_X4 : REAL VAR_INPUT IN1: BOOL; IN2: BOOL; IN3: BOOL; IN4: BOOL; K : REAL; O : REAL; END_VAR VAR_INPUT CONSTANT IN1_MIN: REAL; IN1_MAX: REAL := 1000.0; IN2_MIN: REAL; IN2_MAX: REAL := 1000.0; IN3_MIN: REAL; IN3_MAX: REAL := 1000.0; IN4_MIN: REAL; IN4_MAX: REAL := 1000.0; END_VAR (* version 1.1 26. oct. 2008 programmer hugo tested by tobias this function can scale up to 4 inputs. the input can select between two values with true or false. the summed output is then scaled by an scale input K and a offset O can be added to the output. the min and max input configurations can be edited in the cfc editor by double clicking the symbol body. *) (* @END_DECLARATION := '0' *) SCALE_X4 := (SEL(IN1,IN1_MIN, IN1_MAX) + SEL(IN2, IN2_MIN, IN2_MAX)+SEL(IN3, IN3_MIN, IN3_MAX)+ SEL(IN4, IN4_MIN, IN4_MAX)) * k + o; (* revision history hm 19. jan. 2008 rev 1.0 original version hm 26. oct. 2008 rev 1.1 optimized code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SCALE_X8 : REAL VAR_INPUT in1: BOOL; in2: BOOL; in3: BOOL; in4: BOOL; in5: BOOL; in6: BOOL; in7: BOOL; in8: BOOL; K : REAL; O : REAL; END_VAR VAR_INPUT CONSTANT in1_min: REAL; in1_max: REAL := 1000.0; in2_min: REAL; in2_max: REAL := 1000.0; in3_min: REAL; in3_max: REAL := 1000.0; in4_min: REAL; in4_max: REAL := 1000.0; in5_min: REAL; in5_max: REAL := 1000.0; in6_min: REAL; in6_max: REAL := 1000.0; in7_min: REAL; in7_max: REAL := 1000.0; in8_min: REAL; in8_max: REAL := 1000.0; END_VAR (* version 1.2 24. jan. 2009 programmer hugo tested by oscat this function can scale up to 4 inputs. the input can select between two values with true or false. the summed output is then scaled by an scale input K and a offset O can be added to the output. the min and max input configurations can be edited in the cfc editor by double clicking the symbol body. *) (* @END_DECLARATION := '0' *) SCALE_X8 := (SEL(IN1,IN1_MIN, IN1_MAX)+ SEL(IN2,IN2_MIN, IN2_MAX)+SEL(IN3,IN3_MIN, IN3_MAX)+ SEL(IN4,IN4_MIN, IN4_MAX) +SEL(IN5,IN5_MIN, IN5_MAX)+ SEL(IN6,IN6_MIN, IN6_MAX)+SEL(IN7,IN7_MIN, IN7_MAX)+ SEL(IN8,IN8_MIN, IN8_MAX)) * k + o; (* revision history hm 19. jan. 2008 rev 1.0 original version hm 26. oct. 2008 rev 1.1 optimized code hm 24. jan. 2008 rev 1.2 corrected error in formula *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SEL2_OF_3 VAR_INPUT IN1 : REAL; IN2 : REAL; IN3 : REAL; D : REAL; END_VAR VAR_OUTPUT Y : REAL; W : INT; E : BOOL; END_VAR VAR D12: BOOL; D23: BOOL; D31: BOOL; END_VAR (* version 1.1 10. mar. 2009 programmer oscat tested BY oscat SEL2_OF_3 checks if 3 input Signals are within a distance D from each other and calculates the average of the 3 inputs do not offset more than D from each other. If 1 input is more than D greater or smaller than any other input it will be ignored and the average will be calcualted from the remaining 2 Inputs. In the case the Output is only calculated from 2 Inputs a Warning Output W will display the Number of the discarded Input. if none of the 3 Inputs are within the allowed Delata D from each other, an Error Output E is set True and the last valid Output will remain active until at least 2 Inputs are within a valid Range from each other. *) (* @END_DECLARATION := '0' *) D12 := ABS(IN1-IN2) <= D; D23 := ABS(IN2-IN3) <= D; D31 := ABS(IN3-IN1) <= D; IF (D12 AND D23) OR (D12 AND D31) OR (D23 AND D31) THEN (* all 3 inputs are valid *) Y := (IN1 + IN2 + IN3) * 0.333333333333; E := FALSE; W := 0; ELSIF D12 THEN (* only inut 1 and 2 are valid *) Y := (In1 + IN2) * 0.5; E := FALSE; W := 3; ELSIF D23 THEN (* only inut 2 and 3 are valid *) Y := (In2 + IN3) * 0.5; E := FALSE; W := 1; ELSIF D31 THEN (* only inut 3 and 1 are valid *) Y := (In3 + IN1) * 0.5; E := FALSE; W := 2; ELSE (* no calculation possible *) E := TRUE; W := 4; END_IF; (* revision history hm 18. may 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 improved code *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SEL2_OF_3B VAR_INPUT IN1 : BOOL; IN2 : BOOL; IN3 : BOOL; TD : TIME; END_VAR VAR_OUTPUT Q : BOOL; W : BOOL; END_VAR VAR TDEL : TON; END_VAR (* version 1.0 19. may. 2008 programmer oscat tested BY oscat SEL2_OF_3B is used to connect 3 binary sensors to one signal. the output Q reflects the state of at least two inputs. if two or more of the 3 inputs ate TRUE then Q will be TRUE and if two or more of the three inputs are FALSE then Q will be false. the main putrpose is the connection of 3 redundant binary sensors. The output W will be active when one of the 3 inputs is of different state. to avoid flickering while the sensors are switching the output W will become active after a programmed delay of TD. *) (* @END_DECLARATION := '0' *) Q := (IN1 AND IN2) OR (IN1 AND IN3) OR (IN2 AND IN3); TDEL(IN := (in1 XOR in2) OR (in1 XOR in3) OR (in2 XOR in3), PT := TD); W := TDEL.Q; (* revision history hm 19. may 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SH VAR_INPUT in : REAL; CLK : BOOL; END_VAR VAR_OUTPUT out : REAL; trig : BOOL; END_VAR VAR edge : BOOL; END_VAR (* version 1.1 16 jan 2007 programmer hugo tested by tobias this sample and hold module samples an input at the rising edge of clk an stores it in out. the out stays stable until the next rising clk edge appears. a trigger output as active for one cycle when the output was updated. *) (* @END_DECLARATION := '0' *) IF clk AND NOT edge THEN out := in; trig := TRUE; ELSE; trig := FALSE; END_IF; edge := clk; (* revision history hm 16.1.2007 rev 1.1 added trig output *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SH_1 VAR_INPUT in : REAL; PT : TIME; END_VAR VAR_OUTPUT out : REAL; Trig : BOOL; END_VAR VAR last : TIME; tx: TIME; END_VAR (* version 1.2 17 sep 2007 programmer hugo tested by tobias this sample and hold module samples an input every PT seconds. after a ample is taken the output Trig will be active for one cycle. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := DWORD_TO_TIME(T_PLC_MS()); IF tx - last >= PT THEN last := tx; out := in; trig := TRUE; ELSE trig := FALSE; END_IF; (* revision history HM 6.1.2007 rev 1.1 added trig output HM 17.9.2007 rev 1.2 replaced time() with T_PLC_MS() for compatibility reasons *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SH_2 VAR_INPUT in : REAL; PT : TIME; N : INT := 16; disc : INT; END_VAR VAR_OUTPUT out : REAL; trig : BOOL; avg : REAL; high: REAL; low: REAL; END_VAR VAR M : INT; buf : ARRAY[0..15] OF REAL; buf2 : ARRAY[0..15] OF REAL; last : TIME; i : INT; start: INT; temp: REAL; stop: INT; tx: TIME; d2: INT; END_VAR (* version 1.6 10. mar. 2009 programmer hugo tested by tobias this sample and hold module samples an input every PT seconds. this sample and hold also does avg and other calculations with the input data. an average is calculated over N samples while for this calculation disc samples are discarded, disc = 0 all samples are averaged, if disc = 1 the lowest value is ignored, if disc = 2 the lowest and highest values are ignored, and so on..... *) (* @END_DECLARATION := '0' *) (* read system time *) tx := DWORD_TO_TIME(T_PLC_MS()); d2 := SHR(disc,1); IF tx - last >= PT THEN last := tx; trig := TRUE; (* limit M to 0..16 *) M := LIMIT(1,N,16); (* edge detected lets take the sample *) FOR i := M - 1 TO 1 BY -1 DO buf2[i] := buf2[i-1]; END_FOR; buf2[0] := in; out := in; buf := buf2; (* sort the ARRAY lowest value AT 0 *) FOR start := 0 TO M - 2 DO FOR i := start+1 TO M - 1 DO IF buf[start] > buf[i] THEN temp := buf[start]; buf[start] := buf[i]; buf[i] := temp; END_IF; END_FOR; END_FOR; (* any calculation with the samples is here *) stop := M - 1 - d2; start := d2; IF NOT even(disc) THEN start := start + 1; END_IF; avg := 0; FOR i := start TO stop DO avg := avg + buf[i]; END_FOR; avg := avg / INT_TO_REAL(stop - start +1); low := buf[start]; high := buf[stop]; ELSE Trig := FALSE; END_IF; (* revision history hm 20. jan. 2007 rev 1.1 added input N to specify the amout of samples for average and high low calculations added trig output hm 10. sep. 2007 rev 1.2 an error would be generated if N was set to 0, now n is forced to1 if set to 0. index was out of array. hm 17. sep. 2007 rev 1.3 replaced time() with t_plc_ms() for compatibility reasons hm 6. jan. 2008 rev 1.4 improved performance hm 14. jun. 2008 rev 1.5 set default for input N = 16 hm 10. mar. 2009 rev 1.6 added type conversion for compatibility reasons *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SH_T VAR_INPUT IN : REAL; E : BOOL; END_VAR VAR_OUTPUT OUT : REAL; END_VAR (* version 1.1 18. oct. 2008 programmer hugo tested by oscat this sample and hold module samples an input while en is high. during the high time of en the output follows in. the out stays stable until the en goes high again. *) (* @END_DECLARATION := '0' *) IF E THEN out := in; END_IF; (* revision history hm 1. sep. 2006 rev 1.0 original version hm 18. oct. 2008 rev 1.1 changed input en to e for compatibility reasons *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION STAIR : REAL VAR_INPUT X : REAL; D : REAL; END_VAR (* version 1.4 10. mar. 2009 programmer oscat tested by tobias the function stair converts an anlog input signal to a staircase like output. D is the step width for the output signal. if D = 0 then the output follows the input without a chage. *) (* @END_DECLARATION := '0' *) IF D > 0.0 THEN STAIR := DINT_TO_REAL(REAL_TO_DINT(X / D)) * D; ELSE STAIR := X; END_IF; (* revision history hm 28 jan 2007 rev 1.0 original version hm 27 dec 2007 rev 1.1 changed code for better performance hm 6. jan 2008 rev 1.2 further performance improvement hm 26. oct. 2008 rev 1.3 optimized code hm 10. mar. 2009 rev 1.4 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK STAIR2 VAR_INPUT X : REAL; D : REAL; END_VAR VAR_OUTPUT Y: REAL; END_VAR (* version 1.4 10. mar. 2009 programmer hugo tested by oscat the function stair2 converts an anlog input signal to a staircase like output. D is the step width for the output signal. the output will use D as a hysteresis for a stairchange. if D = 0 then the output follows the input without a chage. *) (* @END_DECLARATION := '0' *) IF D > 0.0 THEN IF X >= Y + D OR X <= Y - D THEN Y := FLOOR(X/D) * D; END_IF; ELSE Y := X; END_IF; (* revision history hm 28 jan 2007 rev 1.0 original version hm 27. dec 2007 rev 1.1 changed code for better performance hm 30. jun. 2008 rev 1.2 added type conversions to avoid warnings under codesys 3.0 ks 26. oct. 2008 rev 1.3 improved code hm 10. mar. 2009 rev 1.4 real constants updated to new systax using dot *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK TREND VAR_INPUT X : REAL; END_VAR VAR_OUTPUT Q : BOOL; TU : BOOL; TD : BOOL; D : REAL; END_VAR VAR last_X: REAL; END_VAR (* version 1.0 21. aug. 2009 programmer hugo tested by oscat trend analyses the trend of a input signal. The Output Q is True if the input X is >= last_X and is false if the input X is <= last_X in addition to the trend the output TU will be high for one cycle to signal a rising of the input value X and TD will signal a decreasing value on the input X. in case of a change the output D will show the delta of the input to the last input value. *) (* @END_DECLARATION := '0' *) TU := X > last_X; TD := X < last_X; Q := TU OR TD; D := X - LAST_X; last_X := X; (* revision history hm 21. aug. 2009 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK TREND_DW VAR_INPUT X : DWORD; END_VAR VAR_OUTPUT Q : BOOL; TU : BOOL; TD : BOOL; D : DWORD; END_VAR VAR last_X: DWORD; END_VAR (* version 1.2 14. mar. 2009 programmer hugo tested by oscat trend_DW analyses the trend of a input signal. The Output Q is True if the input X is >= last_X and is false if the input X is <= last_X in addition to the trend the output TU will be high for one cycle to signal a rising of the input value X and TD will signal a decreasing value on the input X. in case of a change the output D will show the delta of the input to the last input value. *) (* @END_DECLARATION := '0' *) IF X > last_X THEN TU := TRUE; TD := FALSE; D := X - last_X; Q := TRUE; ELSIF X < last_X THEN TD := TRUE; TU := FALSE; D := last_X - X; Q := FALSE; ELSE TU := FALSE; TD := FALSE; D := 0; END_IF; last_X := X; (* revision history hm 21. nov. 2008 rev 1.0 original version hm 20. feb. 2009 rev 1.1 added outputs TU, TD and D hm 14. mar. 2009 rev 1.2 removed double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Engineering\/signal processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION WORD_TO_RANGE : REAL VAR_INPUT X : WORD; low : REAL; high : REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias Word_to_Range converts a Byte into a real between low and high. *) (* @END_DECLARATION := '0' *) WORD_TO_RANGE := (high - low) * WORD_TO_REAL(X) * 0.00001525902189669640 + low; (* revision history hm 9. jan 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 improved code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/List Processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LIST_ADD : BOOL VAR_INPUT SEP : BYTE; INS : STRING(LIST_LENGTH); END_VAR VAR_IN_OUT LIST : STRING(LIST_LENGTH); END_VAR VAR sx : STRING(1); END_VAR (* version 2.0 21. mar. 2011 programmer hugo tested by kurt LIST_ADD hängt ein weiteres element ans ende einer liste. die liste liegt als string von elementen vor die mit den zeichen SEP am anfang makiert sind. *) (* @END_DECLARATION := '0' *) sx := CHR_TO_STRING(SEP); (* convert separation character to string *) INS := CONCAT(sx, INS); (* start element with separation character *) IF LEN(LIST) + LEN(INS) > LIST_LENGTH THEN LIST_ADD := FALSE; (* return false if element does not fit *) ELSE LIST := CONCAT(LIST, INS); LIST_ADD := TRUE; END_IF; (* revision histroy hm 21. mar. 2011 rev 2.0 original release *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/List Processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LIST_CLEAN : BOOL VAR_INPUT SEP : BYTE; END_VAR VAR_IN_OUT LIST : STRING(LIST_LENGTH); END_VAR VAR pt : POINTER TO ARRAY[1..LIST_LENGTH] OF BYTE; read : INT := 1; write : INT := 1; last: BYTE; c: BYTE; END_VAR (* version 2.0 21. mar. 2011 programmer hugo tested by oscat LIST_CLEAN bereinigt eine liste von leeren Elementen. die liste liegt als string von elementen vor die mit den zeichen SEP am anfang markiert sind. *) (* @END_DECLARATION := '0' *) pt := ADR(LIST); FOR read := 1 TO LIST_LENGTH DO c := pt^[read]; (* read character from list *) IF c = 0 THEN EXIT; (* exit the loop if character definbes end of string *) ELSIF c <> SEP OR sep <> last THEN (* copy element from read to write position unless a double sep character is present *) pt^[write] := c; write := write + 1; END_IF; last := c; (* remember last character *) END_FOR; IF last = SEP THEN write := write - 1; END_IF; (* if last character is sep then delete empty element at end *) IF write <= STRING_LENGTH THEN pt^[write] := 0; END_IF; (* terminate string with 0 *) LIST_CLEAN := TRUE; (* retrun TRUE *) (* revision histroy hm 28. jun. 2008 rev 1.0 original release hm 19. jan. 2011 rev 1.1 changed string(255) to string(LIST_LENGTH) hm 21. mar. 2011 rev 2.0 all elements start with SEP *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/List Processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LIST_GET : STRING(LIST_LENGTH) VAR_INPUT SEP : BYTE; POS : INT; END_VAR VAR_IN_OUT LIST : STRING(LIST_LENGTH); END_VAR VAR i : INT := 1; o : INT := 1; pt : POINTER TO ARRAY[1..LIST_LENGTH] OF BYTE; po : POINTER TO ARRAY[1..LIST_LENGTH] OF BYTE; cnt: INT := 0; c: BYTE; END_VAR (* version 2.0 21. mar. 2011 programmer hugo tested by oscat LIST_GET liefert das element an der stelle pos einer liste. die liste liegt als string von elementen vor die mit den zeichen SEP am anfang makiert sind. *) (* @END_DECLARATION := '0' *) pt := ADR(LIST); (* load pointers *) po := ADR(LIST_GET); REPEAT c := pt^[i]; IF cnt = pos THEN (* copy element to output string *) IF c = SEP THEN EXIT; END_IF; (* next element stop copy and finish *) po^[o] := c; o := o + 1; ELSIF c = SEP THEN cnt := cnt + 1; END_IF; i := i + 1; UNTIL (i = LIST_LENGTH) OR (c = 0) END_REPEAT; po^[o] := 0; (* close the output string *) (* revision histroy hm 20. jun. 2008 rev 1.0 original release hm 19. jan. 2011 rev 1.1 changed string(255) to strring(LIST_LENGTH) hm 21. mar. 2011 rev 2.0 all list elements start with SEP *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/List Processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LIST_INSERT : BOOL VAR_INPUT SEP : BYTE; POS : INT; INS : STRING(LIST_LENGTH); END_VAR VAR_IN_OUT LIST : STRING(LIST_LENGTH); END_VAR VAR pt : POINTER TO ARRAY[1..LIST_LENGTH] OF BYTE; read : INT := 1; cnt : INT := 1; sx : STRING(1); END_VAR (* version 2.0 21. mar. 2011 programmer hugo tested by oscat LIST_INSERT setzt ein element an der stelle pos in eine liste ein. die liste liegt als string von elementen vor die mit den zeichen SEP am anfang markiert sind. *) (* @END_DECLARATION := '0' *) (* load pointers *) pt := ADR(LIST); IF LEN(ins) + 1 + LEN(LIST) > LIST_LENGTH THEN LIST_INSERT := FALSE; ELSE LIST_INSERT := TRUE; (* search for position *) WHILE read < LIST_LENGTH DO IF cnt >= POS THEN sx := CHR_TO_STRING(SEP); INS := CONCAT(sx, INS); LIST := INSERT(LIST, INS, read - 1); LIST_INSERT := TRUE; RETURN; END_IF; IF pt^[read] = 0 THEN (* list is too short add empty element *) pt^[read] := SEP; pt^[read + 1] := 0; END_IF; read := read + 1; IF pt^[read] = SEP OR pt^[read] = 0 THEN cnt := cnt + 1; END_IF; END_WHILE; END_IF; (* revision histroy hm 28. jun. 2008 rev 1.0 original release hm 17. dec. 2008 rev 1.1 changes name of function chr to chr_to_string hm 19. jan. 2011 rev 1.2 changed string(255) to string(LIST_LENGTH) hm 21. mar. 2011 rev 2.0 all list elements start with SEP *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/List Processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LIST_LEN : INT VAR_INPUT SEP : BYTE; END_VAR VAR_IN_OUT LIST : STRING(LIST_LENGTH); END_VAR VAR pt : POINTER TO ARRAY[1..LIST_LENGTH] OF BYTE; pos: INT := 1; c: BYTE; END_VAR (* version 2.0 21. mar. 2011 programmer hugo tested by oscat LIST_LEN liefert die anzahl der elemente einer liste. die liste liegt als string von elementen vor die mit den zeichen SEP am anfang markiert sind. *) (* @END_DECLARATION := '0' *) pt := ADR(LIST); LIST_LEN := 0; REPEAT c := pt^[pos]; IF c = SEP THEN LIST_LEN := LIST_LEN + 1; END_IF; pos := pos + 1; UNTIL c = 0 OR pos > LIST_LENGTH END_REPEAT; (* revision histroy hm 25. jun. 2008 rev 1.0 original release hm 16. oct. 2008 rev 1.1 fixed a problem where list_len would only work up to LIST_LENGTH hm 19. jan. 2001 rev 1.2 changed string(255) to string(LIST_LENGTH) hm 21. mar. 2011 rev 2.0 all list elements start with SEP *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/List Processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK LIST_NEXT VAR_INPUT SEP : BYTE; RST : BOOL; END_VAR VAR_IN_OUT LIST : STRING(LIST_LENGTH); END_VAR VAR_OUTPUT LEL : STRING(LIST_LENGTH); NUL : BOOL; END_VAR VAR pos : INT := 1; pt : POINTER TO ARRAY[1..LIST_LENGTH] OF BYTE; po : POINTER TO ARRAY[1..LIST_LENGTH] OF BYTE; c: BYTE; write: INT; END_VAR (* version 2.0 21. mar. 2011 programmer hugo tested by oscat LIST_NEXT retrieves the next element of a list, starting from element 1 after reset or first init. when the end of the lisat is reached a '' empty string is returned and NUL is set to true. die liste liegt als string von elementen vor die mit den zeichen SEP am anfang markiert sind. *) (* @END_DECLARATION := '0' *) pt := ADR(LIST); po := ADR(LEL); IF rst THEN pos := 1; END_IF; IF pt^[pos] = 0 OR pos = LIST_LENGTH THEN LEL := ''; NUL := TRUE; ELSE NUL := FALSE; write := 1; FOR pos := pos + 1 TO LIST_LENGTH DO c := pt^[pos]; IF c = 0 OR c = SEP THEN po^[write] := 0; RETURN; ELSE po^[write] := pt^[pos]; write := write + 1; END_IF; END_FOR; END_IF; (* revision histroy hm 25. jun. 2008 rev 1.0 original release hm 19. jan. 2011 rev 1.1 changed string(255) to string(LIST_LENGTH) hm 21. mar. 2011 rev 2.0 all elements start with SEP *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/List Processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LIST_RETRIEVE : STRING(LIST_LENGTH) VAR_INPUT SEP : BYTE; POS : INT; END_VAR VAR_IN_OUT LIST : STRING(LIST_LENGTH); END_VAR VAR i : INT; o : INT := 1; w : INT := 1; pt : POINTER TO ARRAY[1..LIST_LENGTH] OF BYTE; po : POINTER TO ARRAY[1..LIST_LENGTH] OF BYTE; cnt: INT := 0; c: BYTE; END_VAR (* version 2.0 21. mar. 2011 programmer hugo tested by oscat LIST_RETRIEVE liefert das element an der stelle pos einer liste und löscht das element aus der liste. die liste liegt als string von elementen vor die mit den zeichen SEP separiert sind. *) (* @END_DECLARATION := '0' *) (* load pointers *) pt := ADR(LIST); po := ADR(LIST_RETRIEVE); IF pos > 0 THEN FOR i := 1 TO LIST_LENGTH DO c := pt^[i]; IF c = 0 THEN po^[o] := 0; IF cnt < pos THEN pt^[w + 1] := 0; ELSE pt^[w] := 0; END_IF; RETURN; ELSIF cnt = pos AND c <> SEP THEN (* we have found the element *) po^[o] := pt^[i]; o := o + 1; ELSIF cnt >= pos THEN pt^[w] := c; w := w + 1; ELSE w := i; END_IF; IF c = sep THEN cnt := cnt + 1; END_IF; END_FOR; ELSE LIST_RETRIEVE := ''; END_IF; (* revision histroy hm 28. jun. 2008 rev 1.0 original release hm 19. jan. 2011 rev 1.1 changed string(255) to string(LIST_LENGTH) hm 21. mar. 2011 rev 2.0 all elements start with SEP *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/List Processing' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LIST_RETRIEVE_LAST : STRING(LIST_LENGTH) VAR_INPUT SEP : BYTE; END_VAR VAR_IN_OUT LIST : STRING(LIST_LENGTH); END_VAR VAR i : INT; last : INT := 1; pt : POINTER TO ARRAY[1..LIST_LENGTH] OF BYTE; c: BYTE; END_VAR (* version 2.0 21. mar. 2011 programmer hugo tested by oscat LIST_RETRIEVE_LAST liefert das letzte element einer liste und löscht das element aus der liste. die liste liegt als string von elementen vor die mit den zeichen SEP beginnen. *) (* @END_DECLARATION := '0' *) (* load pointers *) pt := ADR(LIST); (* search position of last element *) FOR i := 1 TO LIST_LENGTH DO c := pt^[i]; IF c = 0 THEN EXIT; ELSIF C = SEP THEN last := i; END_IF; END_FOR; (* return last element of list *) LIST_RETRIEVE_LAST := MID(LIST, LIST_LENGTH, last + 1); (* terminate list at i *) pt^[last] := 0; (* revision histroy hm 21. mar. 2011 rev 2.0 new module *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF edge triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK COUNT_BR VAR_INPUT SET : BOOL; IN : BYTE; UP : BOOL; DN : BOOL; STEP : BYTE := 1; MX : BYTE := 255; RST : BOOL; END_VAR VAR_OUTPUT CNT : BYTE; END_VAR VAR last_up: BOOL; last_dn: BOOL; END_VAR (* version 1.0 16 jan 2008 programmer hugo tested BY tobias Count_BR is a byte counter with independen up and dn inputs. the counter counts from 0 to mx and continues at 0 after is has reached mx a step input sets the counters stepping width. *) (* @END_DECLARATION := '0' *) IF rst THEN cnt := 0; ELSIF set THEN cnt := LIMIT(0,in,mx); ELSIF up AND NOT last_up THEN cnt := INT_TO_BYTE(INC(cnt,step,mx)); ELSIF dn AND NOT last_dn THEN cnt := INT_TO_BYTE(inc(cnt,-step,mx)); END_IF; last_up := up; last_dn := dn; (* revision history hm 16. jan 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF edge triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK COUNT_DR VAR_INPUT SET : BOOL; IN : DWORD; UP : BOOL; DN : BOOL; STEP : DWORD := 1; MX : DWORD := 16#FFFFFFFF; RST : BOOL; END_VAR VAR_OUTPUT CNT : DWORD; END_VAR VAR last_up: BOOL; last_dn: BOOL; END_VAR (* version 1.1 20. jan. 2011 programmer hugo tested BY tobias Count_DR is a DWORD counter with independen up and dn inputs. the counter counts from 0 to mx and continues at 0 after is has reached mx a step input sets the counters stepping width. *) (* @END_DECLARATION := '0' *) IF rst THEN cnt := 0; ELSIF set THEN cnt := LIMIT(0,in,mx); ELSIF up AND NOT last_up THEN IF STEP > MX - CNT THEN CNT := CNT - MX + STEP - 1; ELSE CNT := CNT + STEP; END_IF; ELSIF dn AND NOT last_dn THEN IF STEP > CNT THEN CNT := CNT - STEP + MX + 1; ELSE CNT := CNT - STEP; END_IF; END_IF; last_up := up; last_dn := dn; (* revision history hm 12. jun 2008 rev 1.0 original version hm 20. jan. 2011 rev 1.1 corrected init value of MX to 16#FFFFFFFF *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF edge triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FF_D2E VAR_INPUT D0 : BOOL; D1 : BOOL; CLK : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Q0 : BOOL; Q1 : BOOL; END_VAR VAR edge : BOOL; END_VAR (* version 1.3 14. mar. 2009 programmer hugo tested by oscat dual D-type flip flop with reset and rising clock trigger *) (* @END_DECLARATION := '0' *) IF rst THEN Q0 := FALSE; Q1 := FALSE; ELSIF clk AND NOT edge THEN Q0 := D0; Q1 := D1; END_IF; edge := CLK; (* revision history hm 25. dec 2007 rev 1.0 original version hm 27. dec 2007 rev 1.1 changed code for better performance hm 30. oct. 200 rev 1.2 deleted unnecessary init with 0 hm 14. mar. 2009 rev 1.3 removed double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF edge triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FF_D4E VAR_INPUT D0 : BOOL; D1 : BOOL; D2 : BOOL; D3 : BOOL; CLK : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Q0 : BOOL; Q1 : BOOL; Q2 : BOOL; Q3 : BOOL; END_VAR VAR edge : BOOL; END_VAR (* version 1.3 14. mar. 2009 programmer hugo tested by oscat quad D-type flip flop with reset and rising clock trigger *) (* @END_DECLARATION := '0' *) IF rst THEN Q0 := FALSE; Q1 := FALSE; Q2 := FALSE; Q3 := FALSE; ELSIF clk AND NOT edge THEN Q0 := D0; Q1 := D1; Q2 := D2; Q3 := D3; END_IF; edge := CLK; (* revision history hm 4. aug 2006 rev 1.0 original version hm 27. dec 2007 rev 1.1 changed code for better performance hm 30. oct. 200 rev 1.2 deleted unnecessary init with 0 hm 14. mar. 2009 rev 1.3 removed double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF edge triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FF_DRE VAR_INPUT SET : BOOL; D : BOOL; CLK : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR edge : BOOL; END_VAR (* version 1.2 30. oct. 2008 programmer hugo tested by oscat D-type flip flop with set, reset and rising clock trigger *) (* @END_DECLARATION := '0' *) IF rst OR set THEN Q := NOT rst; ELSIF clk AND NOT edge THEN Q := D; END_IF; edge := CLK; (* revision history hm 4. aug 2006 rev 1.0 original version hm 27. dec 2007 rev 1.1 changed code for better performance hm 30. oct. 2008 rev 1.2 optimized performance *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF edge triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FF_JKE VAR_INPUT SET : BOOL; J : BOOL; CLK : BOOL; K : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR edge : BOOL; END_VAR (* version 1.2 30. oct. 2008 programmer hugo tested by oscat JK-type flip flop with set, reset and rising clock trigger set and reset are asynchron while the FF is rising edge triggered J=0 and K=0 and CLK >>> no action J=1 and K=0 and CLK >>> output = 1 J=0 and K=1 and CLK >>> output = 0 J=1 and K=1 and CLK >>> toggle output *) (* @END_DECLARATION := '0' *) IF rst OR set THEN Q := NOT rst; ELSIF clk AND NOT edge THEN IF J XOR K THEN Q := J; ELSE Q := K XOR Q; END_IF; END_IF; edge := CLK; (* revision history hm 4. aug 2006 rev 1.0 original version hm 27. dec 2007 rev 1.1 changed code for better performance hm 30. oct. 2008 rev 1.2 optimized performance *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF edge triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FF_RSE VAR_INPUT CS : BOOL; CR : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR es, er : BOOL; END_VAR (* version 1.0 16. jul. 2008 programmer hugo tested by oscat this is a rising edge triggered rs flip flop a rising edge on CS sets Q = TRUE and a rising edge on CR sets Q = FALSE. CR has priority over CS. *) (* @END_DECLARATION := '0' *) IF rst THEN (* asynchronous reset *) Q := FALSE; ELSIF CR AND NOT er THEN (* rising edge on CR *) Q := FALSE; ELSIF CS AND NOT es THEN (* rising edge on CS *) Q := TRUE; END_IF; es := CS; er := CR; (* revision history hm 16. jul. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF edge triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SELECT_8 VAR_INPUT E : BOOL; SET : BOOL; IN : BYTE; UP : BOOL; DN : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Q0, Q1, Q2, Q3, Q4, Q5, Q6, Q7 : BOOL; STATE : INT; END_VAR VAR last_up: BOOL; last_dn : BOOL; END_VAR (* version 1.1 27. feb. 2009 programmer hugo tested BY oscat select_8 selects one of 8 outputs at any time. the outputscan be selected by up or down keys and an independent anable switches all outouts off if set false *) (* @END_DECLARATION := '0' *) IF rst THEN state := 0; ELSIF set THEN state := IN; ELSIF up AND NOT last_up THEN state := inc(state,1,7); ELSIF dn AND NOT last_dn THEN state := inc(state,-1,7); END_IF; last_UP := UP; last_DN := DN; Q0 := FALSE; Q1 := FALSE; Q2 := FALSE; Q3 := FALSE; Q4 := FALSE; Q5 := FALSE; Q6 := FALSE; Q7 := FALSE; IF E THEN CASE state OF 0: Q0 := TRUE; 1: Q1 := TRUE; 2: Q2 := TRUE; 3: Q3 := TRUE; 4: Q4 := TRUE; 5: Q5 := TRUE; 6: Q6 := TRUE; 7: Q7 := TRUE; END_CASE; END_IF; (* revision history hm 16. jan 2008 rev 1.0 original version hm 27. feb. 2009 rev 1.1 renamed input en to e *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF edge triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SHR_4E VAR_INPUT SET : BOOL; D0: BOOL; CLK: BOOL; RST: BOOL; END_VAR VAR_OUTPUT Q0: BOOL; Q1: BOOL; Q2: BOOL; Q3: BOOL; END_VAR VAR trig : R_TRIG; END_VAR (* version 1.1 25. oct. 2008 programmer hugo tested by tobias 4 bit shift register with reset *) (* @END_DECLARATION := '0' *) (* trig.Q signals a rising edge on clk *) trig(clk := clk); IF set OR rst THEN Q0 := NOT rst; Q1 := Q0; Q2 := Q0; Q3 := Q0; ELSIF trig.Q THEN Q3 := Q2; Q2 := Q1; Q1 := Q0; Q0 := D0; END_IF; (* revision history hm 4. aug. 2006 rev 1.0 original version hm 25. oct. 2008 rev 1.1 optimized code *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF edge triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SHR_4UDE VAR_INPUT SET : BOOL; D0: BOOL; D3: BOOL; CLK: BOOL; DN : BOOL; RST: BOOL; END_VAR VAR_OUTPUT Q0: BOOL; Q1: BOOL; Q2: BOOL; Q3: BOOL; END_VAR VAR trig : R_TRIG; END_VAR (* version 1.2 14. mar. 2009 programmer hugo tested by tobias 4 bit shift register with reset up / down direction input *) (* @END_DECLARATION := '0' *) (* trig.Q signals a rising edge on clk *) trig(clk := clk); IF set OR rst THEN Q0 := NOT RST; Q1 := Q0; Q2 := Q0; Q3 := Q0; ELSIF trig.Q THEN IF dn THEN Q0 := Q1; Q1 := Q2; Q2 := Q3; Q3 := D3; ELSE Q3 := Q2; Q2 := Q1; Q1 := Q0; Q0 := D0; END_IF; END_IF; (* revision history hm 4. aug. 2006 rev 1.0 original version hm 25. oct. 2008 rev 1.1 optimized code hm 14. mar. 2009 rev 1.2 removed double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF edge triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SHR_8PLE VAR_INPUT Din : BOOL; Dload : BYTE; CLK: BOOL; UP: BOOL :=1; load : BOOL; RST: BOOL; END_VAR VAR_OUTPUT DOut: BOOL; END_VAR VAR edge : BOOL :=1; register : BYTE; END_VAR (* version 1.0 4 aug 2006 programmer hugo tested by oscat 8 bit shift register with reset and parallel load the register can shift up or down it also has a serial input. the Din input is on Bit0 for up shift and on bit 7 for down shift the Dout output is on Bit7 for up shift and on bit 0 for down shift paralle clock is performed clock synchron while a shift is performed first and then the register is reloaded *) (* @END_DECLARATION := '0' *) (* flankenerkennung clk wird high und edge war high reset ist nicht aktiv und set ist nicht aktiv *) IF CLK AND edge AND NOT rst THEN edge := FALSE; (* flanke wurde erkannt und weitere flankenerkennung wird verhindert bis edge wieder true ist *) (* hier ist der code für das flankenevent *) IF UP THEN (*shift up *) register := SHL(register,1); register.0 := Din; Dout := register.7; ELSE (* shift down *); register := SHR(register,1); register.7 := Din; Dout := register.0; END_IF; IF load THEN (* the byte on Din will be loaded if load = true *) register := Dload; IF up THEN Dout := register.7; ELSE Dout := register.0; END_IF; END_IF; END_IF; IF NOT clk THEN edge := TRUE; END_IF; (* sobald clk wieder low wird warten auf nächste flanke *) IF rst THEN (* wenn reset aktiv dann ausgang rücksetzen *) register := 0; Dout := FALSE; END_IF; END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF edge triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SHR_8UDE VAR_INPUT SET : BOOL; D0: BOOL; D7: BOOL; CLK: BOOL; DN : BOOL; RST: BOOL; END_VAR VAR_OUTPUT Q0: BOOL; Q1: BOOL; Q2: BOOL; Q3: BOOL; Q4: BOOL; Q5: BOOL; Q6: BOOL; Q7: BOOL; END_VAR VAR trig : R_TRIG; END_VAR (* version 1.2 14. mar. 2009 programmer hugo tested by tobias 8 bit shift register with reset *) (* @END_DECLARATION := '0' *) (* trig.Q signals a rising edge on clk *) trig(clk := clk); IF set OR rst THEN Q0 := NOT RST; Q1 := Q0; Q2 := Q0; Q3 := Q0; Q4 := Q0; Q5 := Q0; Q6 := Q0; Q7 := Q0; ELSIF trig.Q THEN IF dn THEN Q0 := Q1; Q1 := Q2; Q2 := Q3; Q3 := Q4; Q4 := Q5; Q5 := Q6; Q6 := Q7; Q7 := D7; ELSE Q7 := Q6; Q6 := Q5; Q5 := Q4; Q4 := Q3; Q3 := Q2; Q2 := Q1; Q1 := Q0; Q0 := D0; END_IF; END_IF; (* revision history hm 4. aug. 2006 rev 1.0 original version hm 25. oct. 2008 rev 1.1 optimized code hm 14. mar. 2009 rev 1.2 removed double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF edge triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK TOGGLE VAR_INPUT CLK : BOOL; rst : BOOL; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR edge : BOOL; END_VAR (* version 1.1 30. oct. 2008 programmer hugo tested by oscat toggle flip flop the output changes state with every rising edge of clk. *) (* @END_DECLARATION := '0' *) IF rst THEN q := 0; ELSIF clk AND NOT edge THEN Q := NOT Q; END_IF; edge := clk; (* revision history hm 13.9.2007 rev 1.0 original version hm 30. oct. 2008 rev 1.1 deleted unnecessary init *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF pulse triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK LTCH VAR_INPUT D : BOOL; L : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Q : BOOL; END_VAR (* version 1.2 30. oct. 2008 programmer hugo tested by oscat Transparent Latch with asynchronous reset *) (* @END_DECLARATION := '0' *) (* as long as L is true, the latch is transparent and and change of D is transferred to Q *) (* of course only when there is no asynchronous reset *) IF rst THEN (* if asynchronous reset then Q=0 *) Q := FALSE; ELSIF L THEN (* latch is transparent *) Q := D; END_IF; (* revision history hm 4. aug 2006 rev 1.0 original version hm 27. dec 2007 rev 1.1 changed code for better performance hm 30. oct. 2008 rev 1.2 deleted unnecessary init with 0 *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF pulse triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK LTCH_4 VAR_INPUT D0 : BOOL; D1 : BOOL; D2 : BOOL; D3 : BOOL; L : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Q0 : BOOL; Q1 : BOOL; Q2 : BOOL; Q3 : BOOL; END_VAR (* version 1.3 14. mar. 2009 programmer hugo tested by oscat Quad Transparent Latch with asynchronous reset *) (* @END_DECLARATION := '0' *) (* as long as L is true, the latch is transparent and and change of D is transferred to Q *) (* of course only when there is no asynchronous reset *) IF rst THEN (* if asynchronous reset then Q=0 *) Q0 := FALSE; Q1 := FALSE; Q2 := FALSE; Q3 := FALSE; ELSIF L THEN (* latch is transparent *) Q0 := D0; Q1 := D1; Q2 := D2; Q3 := D3; END_IF; (* revision history hm 4. aug 2006 rev 1.0 original version hm 27. dec 2007 rev 1.1 changed code for better performance hm 30. oct. 2008 rev 1.2 deleted unnecessary init with 0 hm 14. mar. 2009 rev 1.3 removed double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/FF pulse triggered' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK STORE_8 VAR_INPUT Set : BOOL; D0,D1, D2, D3, D4, D5, D6, D7 : BOOL; Clr : BOOL; Rst : BOOL; END_VAR VAR_OUTPUT Q0, Q1, Q2, Q3, Q4, Q5, Q6, Q7 : BOOL; END_VAR VAR edge: BOOL; END_VAR (* version 1.2 14. mar. 2009 programmer hugo tested by oscat Store_4 stores up to 4 boolean inputs until a reset is clearing the outputs. the respective output is set with a true at the respective input and stays true until a reset is generated. a set input is used to set all outputs true at the same time, independent of their inputs. a clear input is used to reset only the lowest priority of the outputs one at a time. the lowest priority is q3. *) (* @END_DECLARATION := '0' *) IF rst OR set THEN q0 := NOT rst; q1 := q0; q2 := q0; q3 := q0; q4 := q0; q5 := q0; q6 := q0; q7 := q0; ELSE IF D0 THEN Q0 := TRUE; END_IF; IF D1 THEN Q1 := TRUE; END_IF; IF D2 THEN Q2 := TRUE; END_IF; IF D3 THEN Q3 := TRUE; END_IF; IF D4 THEN Q4 := TRUE; END_IF; IF D5 THEN Q5 := TRUE; END_IF; IF D6 THEN Q6 := TRUE; END_IF; IF D7 THEN Q7 := TRUE; END_IF; IF clr AND NOT edge THEN IF q0 THEN q0 := FALSE; ELSIF q1 THEN q1 := FALSE; ELSIF q2 THEN q2 := FALSE; ELSIF q3 THEN q3 := FALSE; ELSIF q4 THEN q4 := FALSE; ELSIF q5 THEN q5 := FALSE; ELSIF q6 THEN q6 := FALSE; ELSE q7 := FALSE; END_IF; END_IF; edge := clr; END_IF; (* revision history hm 25.12.2007 rev 1.0 original version hm 30. oct. 2008 rev 1.1 optimized performance hm 14. mar. 2009 rev 1.2 removed double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BCDC_TO_INT : INT VAR_INPUT IN : BYTE; END_VAR (* version 1.1 30. jun. 2008 programmer hugo tested by oscat this function converts a two digit bcd number into an integer. *) (* @END_DECLARATION := '0' *) BCDC_TO_INT := (in AND 16#0F) + (SHR(in,4) * 10); (* revision history hm 13.12.2007 rev 1.0 original version hm 30.6.2008 rev 1.1 changed name BCD_TO_INT to BCDC_TO_INT to avoid collision with util.lib *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BIT_COUNT : INT VAR_INPUT IN : DWORD; END_VAR (* version 1.1 10 sep 2007 programmer hugo tested by tobias BIT_COUNT counts the amount True of bits in a dword. for exabple: bit_count(3) returns 2 because two bits (bits 0 and 1) are true and all others are false. *) (* @END_DECLARATION := '0' *) WHILE in > 0 DO IF in.0 THEN Bit_Count := Bit_Count + 1; END_IF; in := SHR(in,1); END_WHILE; (* revision history 5.7.2007 rev 1.0 original version 10.9.2007 rev 1.1 hm changed algorithm for better performace the execution time has reduced by a factor of 5 deleted unused variable temp *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BIT_LOAD_B : BYTE VAR_INPUT IN : BYTE; VAL : BOOL; POS : INT; END_VAR VAR CONSTANT dat : BYTE := 1; END_VAR (* version 1.1 16. mar 2008 programmer hugo tested by tobias this function loads a bit into a byte at position pos. *) (* @END_DECLARATION := '0' *) IF VAL THEN BIT_LOAD_B := in OR SHL(dat,pos); ELSE BIT_LOAD_B := in AND (NOT SHL(dat,pos)); END_IF; (* revision history hm 29. feb 2008 rev 1.0 original version hm 16. mar 2008 rev 1.1 change input bit to val for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BIT_LOAD_B2 : BYTE VAR_INPUT I : BYTE; D : BOOL; P : INT; N : INT; END_VAR (* version 1.0 18. oct. 2008 programmer hugo tested by tobias this function loads N bits of D at pos P in Byte I *) (* @END_DECLARATION := '0' *) IF D THEN BIT_LOAD_B2 := ROL(SHR(BYTE#255, 8 - N) OR ROR(i, P), P); ELSE BIT_LOAD_B2 := ROL(SHL(BYTE#255, N) AND ROR(I, P), P); END_IF; (* revision history hm 18. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BIT_LOAD_DW : DWORD VAR_INPUT IN : DWORD; VAL : BOOL; POS : INT; END_VAR VAR CONSTANT dat : DWORD := 1; END_VAR (* version 1.1 16. mar 2008 programmer hugo tested by tobias this function loads a bit into a DWord at position pos. *) (* @END_DECLARATION := '0' *) IF val THEN BIT_LOAD_DW := in OR SHL(dat,pos); ELSE BIT_LOAD_DW := in AND (NOT SHL(dat,pos)); END_IF; (* revision history hm 29. feb 2008 rev 1.0 original version hm 16. mar 2008 rev 1.1 change input bit to val for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BIT_LOAD_DW2 : DWORD VAR_INPUT I : DWORD; D : BOOL; P : INT; N : INT; END_VAR (* version 1.0 18. oct. 2008 programmer hugo tested by tobias this function loads N bits of D at pos P in DWORD I *) (* @END_DECLARATION := '0' *) IF D THEN BIT_LOAD_DW2 := ROL(SHR(DWORD#4294967295, 32 - N) OR ROR(i, P), P); ELSE BIT_LOAD_DW2 := ROL(SHL(DWORD#4294967295, N) AND ROR(I, P), P); END_IF; (* revision history hm 18. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BIT_LOAD_W : WORD VAR_INPUT IN : WORD; VAL : BOOL; POS : INT; END_VAR VAR CONSTANT dat : WORD := 1; END_VAR (* version 1.1 16. mar 2008 programmer hugo tested by tobias this function loads a bit into a Word at position pos. *) (* @END_DECLARATION := '0' *) IF val THEN BIT_LOAD_W := in OR SHL(dat,pos); ELSE BIT_LOAD_W := in AND (NOT SHL(dat,pos)); END_IF; (* revision history hm 29. feb 2008 rev 1.0 original version hm 16. mar 2008 rev 1.1 change input bit to val for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BIT_LOAD_W2 : WORD VAR_INPUT I : WORD; D : BOOL; P : INT; N : INT; END_VAR (* version 1.0 18. oct. 2008 programmer hugo tested by tobias this function loads N bits of D at pos P in WORD I *) (* @END_DECLARATION := '0' *) IF D THEN BIT_LOAD_W2 := ROL(SHR(WORD#65535, 16 - N) OR ROR(i, P), P); ELSE BIT_LOAD_W2 := ROL(SHL(WORD#65535, N) AND ROR(I, P), P); END_IF; (* revision history hm 18. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BIT_OF_DWORD : BOOL VAR_INPUT in : DWORD; N : INT; END_VAR (* version 1.2 6. jun. 2008 programmer hugo tested by tobias this function extracts a single bit from the nth position from right (right is lowest bit) the lowest Bit (Bit0 from in) is selected with N=0. *) (* @END_DECLARATION := '0' *) BIT_OF_DWORD := (SHR(in,N) AND 16#00000001) > 0; (* old code used before rev 1.1 temp := SHR(in,n); Bit_of_Dword := temp.0; *) (* revision history hm 4. aug. 2006 rev 1.0 original version hm 29. feb 2008 rev 1.1 improved performance hm 6. jun. 2008 rev 1.2 changes type of input N from byte to int *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BIT_TOGGLE_B : BYTE VAR_INPUT IN : BYTE; POS : INT; END_VAR (* version 1.0 18. oct. 2008 programmer hugo tested by tobias this function toggles a bit of a BYTE at position pos. *) (* @END_DECLARATION := '0' *) BIT_TOGGLE_B := SHL(BYTE#1, POS) XOR IN; (* revision history hm 18. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BIT_TOGGLE_DW : DWORD VAR_INPUT IN : DWORD; POS : INT; END_VAR (* version 1.0 18. oct. 2008 programmer hugo tested by tobias this function toggles a bit of a WORD at position pos. *) (* @END_DECLARATION := '0' *) BIT_TOGGLE_DW := SHL(DWORD#1, POS) XOR IN; (* revision history hm 18. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BIT_TOGGLE_W : WORD VAR_INPUT IN : WORD; POS : INT; END_VAR (* version 1.0 18. oct. 2008 programmer hugo tested by tobias this function toggles a bit of a WORD at position pos. *) (* @END_DECLARATION := '0' *) BIT_TOGGLE_W := SHL(WORD#1, POS) XOR IN; (* revision history hm 18. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BYTE_OF_BIT : BYTE VAR_INPUT B0:BOOL; B1:BOOL; B2:BOOL; B3:BOOL; B4:BOOL; B5:BOOL; B6:BOOL; B7:BOOL; END_VAR (* version 1.1 18. feb 2006 programmer hugo tested by tobias this function creates a byte from 8 individual bits *) (* @END_DECLARATION := '0' *) Byte_of_bit := SHL(SHL(SHL(SHL(SHL(SHL(SHL(BOOL_TO_BYTE(B7),1) OR BOOL_TO_BYTE(B6),1) OR BOOL_TO_BYTE(B5),1) OR BOOL_TO_BYTE(B4),1) OR BOOL_TO_BYTE(B3),1) OR BOOL_TO_BYTE(B2),1) OR BOOL_TO_BYTE(B1),1) OR BOOL_TO_BYTE(B0); (* revision history hm 4. aug 2006 rev 1.0 original version hm 18. feb. 2008 rev 1.1 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BYTE_OF_DWORD : BYTE VAR_INPUT in : DWORD; N : BYTE; END_VAR (* version 1.2 30. oct. 2008 programmer hugo tested by oscat this function extracts a single byte from the nth position from right (right is lowest byte) the lower byte (starting with Bit0 from in) is selected with N=0. *) (* @END_DECLARATION := '0' *) BYTE_OF_DWORD := DWORD_TO_BYTE(SHR(in,SHL(n,3))); (* revision history hm 17. jan 2007 rev 1.0 original version hm 2. jan 2008 rev 1.1 improved performance hm 30. oct. 2008 rev 1.2 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK BYTE_TO_BITS VAR_INPUT IN: BYTE; END_VAR VAR_OUTPUT B0: BOOL; B1: BOOL; B2: BOOL; B3: BOOL; B4: BOOL; B5: BOOL; B6: BOOL; B7: BOOL; END_VAR (* version 1.1 16. mar. 2008 programmer hugo tested by tobias this Function Block extracts the 8 Bits from a byte *) (* @END_DECLARATION := '0' *) B0 := IN.0; B1 := IN.1; B2 := IN.2; B3 := IN.3; B4 := IN.4; B5 := IN.5; B6 := IN.6; B7 := IN.7; (* revision history hm 4. aug 2006 rev 1.0 original version hm 16. mar 2008 rev 1.1 renamed from byte_to_bit to byte_to_bits for compatibility reasons *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BYTE_TO_GRAY : BYTE VAR_INPUT IN : BYTE; END_VAR (* version 1.0 9. nov. 2009 programmer hugo tested by oscat this function converts a binary to gray code *) (* @END_DECLARATION := '0' *) BYTE_TO_GRAY := IN XOR SHR(IN,1); (* revision history hm 9. nov. 2009 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CHECK_PARITY : BOOL VAR_INPUT in : DWORD; p : BOOL; END_VAR (* version 1.3 18 feb 2008 programmer hugo tested by tobias this function checks for an even partity for a dword and partity bit. *) (* @END_DECLARATION := '0' *) CHECK_PARITY := NOT p; WHILE in > 0 DO CHECK_PARITY := CHECK_PARITY XOR in.0 XOR in.1 XOR in.2 XOR in.3; in := SHR(in,4); END_WHILE; (* code before rev 1.2 WHILE in > 0 DO IF in.0 THEN cnt := cnt + 1; END_IF; in := SHR(in,1); END_WHILE; check_parity := even(cnt) XOR P; *) (* revision history rev 1.0 HM 1.oct.2006 rev 1.1 hm 10.sep.2007 changed algorithm to improove performance rev 1.2 hm 8 dec 2007 changed algorithm to improove performance rev 1.3 hm 18. feb 2008 changed algorithm to improove performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CHK_REAL : BYTE VAR_INPUT X : REAL; END_VAR VAR pt : POINTER TO DWORD; tmp : DWORD; END_VAR (* version 1.0 20. jan. 2011 programmer hugo tested by tobias this function checks a floating point variable of type real (IEEE754-32Bits) for NAN and infinity RETURN values: #0 = normal value, #20 = +infinity, #40 = -infinty, #80 = NAN *) (* @END_DECLARATION := '0' *) pt := ADR(X); (* move real to dword, real_to_dword does not work becasze it treats dword as a number on many systems *) tmp := ROL(pt^,1); (* rotate left foir easy checking, sign will be at lease significant digit *) IF tmp < 16#FF000000 THEN CHK_REAL := 16#00; (* normalized and denormalized numbers *) ELSIF tmp = 16#FF000000 THEN CHK_REAL := 16#20; (* X is +infinity see IEEE754 *) ELSIF tmp = 16#FF000001 THEN CHK_REAL := 16#40; (* X is -infinity see IEEE754 *) ELSE CHK_REAL := 16#80; (* X is NAN see IEEE754 *) END_IF; (* revision history hm 20. jan. 2011 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK DEC_2 VAR_INPUT D: BOOL; A: BOOL; END_VAR VAR_OUTPUT Q0: BOOL; Q1: BOOL; END_VAR (* version 1.1 3 Mar 2007 programmer hugo tested by tobias a bit input will be decoded to the two outputs Q0 or Q1 dependent on the value of A A=0 decodes to Q0 and A=1 decodes to Q1 *) (* @END_DECLARATION := '0' *) Q0 := D AND NOT A; Q1 := D AND A; (* revision history hm 3.3.2007 rev 1.1 rewritten in ST for better compatibility *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK DEC_4 VAR_INPUT D: BOOL; A0: BOOL; A1: BOOL; END_VAR VAR_OUTPUT Q0: BOOL; Q1: BOOL; Q2: BOOL; Q3: BOOL; END_VAR (* version 1.1 3 Mar 2007 programmer hugo tested by tobias a bit input will be decoded to one of the 4 outputs dependent on the Adress on A0 and A1 executioin TIME on wago 750-841 = 9us *) (* @END_DECLARATION := '0' *) Q0 := D AND NOT A0 AND NOT A1; Q1 := D AND A0 AND NOT A1; Q2 := D AND NOT A0 AND A1; Q3 := D AND A0 AND A1; (* revision history hm 3.3.2007 rev 1.1 rewritten in ST for better compatibility *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK DEC_8 VAR_INPUT D: BOOL; A0: BOOL; A1: BOOL; A2: BOOL; END_VAR VAR_OUTPUT Q0: BOOL; Q1: BOOL; Q2: BOOL; Q3: BOOL; Q4: BOOL; Q5: BOOL; Q6: BOOL; Q7: BOOL; END_VAR VAR X : INT; END_VAR (* version 1.3 28. mar. 2009 programmer hugo tested by oscat a bit input will be decoded to one of the 8 outputs dependent on the Adress on A0, A1 and A2 *) (* @END_DECLARATION := '0' *) X.0 := A0; X.1 := A1; X.2 := A2; Q0 := FALSE; Q1 := FALSE; Q2 := FALSE; Q3 := FALSE; Q4 := FALSE; Q5 := FALSE; Q6 := FALSE; Q7 := FALSE; CASE X OF 0 : Q0 := D; 1 : Q1 := D; 2 : Q2 := D; 3 : Q3 := D; 4 : Q4 := D; 5 : Q5 := D; 6 : Q6 := D; 7 : Q7 := D; END_CASE; (* revision history hm 3. mar. 2007 rev 1.1 rewritten in ST for better compatibility hm 26. oct. 2008 rev 1.2 code optimized hm 28. mar. 2009 rev 1.3 replaced multiple assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DW_TO_REAL : REAL VAR_INPUT X : DWORD; END_VAR VAR pt : POINTER TO REAL; END_VAR (* version 1.0 18. apr. 2008 programmer hugo tested by tobias this function converts a DWORD to REAL in a bitwise manner. *) (* @END_DECLARATION := '0' *) pt := ADR(X); DW_TO_REAL := pt^; (* revision history hm 18. apr. 2008 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DWORD_OF_BYTE : DWORD VAR_INPUT B3 : BYTE; B2 : BYTE; B1 : BYTE; B0 : BYTE; END_VAR (* version 1.3 18. jul. 2009 programmer hugo tested by tobias this function creates a Dword from 4 individual bytes *) (* @END_DECLARATION := '0' *) DWORD_OF_BYTE := SHL(SHL(SHL(BYTE_TO_DWORD(B3),8) OR BYTE_TO_DWORD(B2),8) OR BYTE_TO_DWORD(B1),8) OR BYTE_TO_DWORD(B0); (* revision history hm 24. jan 2007 rev 1.0 original version hm 2. jan 2008 rev 1.1 inproved performance hm 23. apr. 2008 rev 1.2 reverse order of inputs to be more logical hm 18. jul. 2009 rev 1.3 added type conversions for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DWORD_OF_WORD : DWORD VAR_INPUT W1 : WORD; W0 : WORD; END_VAR (* version 1.0 18. jul. 2009 programmer hugo tested by tobias this function creates a Dword from 2 individual Words *) (* @END_DECLARATION := '0' *) DWORD_OF_WORD := SHL(WORD_TO_DWORD(W1),16) OR WORD_TO_DWORD(W0); (* revision history hm 18. jul. 2009 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION GRAY_TO_BYTE : BYTE VAR_INPUT IN : BYTE; END_VAR (* version 1.0 9. nov. 2009 programmer hugo tested by oscat this function converts a gray code into binary *) (* @END_DECLARATION := '0' *) GRAY_TO_BYTE := SHR(IN,4) XOR IN; GRAY_TO_BYTE := SHR(GRAY_TO_BYTE,2) XOR GRAY_TO_BYTE; GRAY_TO_BYTE := SHR(GRAY_TO_BYTE,1) XOR GRAY_TO_BYTE; (* revision history hm 9. nov. 2009 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION INT_TO_BCDC : BYTE VAR_INPUT IN : INT; END_VAR (* version 1.1 30. jun. 2008 programmer hugo tested by tobias this function converts an integer into a two digit bcd number. *) (* @END_DECLARATION := '0' *) INT_TO_BCDC := SHL(INT_TO_BYTE(IN / INT#10),4) OR INT_TO_BYTE(in MOD INT#10); (* revision history hm 13.12.2007 original version hm 30.6.2008 rev 1.1 changed name INT_TO_BCD to INT_TO_BCDC to avoid collision with util.lib corrected error in code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MUX_2 : BOOL VAR_INPUT D0: BOOL; D1: BOOL; A0: BOOL; END_VAR (* version 1.2 16. oct. 2008 programmer hugo tested BY oscat dual input multiplexer depending on the value of A0, D0 or D1 will be switched to the Output executioin TIME on wago 750-841 = 7us *) (* @END_DECLARATION := '0' *) MUX_2 := SEL(A0, D0, D1); (* revision history: hm 5.10.2006 rev 1.1 changed to ST Langage for better portability. hm 16. oct. 2008 rev 1.2 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MUX_4 : BOOL VAR_INPUT D0: BOOL; D1: BOOL; D2: BOOL; D3: BOOL; A0: BOOL; A1: BOOL; END_VAR (* version 1.2 16. oct. 2008 programmer hugo tested BY oscat quad input multiplexer depending on the value of A0 and A1, one of the 4 inputs will be switched to the Output *) (* @END_DECLARATION := '0' *) IF A1 THEN MUX_4 := SEL(A0, D2, D3); ELSE MUX_4 := SEL(A0, D0, D1); END_IF; (* revision history: hm 5.10.2006 rev 1.1 rewritten to ST for better portability hm 16. oct. 2008 rev 1.2 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION PARITY : BOOL VAR_INPUT in : DWORD; END_VAR (* version 1.3 18 feb 2008 programmer hugo tested BY hans this function calculates the even parity of an input Dword the output will be true if the amount of high bits are an odd number. *) (* @END_DECLARATION := '0' *) WHILE in > 0 DO PARITY := PARITY XOR in.0 XOR in.1 XOR in.2 XOR in.3; in := SHR(in,4); END_WHILE; (* code before rev 1.2 WHILE in > 0 DO IF in.0 THEN cnt := cnt +1; END_IF; in := SHR(in,1); END_WHILE; IF (cnt MOD 2) = 1 THEN parity := TRUE; ELSE parity := FALSE; END_IF; *) (* revision history rev 1.0 hm 1 sep 2006 original version rev 1.1 hm 10.9.2007 changed algorithm to improve performance rev 1.2 hm 8 dec 2007 changed algorithm to improve performance rev 1.3 hm 18 feb 2008 changed algorithm to improve performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION REAL_TO_DW : DWORD VAR_INPUT X : REAL; END_VAR VAR pt : POINTER TO DWORD; END_VAR (* version 1.0 18. apr. 2008 programmer hugo tested by tobias this function converts a 32 Bit Real to a dword in a bitwise manner. *) (* @END_DECLARATION := '0' *) pt := ADR(X); REAL_TO_DW := pt^; (* revision history hm 18. apr. 2008 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION REFLECT : DWORD VAR_INPUT D : DWORD; L : INT; END_VAR VAR i : INT; END_VAR (* version 1.0 16. jan 2011 programmer hugo tested BY tobias This function reverses the specified amount of bits from bit 0 to bit n within a dword while L specifies the amount of Bits to be reflected. *) (* @END_DECLARATION := '0' *) REFLECT := 0; FOR i := 1 TO L DO REFLECT := SHL(REFLECT, 1) OR BOOL_TO_DWORD(D.0); D := SHR(D, 1); END_FOR; REFLECT := REFLECT OR SHL(D, L); (* revision history hm 16. jan 2011 new module *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION REVERSE : BYTE VAR_INPUT IN : BYTE; END_VAR (* version 1.1 18. feb 2008 programmer hugo tested BY tobias This function reverses the bits of a byte so that after execution bit 7 is at bit 0 location and so forth. *) (* @END_DECLARATION := '0' *) REVERSE := SHL(in,7) OR SHR(in,7) OR (ROR(in,3) AND 2#01000100) OR (ROL(in,3) AND 2#00100010) OR (SHL(in,1) AND 2#00010000) OR (SHR(in,1) AND 2#00001000); (* revision history hm 9.oct 2007 rev 1.0 original version hm 18. feb 2008 rev 1.1 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SHL1 : DWORD VAR_INPUT IN : DWORD; N : INT; END_VAR VAR CONSTANT temp : DWORD := 16#FFFF_FFFF; END_VAR (* version 1.1 27 dec 2007 programmer hugo tested by tobias SHL1 shifts N bits to the left filling the new bits with 1 *) (* @END_DECLARATION := '0' *) SHL1 := SHR(temp,32-N) OR SHL(IN,N); (* revision history hm 14.9.2007 rev 1.0 original version hm 27. dec 2007 rev 1.1 changed code for better performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SHR1 : DWORD VAR_INPUT IN : DWORD; N : INT; END_VAR VAR CONSTANT temp : DWORD := 16#FFFF_FFFF; END_VAR (* version 1.1 27 dec 2007 programmer hugo tested by tobias SHR1 shifts N bits to the right filling the new bits with 1 *) (* @END_DECLARATION := '0' *) SHR1 := SHL(temp,32-N) OR SHR(IN,N); (* revision history hm 14.9.2007 rev 1.0 original version hm 27. dec 2007 rev 1.1 changed code for better performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SWAP_BYTE : WORD VAR_INPUT IN : WORD; END_VAR (* version 1.0 4 feb 2008 programmer hugo tested by tobias This function swaps the high and low byte of the word in. *) (* @END_DECLARATION := '0' *) SWAP_BYTE := ROL(in,8); (* revision history hm 4. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SWAP_BYTE2 : DWORD VAR_INPUT IN : DWORD; END_VAR (* version 1.0 4 feb 2008 programmer hugo tested by tobias This function reverses the byte order in the dword. *) (* @END_DECLARATION := '0' *) Swap_Byte2 := (ROR(in,8) AND 16#FF00FF00) OR (ROL(in,8) AND 16#00FF00FF); (* revision history hm 4. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION WORD_OF_BYTE : WORD VAR_INPUT B1 : BYTE; B0 : BYTE; END_VAR (* version 1.4 18. jul. 2009 programmer hugo tested by tobias this function creates a word from 2 individual bytes *) (* @END_DECLARATION := '0' *) WORD_OF_BYTE := SHL(BYTE_TO_WORD(B1),8) OR BYTE_TO_WORD(B0); (* revision history hm 24. jan 2007 rev 1.0 original version hm 2. jan 2008 rev 1.1 improved performance hm 19. feb 2008 rev 1.2 replaced and with or for better compatibility hm 23. apr. 2008 rev 1.3 reverse order of inputs to be more logical hm 18. jul. 2009 rev 1.4 added type conversions for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/gate logic' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION WORD_OF_DWORD : WORD VAR_INPUT in : DWORD; N : BYTE; END_VAR (* version 1.2 30. oct. 2008 programmer hugo tested by oscat this function extracts a single word from the nth position from right (right is lowest byte) the lower word (starting with Bit0 from in) is selected with N=0. *) (* @END_DECLARATION := '0' *) WORD_OF_DWORD := DWORD_TO_WORD(SHR(in,SHL(n,4))); (* revision history hm 17. jan 2007 rev 1.0 original version hm 2. jan 2008 rev 1.1 improved performance hm 30. oct. 2008 rev 1.2 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK A_TRIG VAR_INPUT IN : REAL; RES : REAL; END_VAR VAR_OUTPUT Q : BOOL; D : REAL; END_VAR VAR last_in : REAL; END_VAR (* version 1.1 25. oct. 2008 programmer hugo tested by oscat this block is similar to the IEC Standard R_trig and F_trig but it monitors a REAL for change. if the valiue on IN changes more then D from the last value it will generate trigger and display the difference in output D. the trigger will only be active for one cycle. *) (* @END_DECLARATION := '0' *) D := IN - LAST_IN; Q := ABS(D) > res; IF Q THEN last_in := IN; END_IF; D := IN - LAST_IN; (* revision history hm 16. jul. 2008 rev 1.0 original version released hm 25. oct. 2008 rev 1.1 code optimization a_trig now also works for res = 0 *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK B_TRIG VAR_INPUT CLK : BOOL; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR edge : BOOL; END_VAR (* version 1.0 4. aug. 2006 programmer hugo tested by tobias this block is similar to R_trig and F_trig but it generates a pulse on rising and falling edge. *) (* @END_DECLARATION := '0' *) Q := clk XOR edge; edge := CLK; (* revision history hm 4. aug. 2006 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CLICK_CNT VAR_INPUT IN : BOOL; N : INT; TC : TIME; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR tx : TP; edge: BOOL; cnt: INT := -1; END_VAR (* version 1.0 16. jul. 2008 programmer hugo tested by oscat this Module decodes a specified number of clicks. the output trig is high for one cycle if N clicks are present within a specified time TC. *) (* @END_DECLARATION := '0' *) (* Q shall only be active for one cycle only *) Q := FALSE; IF in AND NOT edge AND NOT tx.q THEN (* a rising edge on in sets the counter to 0 *) cnt := 0; ELSIF tx.Q AND NOT IN AND edge THEN (* count falling edges when tp.q is true *) cnt := cnt + 1; ELSIF NOT tx.Q THEN Q := cnt = N; cnt := -1; END_IF; (* remember the status of IN *) edge := IN; tx(in := IN, pt := TC); (* revision history hm 16. jul. 2008 rev 1.0 original version released *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CLICK_DEC VAR_INPUT IN : BOOL; TC : TIME; END_VAR VAR_OUTPUT Q0, Q1, Q2, Q3 : BOOL; END_VAR VAR tx : TP; edge: BOOL; cnt: INT := -1; END_VAR (* version 1.1 14. mar. 2009 programmer hugo tested by oscat this Module detects a rising edge on In and decodes the amount of falling edges (Pulses) within a specified time TC. the outputs Q0..Q3 will go true for one cycle and the amount of clicks within a specified time TC. if only a rising egde is detected within TC Q0 will respond, Q1 for 1 pulse and so on.. *) (* @END_DECLARATION := '0' *) (* Q shall only be active for one cycle only *) IF in = FALSE THEN Q0 := FALSE; Q1 := FALSE; Q2:= FALSE; Q3 := FALSE; END_IF; IF in AND NOT edge AND NOT tx.q THEN (* a rising edge on in sets the counter to 0 *) cnt := 0; ELSIF tx.Q AND NOT IN AND edge THEN (* count falling edges when tp.q is true *) cnt := cnt + 1; ELSIF NOT tx.Q THEN CASE cnt OF 0 : Q0 := TRUE; 1 : Q1 := TRUE; 2 : Q2 := TRUE; 3 : Q3 := TRUE; END_CASE; cnt := -1; END_IF; (* remember the status of IN *) edge := IN; tx(in := IN, pt := TC); (* revision history hm 17. jul. 2008 rev 1.0 original version released hm 14. mar. 2009 rev 1.1 removed double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CLK_DIV VAR_INPUT clk : BOOL; rst: BOOL; END_VAR VAR_OUTPUT Q0 : BOOL; Q1 : BOOL; Q2 : BOOL; Q3 : BOOL; Q4 : BOOL; Q5 : BOOL; Q6 : BOOL; Q7 : BOOL; END_VAR VAR cnt: BYTE; END_VAR (* version 1.1 2 jan 2008 programmer hugo tested by tobias this is a clock divider each output divides the signal by 2 Q0 = clk / 2 , Q1 = Q0 / 2 and so on. the outputs have a 50% duty cycle each. *) (* @END_DECLARATION := '0' *) IF rst THEN cnt:= 0; Q0 := 0; Q1 := 0; Q2 := 0; Q3 := 0; Q4 := 0; Q5 := 0; Q6 := 0; Q7 := 0; ELSIF clk THEN cnt:= cnt +1; Q0 := cnt.0; Q1 := cnt.1; Q2 := cnt.2; Q3 := cnt.3; Q4 := cnt.4; Q5 := cnt.5; Q6 := cnt.6; Q7 := cnt.7; END_IF; (* revision history hm 4. aug. 2006 rev 1.0 original version hm 2. jan 2008 rev 1.1 improved performance *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CLK_N VAR_INPUT N : INT; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR edge : BOOL; stime: DWORD; clk: BOOL; END_VAR (* version 1.0 17 sep 2007 programmer hugo tested by tobias clk_N uses the internal sps time to generate one pulse every N ms every pulse is only valid for one cycle so that a edge trigger is not necessary clk_N generates pulses depending on the accuracy of the system clock. The input N controlls the period time of the clock. N=0 equals 1ms, N=1 equals 2ms, N=2 equals 4ms, N=3 equals 8ms .... be careful, 1ms clocks will only work on very powerful sps cpu's *) (* @END_DECLARATION := '0' *) stime := SHR(T_PLC_MS(),N); clk := stime.0; Q := clk XOR edge; edge := CLK; (* revision history hm 16. dec 2007 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CLK_PRG VAR_INPUT PT : TIME := t#10ms; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR init : BOOL; last : TIME; tx: TIME; END_VAR (* version 1.3 25. oct. 2008 programmer hugo tested by tobias clk_prg uses the internal sps time to generate a clock with programmable period time. the first cycle after start is a clk pulse and then depending on the programmed period time a delay. every pulse is only valid for one cycle. the accuracy of clk_prg is depending on the accuracy of the system clock. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := DWORD_TO_TIME(T_PLC_MS()); (* initialize on startup *) IF NOT init THEN init := TRUE; last := tx - pt; END_IF; (* generate output pulse when next_pulse is reached *) Q := tx - last >= pt; IF Q THEN last := tx; END_IF; (* revision hiostory hm 25 feb 2007 rev 1.1 rewritten code for higher performance pt can now be changed during runtime hm 17. sep 2007 rev 1.2 replaced time() with t_plc_ms() for compatibility reasons hm 25. oct. 2008 rev 1.3 optimized code *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CLK_PULSE VAR_INPUT PT : TIME; N : INT; RST : BOOL ; END_VAR VAR_OUTPUT Q : BOOL; CNT : INT; RUN : BOOL; END_VAR VAR tx, tn: DWORD; init: BOOL; END_VAR (* version 1.2 16 feb 2011 programmer hugo tested by oscat clk_pulse uses the internal sps time to generate a clock with programmable period time. the period time is defined for 10ms .. 65s. pulse generation is continuous if N = 0 and for n pulses otherwise. the first cycle after start is a clk pulse and then depending on the programmed period time a delay. every pulse is only valid for one cycle so that a edge trigger is not necessary clk_prg depending on the accuracy of the system clock. *) (* @END_DECLARATION := '0' *) tx := T_PLC_MS(); (* read system *) Q := FALSE; (* reset Q we generate pulses only for one cycle *) RUN := CNT < N; IF NOT init OR RST THEN init := TRUE; CNT := 0; tn := tx - TIME_TO_DWORD(PT); RUN := FALSE; ELSIF (cnt < N OR N = 0) AND tx - tn >= TIME_TO_DWORD(PT) THEN (* generate a pulse *) CNT := CNT + 1; Q := TRUE; tn := tn + TIME_TO_DWORD(PT); END_IF; (* revision history hm 4. aug 2006 rev 1.0 original version hm 17. sep 2007 rev 1.1 replaced time() with T_PLC_S() for compatblity reasons hm 16. feb. 2011 rev 1.2 fixed an error when timer overflows *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CYCLE_4 VAR_INPUT E : BOOL := TRUE; T0, T1, T2, T3 : TIME; S0 : BOOL; SX : INT; SL : BOOL; END_VAR VAR_OUTPUT STATE : INT; END_VAR VAR tx : TIME; last : TIME; init: BOOL; END_VAR (* version 1.0 programmer hugo tested by oscat *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := DWORD_TO_TIME(T_PLC_MS()); (* init on first cycle *) IF NOT init THEN init := TRUE; last := tx; END_IF; IF E THEN IF SL THEN (* when sx > 0 then the state sx is forced to start *) state:= LIMIT(0,SX,3); last := tx; (* this is to avoid to reset sx from the calling programm it does work fine on codesys but i am not sure about other systems, because we are writing to an input *) SL := FALSE; ELSE CASE state OF 0 : (* wait for T0 and switch to next cycle *) IF tx - last >= T0 THEN state := 1; last := tx; END_IF; 1 : (* wait for T1 over 1st cycle *) IF tx - last >= T1 THEN state := 2; last := tx; END_IF; 2 : (* wait for T1 over 1st cycle *) IF tx - last >= T2 THEN state := 3; last := tx; END_IF; 3 : (* wait for T2 over 2nd cycle *) IF tx - last >= T3 THEN IF S0 THEN State := 0; END_IF; (* if S0 is false, the sequence stops at state 3 *) last := tx; END_IF; END_CASE; END_IF; ELSE state := 0; last := tx; END_IF; (* hm 3. nov. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK D_TRIG VAR_INPUT in : DWORD; END_VAR VAR_OUTPUT Q : BOOL; X : DWORD; END_VAR VAR last_in : DWORD; END_VAR (* version 1.1 19. feb 2008 programmer hugo tested by tobias this block is similar to the IEC Standard R_trig and F_trig but it monitors a DWORD, WORD or Byte Variable instead and generated an Output Pulse for one cycle only when the input has changed. an additional output x (Dword) will state the chage of the input. Example: the input has chaged from 0001 to 0010 then the output x will be 2. *) (* @END_DECLARATION := '0' *) Q := in <> last_in; X := in - last_in; last_in := in; (* revision history hm 4.09.2007 rev 1.0 original version released hm 19. feb. 2008 rev 1.1 performance improvement *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK GEN_BIT VAR_INPUT in0 : DWORD; in1 : DWORD; in2 : DWORD; in3 : DWORD; clk : BOOL; steps : INT; rep : INT; rst : BOOL; END_VAR VAR_OUTPUT Q0 :BOOL; Q1 :BOOL; Q2 :BOOL; Q3 :BOOL; cnt : INT; run : BOOL; END_VAR VAR r0: DWORD; r1: DWORD; r2: DWORD; r3: DWORD; rx : INT := 1; END_VAR (* version 1.2 14. mar. 2009 programmer hugo tested by tobias gen_bit is 4 bit sequencial pattern generator with 4 DWORD inputs and 4 serial outputs. with the first clock pulse after a reset or after power on, bit 0 of the input DWORDS IN is present on the Outputs Q and the next clock cycle shifts to Bit 1 and so on. the input steps defines how many bits of the input dwords will be shifted to the outputs. the sequence can be repetive when rep = 0 or any amount of repetitions can be defined with input rep. *) (* @END_DECLARATION := '0' *) (* check if number of runs is finished or rep = 0 which means continuous *) IF clk AND NOT rst THEN run := (rep = 0) OR (rx <= rep); IF run THEN (* check for step counter reached and reset to 0 if cnt = steps *) IF cnt = steps THEN cnt := 0; END_IF; (* when cnt = 0 then reload the inputs into the registers *) IF cnt = 0 THEN r0 := in0; r1 := in1; r2 := in2; r3 := in3; END_IF; (* when cnt < steps, shift the lowest bits to the outputs *) IF (cnt < steps) THEN Q0 := r0.0; Q1 := r1.0; Q2 := r2.0; Q3 := r3.0; r0 := SHR(r0,1); r1 := SHR(r1,1); r2 := SHR(r2,1); r3 := SHR(r3,1); END_IF; (* increment the step counter *) cnt := cnt +1; IF (cnt = steps) AND (rep <> 0) THEN rx := rx +1; END_IF; IF (rx > rep) AND (rep <> 0) THEN run := FALSE; END_IF; END_IF; ELSE IF rst THEN run := FALSE; Q0 := FALSE; Q1 := FALSE; Q2 := FALSE; Q3 := FALSE; r0 := 0; r1 := 0; r2 := 0; r3 := 0; cnt := 0; rx := 1; END_IF; END_IF; (* revision histroy hm 4 aug 2006 rev 1.0 original version hm 15. oct. 2008 rev 1.1 improved performance hm 14. mar. 2009 rev 1.2 removed double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK GEN_SQ VAR_INPUT PT : TIME; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR tn: DWORD; tx: DWORD; init : BOOL; END_VAR (* version 1.3 16. feb. 2011 programmer hugo tested by tobias gen_sq generates square wave signal with programmable period time. *) (* @END_DECLARATION := '0' *) (* read system time *) tx := T_PLC_MS(); IF NOT init THEN init := TRUE; tn := tx; Q := TRUE; ELSIF tx - tn >= SHR(TIME_TO_DWORD(PT),1) THEN Q := NOT Q; tn := tn + SHR(TIME_TO_DWORD(pt),1); END_IF; (* revision history hm 4. aug 2006 rev 1.0 original version hm 17. sep 2007 rev 1.1 replaced time() with T_PLC_MS() for compatibility reasons hm 18. jul. 2009 rev 1.2 improved accuracy hm 16. feb 2011 rev 1.3 corrected an error with timer overflow *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SCHEDULER VAR_INPUT E0 : BOOL; E1 : BOOL; E2 : BOOL; E3 : BOOL; END_VAR VAR_INPUT CONSTANT T0, T1, T2, T3 : TIME; END_VAR VAR_OUTPUT Q0, Q1, Q2, Q3 : BOOL; END_VAR VAR init : BOOL; s0, s1, s2, s3 : TIME; tx : TIME; c : INT; END_VAR (* version 1.1 14. mar. 2009 programmer hugo tested by tobias SCHEDULER is used to call programs or function blocks at specific intervals. T0..T3 defines the interval times. *) (* @END_DECLARATION := '0' *) (* read system_time *) tx := DWORD_TO_TIME(T_PLC_MS()); IF NOT init THEN init := TRUE; s0 := tx - T0; s1 := tx - T1; s2 := tx - T2; s3 := tx - T3; END_IF; Q0 := FALSE; Q1 := FALSE; Q2 := FALSE; Q3 :=FALSE; CASE c OF 0: IF tx - s0 >= T0 THEN Q0 := E0; s0 := tx; END_IF; c := 1; 1: IF tx - s1 >= T1 THEN Q1 := E1; s1 := tx; END_IF; c := 2; 2: IF tx - s2 >= T2 THEN Q2 := E2; s2 := tx; END_IF; c := 3; 3: IF tx - s3 >= T3 THEN Q3 := E3; s3 := tx; END_IF; c := 0; END_CASE; (* revision history hm 28. sep. 2008 rev 1.0 original version hm 14. mar. 2009 rev 1.1 removed double assignments *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SCHEDULER_2 VAR_INPUT E0 : BOOL; E1 : BOOL; E2 : BOOL; E3 : BOOL; END_VAR VAR_INPUT CONSTANT C0, C1, C2, C3 : UINT; O0, O1, O2, O3 : UINT; END_VAR VAR_OUTPUT Q0, Q1, Q2, Q3 : BOOL; END_VAR VAR sx : UINT; END_VAR (* version 1.0 29. sep 2008 programmer hugo tested by tobias SCHEDULER_2 is used to call programs or function blocks at specific cycles. C0..C3 defines after how many cycles the output becomes active. O0..O3 defines a cycle offset at startup. *) (* @END_DECLARATION := '0' *) Q0 := E0 AND (sx MOD C0 - O0 = 0); Q1 := E1 AND (sx MOD C1 - O1 = 0); Q2 := E2 AND (sx MOD C2 - O2 = 0); Q3 := E3 AND (sx MOD C3 - O3 = 0); (* increment cycle counter every cycle *) sx := sx + 1; (* revision history hm 29. sep. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SEQUENCE_4 VAR_INPUT in0 : BOOL := TRUE; in1 : BOOL := TRUE; in2 : BOOL := TRUE; in3 : BOOL := TRUE; start : BOOL; rst : BOOL; wait0 : TIME; delay0 : TIME; wait1 : TIME; delay1 : TIME; wait2 : TIME; delay2 : TIME; wait3 : TIME; delay3 : TIME; END_VAR VAR_INPUT CONSTANT stop_on_error : BOOL; END_VAR VAR_OUTPUT Q0 : BOOL; Q1 : BOOL; Q2 : BOOL; Q3 : BOOL; QX : BOOL; run: BOOL; step : INT := -1; status : BYTE; END_VAR VAR last : TIME; edge : BOOL; tx: TIME; init: BOOL; END_VAR (* version 1.5 13. mar. 2009 programmer oscat tested by hans sequence_4 enables run when a low to high transition is present on start. a low to high transition on start will restart the sequencer at any time while a rst will hold the sequencer in reset while true. after run is enabled with a rising edge on start the sequencer waits for wait0 on in0 to enable q0 which stays on for delay0. the next cycle starts with wait1, continues with delay 1 and so on... if an edge is not detected during the wait period, the sequencer will then display the error number on the status output. the status numbers 1..4 are errors in step 0..3. after the last step that sets q3, the sequencer leaves q3 on for delay3 and then resets to the initial state. a step output will indicate the current step of the sequencer and will also be present with a fault condition. after the first output is turened on the sequencer switches from q0 to q1 and so on, at any time there is only one output enabled. if an input signal is not detected during a wait period, the sequencer will display the error number ( 1 for in0, 2 for in1 .... ). when an error is present and the config variable stop_on_error is set then the sequencer will stop. otherwise it will continue. The status output will also display 110 for waiting and 111 for sequence running. *) (* @END_DECLARATION := '0' *) (* read sps timer *) tx := DWORD_TO_TIME(T_PLC_MS()); (* initialize on startup *) IF NOT init THEN last := tx; init := TRUE; status := 110; END_IF; (* asynchronous reset *) IF rst THEN step := -1; Q0 := 0; Q1 := 0; Q2 := 0; Q3 := 0; status := 110; run := 0; (* edge on start input restarts the sequencer *) ELSIF start AND NOT edge THEN step := 0; last := tx; status := 111; Q0 := 0; Q1 := 0; Q2 := 0; Q3 := 0; run := 1; END_IF; edge := start; (* check if stop on status is necessary *) IF status > 0 AND status < 100 AND stop_on_error THEN RETURN; END_IF; (* sequence is running *) IF run AND step = 0 THEN IF NOT q0 AND in0 AND tx - last <= wait0 THEN Q0 := TRUE; last := tx; ELSIF NOT q0 AND tx - last > wait0 THEN status := 1; run := FALSE; ELSIF q0 AND tx - last >= delay0 THEN step := 1; last := tx; END_IF; END_IF; IF run AND step = 1 THEN IF NOT q1 AND in1 AND tx - last <= wait1 THEN Q0 := FALSE; Q1 := TRUE; last := tx; ELSIF NOT q1 AND Tx - last > wait1 THEN status := 2; q0 := FALSE; run := FALSE; ELSIF q1 AND tx - last >= delay1 THEN step := 2; last := tx; END_IF; END_IF; IF run AND step = 2 THEN IF NOT q2 AND in2 AND tx - last <= wait2 THEN Q1 := FALSE; Q2 := TRUE; last := tx; ELSIF NOT q2 AND Tx - last > wait2 THEN status := 3; q1 := FALSE; run := FALSE; ELSIF q2 AND tx - last >= delay2 THEN step := 3; last := tx; END_IF; END_IF; IF run AND step = 3 THEN IF NOT q3 AND in3 AND tx - last <= wait3 THEN Q2 := FALSE; Q3 := TRUE; last := tx; ELSIF NOT q3 AND Tx - last > wait3 THEN status := 4; q2 := FALSE; run := FALSE; ELSIF q3 AND tx - last >= delay3 THEN step := -1; q3 := FALSE; run := FALSE; status := 110; END_IF; END_IF; QX := q0 OR q1 OR q2 OR q3; (* hm 1.10.06 rev 1.1 corrected delay logic to be after event and not before added any output hm 1.12.06 rev 1.2 corrected failure in sequence logic. added init at startup to prevent from initial error after start. hm 17.1.2007 rev 1.3 changed output fault to status for better compatibility with other modules (ESR). added stop on error functionality and setup variable default for inputs in0..3 is true. renamed variable state to step hm 17.sep 2007 rev 1.4 replaced time() with T_PLC_MS() for compatibility reasons hm 13. mar. 2009 rev 1.5 renamed output any to qx for compatibility resons *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTIONBLOCK SEQUENCE_64 VAR_INPUT START : BOOL; SMAX : INT; PROG : ARRAY[0..63]OF TIME; RST : BOOL; END_VAR VAR_OUTPUT STATE : INT := -1; TRIG : BOOL; END_VAR VAR tx : TIME; edge: BOOL; last: TIME; END_VAR (* version 1.0 29. jun. 2008 programmer hugo tested by oscat sequence generates a sequence of states with a programmable length for each state. *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := DWORD_TO_TIME(T_PLC_MS()); TRIG := FALSE; IF RST THEN STATE := -1; (* start sequence *) ELSIF START AND NOT edge THEN STATE := 0; last := tx; TRIG := TRUE; (* sequence generator *) ELSIF (STATE >= 0) THEN IF (tx - last) >= PROG[STATE] THEN STATE := INC2(STATE, 1, -1, SMAX); last := tx; TRIG := TRUE; END_IF; END_IF; edge := START; (* revision history hm 29. jun. 2008 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SEQUENCE_8 VAR_INPUT in0 : BOOL := TRUE; in1 : BOOL := TRUE; in2 : BOOL := TRUE; in3 : BOOL := TRUE; in4 : BOOL := TRUE; in5 : BOOL := TRUE; in6 : BOOL := TRUE; in7 : BOOL := TRUE; start : BOOL; rst : BOOL; wait0 : TIME; delay0 : TIME; wait1 : TIME; delay1 : TIME; wait2 : TIME; delay2 : TIME; wait3 : TIME; delay3 : TIME; wait4 : TIME; delay4 : TIME; wait5 : TIME; delay5 : TIME; wait6 : TIME; delay6 : TIME; wait7 : TIME; delay7 : TIME; END_VAR VAR_INPUT CONSTANT stop_on_error : BOOL; END_VAR VAR_OUTPUT Q0 : BOOL; Q1 : BOOL; Q2 : BOOL; Q3 : BOOL; Q4 : BOOL; Q5 : BOOL; Q6 : BOOL; Q7 : BOOL; QX : BOOL; run: BOOL; step : INT := -1; status : BYTE; END_VAR VAR last : TIME; edge : BOOL; tx: TIME; init: BOOL; END_VAR (* version 1.5 13. mar. 2009 programmer oscat tested by hans sequence_8 enables run when a low to high transition is present on start. a low to high transition on start will restart the sequencer at any time while a rst will hold the sequencer in reset while true. after run is enabled with a rising edge on start the sequencer waits for wait0 on in0 to enable q0 which stays on for delay0. the next cycle starts with wait1, continues with delay 1 and so on... if an edge is not detected during the wait period, the sequencer will then display the error number on the status output. The status numbers are 1 .. 8 for erros in step 0..7. after the last step that sets q7, the sequencer leaves q7 on for delay7 and then resets to the initial state. a step output will indicate the current step of the sequencer and will also be present with a fault condition. after the first output is turened on the sequencer switches from q0 to q1 and so on, at any time there is only one output enabled. if an input signal is not detected during a wait period, the sequencer will display the error number ( 1 for in0, 2 for in1 .... ). when an error is present and the config variable stop_on_error is set then the sequencer will stop. otherwise it will continue. The status output will also display 110 for waiting and 111 sequece running. *) (* @END_DECLARATION := '0' *) (* read sps timer *) tx := DWORD_TO_TIME(T_PLC_MS()); (* initialize on startup *) IF NOT init THEN last := tx; init := TRUE; status := 110; END_IF; (* asynchronous reset *) IF rst THEN step := -1; Q0 := 0; Q1 := 0; Q2 := 0; Q3 := 0; Q4 := 0; Q5 := 0; Q6 := 0; Q7 := 0; status := 110; run := 0; (* edge on start input restarts the sequencer *) ELSIF start AND NOT edge THEN step := 0; last := tx; status := 111; Q0 := 0; Q1 := 0; Q2 := 0; Q3 := 0; Q4 := 0; Q5 := 0; Q6 := 0; Q7 := 0; run := 1; END_IF; edge := start; (* check if stop on error is necessary *) IF status > 0 AND status < 100 AND stop_on_error THEN RETURN; END_IF; (* sequence is running *) IF run AND step = 0 THEN IF NOT q0 AND in0 AND tx - last <= wait0 THEN Q0 := TRUE; last := tx; ELSIF NOT q0 AND tx - last > wait0 THEN status := 1; run := FALSE; ELSIF q0 AND tx - last >= delay0 THEN step := 1; last := tx; END_IF; END_IF; IF run AND step = 1 THEN IF NOT q1 AND in1 AND tx - last <= wait1 THEN Q0 := FALSE; Q1 := TRUE; last := tx; ELSIF NOT q1 AND Tx - last > wait1 THEN status := 2; q0 := FALSE; run := FALSE; ELSIF q1 AND tx - last >= delay1 THEN step := 2; last := tx; END_IF; END_IF; IF run AND step = 2 THEN IF NOT q2 AND in2 AND tx - last <= wait2 THEN Q1 := FALSE; Q2 := TRUE; last := tx; ELSIF NOT q2 AND Tx - last > wait2 THEN status := 3; q1 := FALSE; run := FALSE; ELSIF q2 AND tx - last >= delay2 THEN step := 3; last := tx; END_IF; END_IF; IF run AND step = 3 THEN IF NOT q3 AND in3 AND tx - last <= wait3 THEN Q2 := FALSE; Q3 := TRUE; last := tx; ELSIF NOT q3 AND Tx - last > wait3 THEN status := 4; q2 := FALSE; run := FALSE; ELSIF q3 AND tx - last >= delay3 THEN step := 4; last := tx; END_IF; END_IF; IF run AND step = 4 THEN IF NOT q4 AND in4 AND tx - last <= wait4 THEN Q3 := FALSE; Q4 := TRUE; last := tx; ELSIF NOT q4 AND Tx - last > wait4 THEN status := 5; q3 := FALSE; run := FALSE; ELSIF q4 AND tx - last >= delay4 THEN step := 5; last := tx; END_IF; END_IF; IF run AND step = 5 THEN IF NOT q5 AND in5 AND tx - last <= wait5 THEN Q4 := FALSE; Q5 := TRUE; last := tx; ELSIF NOT q5 AND Tx - last > wait5 THEN status := 6; q4 := FALSE; run := FALSE; ELSIF q5 AND tx - last >= delay5 THEN step := 6; last := tx; END_IF; END_IF; IF run AND step = 6 THEN IF NOT q6 AND in6 AND tx - last <= wait6 THEN Q5 := FALSE; Q6 := TRUE; last := tx; ELSIF NOT q6 AND Tx - last > wait6 THEN status := 7; q5 := FALSE; run := FALSE; ELSIF q6 AND tx - last >= delay6 THEN step := 7; last := tx; END_IF; END_IF; IF run AND step = 7 THEN IF NOT q7 AND in7 AND tx - last <= wait7 THEN Q6 := FALSE; Q7 := TRUE; last := tx; ELSIF NOT q7 AND Tx - last > wait7 THEN status := 8; q6 := FALSE; run := FALSE; ELSIF q7 AND tx - last >= delay7 THEN step := -1; Q7 := FALSE; Run := FALSE; status := 110; END_IF; END_IF; QX := q0 OR q1 OR q2 OR q3 OR q4 OR q5 OR q6 OR q7; (* hm 1.10.06 rev 1.1 corrected delay logic to be after event and not before added any output hm 1.12.06 rev 1.2 corrected failure in sequence logic. added init at startup to prevent from initial statuss after start. hm 17.1.2007 rev 1.3 changed output fault to status for better compatibility with other modules (ESR) added stop on error functionality and setup variable default for inputs in0..7 is true. renames variable state to step hm 17.sep 2007 rev 1.4 replaced time() with T_PLC_MS() for compatibility reasons hm 13. mar. 2009 rev 1.5 renamed output any to qx for compatibility resons *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTIONBLOCK TMAX VAR_INPUT IN : BOOL; PT : TIME; END_VAR VAR_OUTPUT Q : BOOL; Z : BOOL; END_VAR VAR tx : TIME; start : TIME; last_in: BOOL; END_VAR (* version 1.0 20. jul. 2008 programmer hugo tested by oscat Q of tmax will follow IN except that it forces a maximum ontime for the output Q. the output Z will be active for one cycle if the output is forced low by the timeout. *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := DWORD_TO_TIME(T_PLC_MS()); Z := FALSE; IF NOT in THEN Q := FALSE; ELSIF IN AND NOT last_in THEN Q := TRUE; start := tx; ELSIF (tx - start >= PT) AND Q THEN Q := FALSE; Z := TRUE; END_IF; last_in := IN; (* revision history hm 20. jul. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTIONBLOCK TMIN VAR_INPUT IN : BOOL; PT : TIME; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR pm : TP; END_VAR (* version 1.0 21. jul. 2008 programmer hugo tested by oscat Q of tMIN will follow IN except that it forces a minimum ontime for the output Q. *) (* @END_DECLARATION := '0' *) pm(in := IN, PT := PT); Q := IN OR pm.Q; (* revision history hm 21. jul. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTIONBLOCK TOF_1 VAR_INPUT IN : BOOL; PT : TIME; RST : BOOL; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR tx : TIME; start : TIME; END_VAR (* version 1.0 17. jul. 2008 programmer hugo tested by oscat TOF_1 will extend a pulse on input in for PT seconds. in addition the timer can be cleared asynchronously with rst. TOF_1 is the same function as TOF from the standard LIB except the asynchronous reset input. *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := DWORD_TO_TIME(T_PLC_MS()); IF RST THEN Q := FALSE; ELSIF IN THEN Q := TRUE; start := tx; ELSIF tx - start >= PT THEN Q := FALSE; END_IF; (* revision history hm 17. jul. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK TONOF VAR_INPUT IN : BOOL; T_ON,T_OFF : TIME; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR X : TON; old: BOOL; mode: BOOL; END_VAR (* version 1.2 21. jul 2009 programmer hugo tested by tobias TONOF generated a TON and TOF Delay for the Input N TON (T1) and TOF (T2) can be configured separately *) (* @END_DECLARATION := '0' *) IF IN XOR old THEN X(IN := FALSE, PT := SEL(IN,T_OFF,T_ON)); mode := IN; old := IN; END_IF; X(IN := TRUE); IF X.Q THEN Q := mode;END_IF; (* revision history hm 10. dec 2007 rev 1.0 original version hm 17. sep. 2007 rev 1.1 improved performance hm 21. jul. 2009 rev 1.2 fixed a timing probelm *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTIONBLOCK TP_1 VAR_INPUT IN : BOOL; PT : TIME; RST : BOOL; END_VAR VAR_OUTPUT Q : BOOL; END_VAR VAR tx : TIME; start : TIME; ix : BOOL; END_VAR (* version 1.0 28. jun. 2008 programmer hugo tested by oscat TP_1 generates a pulse every time it is calles with in := TRUE. in addition the timer can be cleared asynchronously with rst. the timer can be retriggered as often as necessary. it will count PT from the last trigger. *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := DWORD_TO_TIME(T_PLC_MS()); IF RST THEN Q := FALSE; ELSIF IN AND NOT ix THEN Q := TRUE; start := tx; ELSIF tx - start >= PT THEN Q := FALSE; END_IF; ix:= IN; (* revision history hm 28. jun. 2008 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTIONBLOCK TP_1D VAR_INPUT IN : BOOL; PT1 : TIME; PTD : TIME; RST : BOOL; END_VAR VAR_OUTPUT Q : BOOL; W : BOOL; END_VAR VAR tx : TIME; start: TIME; ix: BOOL; END_VAR (* version 1.0 28. jun. 2008 programmer hugo tested by oscat TP_1D generates a pulse every time it is calles with in := TRUE. the module will reset IN by itself so no clearing of in is necessary. in addition the timer can be cleared asynchronously with rst. also the rst input is cleared by the module itself. the timer can be retriggered as often as necessary. it will count PT from the last trigger. after the time PT1 is elapsed, the timer blocks itself for PT2 and only allow new sequences after PT2 has elapsed. *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := DWORD_TO_TIME(T_PLC_MS()); IF RST THEN Q := FALSE; rst := FALSE; W := FALSE; ELSIF W THEN IF tx - start >= PTD THEN W := FALSE; END_IF; ELSIF IN AND NOT ix THEN Q := TRUE; start := tx; in := FALSE; ELSIF tx - start >= PT1 THEN Q := FALSE; W := TRUE; start := tx; END_IF; ix := IN; (* revision history hm 28. jun. 2008 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/generators' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK TP_X VAR_INPUT IN : BOOL; PT : TIME; END_VAR VAR_OUTPUT Q : BOOL; ET : TIME; END_VAR VAR edge : BOOL; start : TIME; tx: TIME; END_VAR (* version 1.3 17. dec. 2008 programmer hugo tested by oscat retriggerable edge triggered pulse similar to TP but with a retrigger function if the pt input is 0 then output is always low. *) (* @END_DECLARATION := '0' *) (* read system_time *) tx := DWORD_TO_TIME(T_PLC_MS()); (* rising edge trigger *) IF IN AND NOT edge THEN start := tx; Q := PT > t#0ms; ELSIF Q THEN ET := tx - start; IF ET >= PT THEN Q := FALSE; ET := t#0ms; END_IF; END_IF; edge := IN; (* revision history hm 4. aug 2006 rev 1.0 original version hm 17. sep 2007 rev 1.1 replaced time() with T_PLC_MS() for compatibility reasons hm 19. oct. 2008 rev 1.2 renamed to TP_R to TP_X for compatibility reasons hm 17. dec. 2008 rev 1.3 code optimized *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/memory' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FIFO_16 VAR_INPUT Din : DWORD; E : BOOL := TRUE; RD : BOOL; WD : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Dout : DWORD; EMPTY : BOOL := TRUE; FULL : BOOL; END_VAR VAR fifo : ARRAY[0..n] OF DWORD; pr : INT; pw : INT; END_VAR VAR CONSTANT n : INT := 16; (* changing this value will chage the number of stored elements in the fifo *) END_VAR (* version 2.0 24. jul. 2009 programmer hugo tested by oscat 16 Dword FIFO memory *) (* @END_DECLARATION := '0' *) IF RST THEN pw := pr; FULL := FALSE; EMPTY := TRUE; Dout := 0; ELSIF E THEN IF NOT EMPTY AND RD THEN Dout := fifo[pr]; pr := INC1(pr,n); EMPTY := pr = pw; FULL := FALSE; END_IF; IF NOT FULL AND WD THEN fifo[pw] := Din; pw := INC1(pw,n); FULL := pw = pr; EMPTY := FALSE; END_IF; END_IF; (* revision history hm 4. aug. 2006 rev 1.0 original version hm 19. feb 2008 rev 1.1 performance improvements hm 17. oct. 2008 rev 1.2 improved performance ks 27. oct. 2008 rev 1.3 optimized coding hm 14. mar. 2009 rev 1.4 removed double assignments hm 24. jul. 2009 rev 2.0 chaged inputs E and WR to E, WD and WR allow read and write in one cycle *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/memory' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FIFO_32 VAR_INPUT Din : DWORD; E : BOOL := TRUE; RD : BOOL; WD : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Dout : DWORD; EMPTY : BOOL := TRUE; FULL : BOOL; END_VAR VAR fifo : ARRAY[0..n] OF DWORD; pr : INT; pw : INT; END_VAR VAR CONSTANT n : INT := 32; (* changing this value will chage the number of stored elements in the fifo *) END_VAR (* version 2.0 24. jul. 2009 programmer hugo tested by oscat 32 Dword FIFO memory *) (* @END_DECLARATION := '0' *) IF RST THEN pw := pr; FULL := FALSE; EMPTY := TRUE; Dout := 0; ELSIF E THEN IF NOT EMPTY AND RD THEN Dout := fifo[pr]; pr := INC1(pr,n); EMPTY := pr = pw; FULL := FALSE; END_IF; IF NOT FULL AND WD THEN fifo[pw] := Din; pw := INC1(pw,n); FULL := pw = pr; EMPTY := FALSE; END_IF; END_IF; (* revision history hm 4. aug. 2006 rev 1.0 original version hm 19. feb 2008 rev 1.1 performance improvements hm 17. oct. 2008 rev 1.2 improved performance ks 27. oct. 2008 rev 1.3 improved code hm 14. mar. 2009 rev 1.4 removed double assignments hm 24. jul. 2009 rev 2.0 chaged inputs E and WR to E, WD and WR allow read and write in one cycle *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/memory' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK STACK_16 VAR_INPUT Din : DWORD; E : BOOL := TRUE; RD : BOOL; WD : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Dout : DWORD; EMPTY : BOOL := TRUE; FULL : BOOL; END_VAR VAR stack : ARRAY[0..n] OF DWORD; pt : INT; END_VAR VAR CONSTANT n : INT := 15; (* changing this value will chage the number of stored elements in the fifo *) END_VAR (* version 2.0 25. jul 2009 programmer hugo tested by oscat 16 Dword STACK memory *) (* @END_DECLARATION := '0' *) IF RST THEN (* asynchronous reset for the fifo *) pt := 0; EMPTY := TRUE; FULL := FALSE; Dout := 0; ELSIF E THEN IF NOT EMPTY AND RD THEN (* read one element *) pt := pt - 1; Dout := stack[pt]; EMPTY := pt = 0; FULL := FALSE; END_IF; IF NOT FULL AND WD THEN (* write one element *) stack[pt] := Din; pt := pt + 1; FULL := pt > n; EMPTY := FALSE; END_IF; END_IF; (* revision history hm 4. aug. 2006 rev 1.0 original version hm 19. feb 2008 rev 1.1 performance improvements hm 17. oct. 2008 rev 1.2 deleted unnecessary init with 0 hm 27. oct. 2008 rev 1.3 optimized performance hm 25. jul 2009 rev 2.0 changed inputs to allow simultsaneous read and write *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/memory' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK STACK_32 VAR_INPUT Din : DWORD; E : BOOL := TRUE; RD : BOOL; WD : BOOL; RST : BOOL; END_VAR VAR_OUTPUT Dout : DWORD; EMPTY : BOOL := TRUE; FULL : BOOL; END_VAR VAR stack : ARRAY[0..n] OF DWORD; pt : INT; END_VAR VAR CONSTANT n : INT := 31; (* changing this value will chage the number of stored elements in the fifo *) END_VAR (* version 2.0 25. jul 2009 programmer hugo tested by oscat 32 Dword STACK memory *) (* @END_DECLARATION := '0' *) IF RST THEN (* asynchronous reset for the fifo *) pt := 0; EMPTY := TRUE; FULL := FALSE; Dout := 0; ELSIF E THEN IF NOT EMPTY AND RD THEN (* read one element *) pt := pt - 1; Dout := stack[pt]; EMPTY := pt = 0; FULL := FALSE; END_IF; IF NOT FULL AND WD THEN (* write one element *) stack[pt] := Din; pt := pt + 1; FULL := pt > n; EMPTY := FALSE; END_IF; END_IF; (* revision history hm 4. aug. 2006 rev 1.0 original version hm 19. feb 2008 rev 1.1 performance improvements hm 17. oct. 2008 rev 1.2 deleted unnecessary init with 0 ks 27. oct. 2008 rev 1.3 optimized performance hm 25. jul 2009 rev 2.0 changed inputs to allow simultsaneous read and write *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/Others' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CRC_GEN : DWORD VAR_INPUT PT : POINTER TO ARRAY[0..32000] OF BYTE; SIZE : INT; PL : INT; PN : DWORD; INIT : DWORD; REV_IN : BOOL; REV_OUT : BOOL; XOR_OUT : DWORD; END_VAR VAR pos : INT; shift : INT; dx: BYTE; bits: INT; END_VAR (* version 2.0 16.Jan. 2011 programmer hugo tested by tobias CRC_GEN generates a CRC checksum from a block of data and returns the checksum in a DWORD to be connected to the data for transmission. the CRC Polynom is specified with the config variable PN and the length of the Polynom is specified by PL A Polynom x4 + X + 1 is represented by 0011 with length 4, the highest order bit is not specified at all.. The input data is an array of byte of any size, the function is called by CRC_GEN(ADR(array),SIZEOF(array), ....). *) (* @END_DECLARATION := '0' *) (* align polygon *) shift := 32 - PL; PN := SHL(PN, shift); (* load first 4 bytes into register minimum message size is 4 bytes for smaller messages fill with 0#s at the beginning*) FOR pos := 0 TO 3 DO IF REV_IN THEN CRC_GEN := SHL(CRC_GEN, 8) OR REVERSE(PT^[pos]); ELSE CRC_GEN := SHL(CRC_GEN, 8) OR PT^[pos]; END_IF; END_FOR; pos := 4; (* xor with init value *) CRC_GEN := CRC_GEN XOR SHL(init, shift); (* calculate CRC for each byte *) WHILE pos < size DO IF REV_IN THEN DX := REVERSE(PT^[pos]); ELSE DX := PT^[pos]; END_IF; pos := pos + 1; (* crc calculation for one byte *) FOR bits := 0 TO 7 DO IF CRC_GEN.31 THEN CRC_GEN := (SHL(CRC_GEN, 1) OR BOOL_TO_DWORD(DX.7)) XOR PN; ELSE CRC_GEN := SHL(CRC_GEN, 1) OR BOOL_TO_DWORD(DX.7); END_IF; dx := SHL(dx, 1); END_FOR; END_WHILE; (* all bytes are processed, need to finish the registers 32 bits *) FOR bits := 0 TO 31 DO IF CRC_GEN.31 THEN CRC_GEN := (SHL(CRC_GEN, 1) OR BOOL_TO_DWORD(DX.7)) XOR PN; ELSE CRC_GEN := SHL(CRC_GEN, 1) OR BOOL_TO_DWORD(DX.7); END_IF; END_FOR; (* final XOR *) CRC_GEN := SHR(CRC_GEN, shift) XOR XOR_OUT; (* reverse the crc_out put if necessary *) IF REV_OUT THEN CRC_GEN := REFLECT(CRC_GEN, PL); END_IF; (* typical crc polynoms CRC-4-ITU x4 + x + 1 (ITU G.704, p. 12) 0x3 or 0xC (0x9) CRC-5-ITU x5 + x4 + x2 + 1 (ITU G.704, p. 9) 0x15 or 0x15 (0x0B) Bluetooth CRC-5-USB x5 + x2 + 1 (use: USB token packets) 0x05 or 0x14 (0x9) CRC-6-ITU x6 + x + 1 (ITU G.704, p. 3) 0x03 or 0x30 (0x21) CRC-7 x7 + x3 + 1 (use: telecom systems, MMC) 0x09 or 0x48 (0x11) CRC-8-ATM x8 + x2 + x + 1 (use: ATM HEC) 0x07 or 0xE0 (0xC1) CRC-8-CCITT x8 + x7 + x3 + x2 + 1 (use: 1-Wire bus) 0x8D or 0xB1 (0x63) CRC-8-Dallas/Maxim x8 + x5 + x4 + 1 (use: 1-Wire bus) 0x31 or 0x8C (0x19) CRC-8 x8 + x7 + x6 + x4 + x2 + 1 0xD5 or 0xAB (0x57) CRC-8-SAE J1850 x8 + x4 + x3 + x2 + 1 0x1D or 0xB8 CRC-10 x10 + x9 + x5 + x4 + x + 1 0x233 or 0x331 (0x263) CRC-12 x12 + x11 + x3 + x2 + x + 1 (use: telecom systems) 0x80F or 0xF01 (0xE03) CRC-15-CAN x15 + x14 + x10 + x8 + x7 + x4 + x3 + 1 0x4599 or 0x4CD1 (0x19A3) CRC-16-Fletcher Not a CRC; see Fletcher's checksum Used in Adler-32 A & B CRCs CRC-16-CCITT x16 + x12 + x5 + 1 (XMODEM,X.25, V.41, Bluetooth, PPP, IrDA; known as "CRC-CCITT") 0x1021 or 0x8408 (0x0811) CRC-16-IBM x16 + x15 + x2 + 1 (USB, many others; also known as "CRC-16") 0x8005 or 0xA001 (0x4003) CRC-24-Radix-64 x24 + x23 + x18 + x17 + x14 + x11 + x10 + x7 + x6 + x5 + x4 + x3 + x + 1 0x864CFB or 0xDF3261 (0xBE64C3) CRC-32-Adler Not a CRC; see Adler-32 See Adler-32 CRC-32-MPEG2 x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 1 0x04C11DB7 or 0xEDB88320 (0xDB710641) Also used in IEEE 802.3 CRC-32-IEEE 802.3 x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 1 (V.42) 0x04C11DB7 or 0xEDB88320 (0xDB710641) CRC-32C (Castagnoli) x32 + x28 + x27 + x26 + x25 + x23 + x22 + x20 + x19 + x18 + x14 + x13 + x11 + x10 + x9 + x8 + x6 + 1 0x1EDC6F41 or 0x82F63B78 (0x05EC76F1) CRC-64-ISO x64 + x4 + x3 + x + 1 (use: ISO 3309) 0x000000000000001B or 0xD800000000000000 (0xB000000000000001) CRC-64-ECMA-182 x64 + x62 + x57 + x55 + x54 + x53 + x52 + x47 + x46 + x45 + x40 + x39 + x38 + x37 + x35 + x33 + x32 + x31 + x29 + x27 + x24 + x23 + x22 + x21 + x19 + x17 + x13 + x12 + x10 + x9 + x7 + x4 + x + 1 (as described in ECMA-182 p.63) 0x42F0E1EBA9EA3693 or 0xC96C5795D7870F42 (0x92D8AF2BAF0E1E85) *) (* revision history hm 9.6.2007 rev 1.0 original version hm 11.9.2007 rev 1.1 deleted unused variable i hm 9. oct 2007 rev 1.2 added init code for crc and xor_out added refelct in and reflect_out (rev_in und Rev_out) hm 2. jan 2008 rev 1.3 small changes for performance improvements hm 16. mar. 2008 rev 1.4 changed type of input size to uint hm 10. mar. 2009 rev 1.5 removed nested comments hm 16. jan. 2011 rev 2.0 new version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/Others' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK MATRIX VAR_INPUT x1, x2, x3, x4, X5 : BOOL; END_VAR VAR_INPUT CONSTANT Release : BOOL; END_VAR VAR_OUTPUT code : BYTE; TP : BOOL; y1: BOOL := TRUE; y2, y3, y4 : BOOL; END_VAR VAR line : BYTE; X : ARRAY[0..3] OF BYTE; (* scan line inputs *) L : ARRAY[0..3] OF BYTE; (* scan line status *) i: INT; temp: BYTE; END_VAR (* version 1.3 26 oct. 2008 programmer hugo tested by oscat MATRIX is a matrix keyboard encoder for 4 rows and up to 5 columns. matrix can send a code while a key is pressed and it sends another code while a key is released when the setup variable 'release' is set to true. the setup variable release is false the code is only sent when the key is pressed. the output byte holds the 5 columns in the lowest bits 0..2 and the row number in bits 4..6, while bit 7 is true for a key pressed and false for a key released. *) (* @END_DECLARATION := '0' *) TP := FALSE; CODE := 0; (* read scan lines *) X[line].0 := X1; X[line].1 := X2; X[line].2 := X3; X[line].3 := X4; X[line].4 := X5; (* compare for change *) FOR i := 0 TO 3 DO IF X[i] <> L[i] THEN (* scan line information has changed code need to be found and generated *) temp := x[i] XOR L[i]; IF temp.0 THEN CODE := 1; code.7 := X[i].0; L[i].0 := X[i].0; ELSIF temp.1 THEN code := 2; code.7 := X[i].1; L[i].1 := X[i].1; ELSIF temp.2 THEN code := 3; code.7 := X[i].2; L[i].2 := X[i].2; ELSIF temp.3 THEN CODE := 4; CODE.7 := X[i].3; L[i].3 := X[i].3; ELSIF temp.4 THEN CODE := 5; CODE.7 := X[i].4; L[i].4 := X[i].4; END_IF; TP := TRUE; CODE.4 := Line.0; CODE.5 := Line.1; CODE.6 := Line.2; (* check if release codes need to be killed *) IF NOT release AND CODE < 127 THEN CODE := 0; TP := FALSE; END_IF; EXIT; END_IF; END_FOR; (* increment scan line every cycle *) line := (line + 1) AND 2#0000_0011; temp := SHL(BYTE#1,line); Y1 := temp.0; Y2 := temp.1; Y3 := temp.2; Y4 := temp.3; (* revision history hm 10.6.2007 rev 1.0 original version hm 11.9.2007 rev 1.1 deleted unused variables k and old_code hm 23.12.2007 rev 1.2 added exit statement in for loop instead of i:=5 hm 26. oct. 2008 rev 1.3 code optimized *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Logic\/Others' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK PIN_CODE VAR_INPUT CB : BYTE; E : BOOL; END_VAR VAR_INPUT CONSTANT PIN : STRING(8); END_VAR VAR_OUTPUT TP : BOOL; END_VAR VAR POS: INT := 1; END_VAR (* version 1.0 30. oct. 2008 programmer hugo tested by oscat MATRIX_CODE scans the input of a key_pad (MATRIX) for a sequence of characters. *) (* @END_DECLARATION := '0' *) tp := FALSE; IF e THEN IF CB = CODE(pin, pos) THEN pos := pos + 1; IF pos > LEN(PIN) THEN (* proper code is detected *) TP := TRUE; pos := 1; END_IF; ELSE pos := 1; END_IF; END_IF; (* revision history hm 30. oct. 2008 rev 1.0 original version *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION _ARRAY_ABS : BOOL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR VAR i: UINT; stop: UINT; END_VAR (* version 1.0 2. apr. 2008 programmer hugo tested by tobias this function will calculate the absolute value of each element of the array and stroe the result instead of the element. Array[i] := ABS(ARRAY[i]). the function needs to be called: array_avg(adr("array"),sizeof("array")); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) stop := SHR(size,2)-1; FOR i := 0 TO stop DO PT^[i] := ABS(PT^[i]); END_FOR; _ARRAY_ABS := TRUE; (* revision history hm 2. apr 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION _ARRAY_ADD : BOOL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; X : REAL; END_VAR VAR i: UINT; stop: UINT; END_VAR (* version 1.0 2. apr. 2008 programmer hugo tested by tobias this function will add an offset X to each element of the array and stroe the result instead of the element. Array[i] := ARRAY[i] + X. the function needs to be called: array_avg(adr("array"),sizeof("array")); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) stop := SHR(size,2)-1; FOR i := 0 TO stop DO PT^[i] := PT^[i] + X; END_FOR; _ARRAY_ADD := TRUE; (* revision history hm 2. apr 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION _ARRAY_INIT : BOOL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; init : REAL; END_VAR VAR i: UINT; stop: UINT; END_VAR (* version 1.3 16 mar 2008 programmer hugo tested by tobias this function will initialize a given array with a value init. the function needs to be called: _array_init(adr("array"),sizeof("array")); this function will manipulate a given array. the function manipulates the original array, it rerturnes true when finished. because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) stop := SHR(size,2)-1; FOR i := 0 TO stop DO pt^[i] := init; END_FOR; _array_init := TRUE; (* revision History hm 6.1.2007 rev 1.1 change type of function to bool added array_init := true to set output true. hm 14.11.2007 rev 1.2 changed stop calculation to be more efficient hm 16.3. 2008 rev 1.3 changed type of input size to uint *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION _ARRAY_MEDIAN : REAL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR VAR i: UINT; stop: UINT; END_VAR (* version 1.5 16 mar 2008 programmer hugo tested by tobias this function will calculate the median of a given array. in order to do so the ariginal array is sorted and stays sorted after the function finishes the function needs to be called: _array_median(adr("array"),sizeof("array")); this function will manipulate a given array. the function will return the median of the original array. for example [12,0,4,7,1] the median is 4 and the array after the function is called is [0,1,4,7,12] because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) _ARRAY_SORT(pt,size); stop := SHR(size,2)-1; IF EVEN(UINT_TO_INT(stop)) THEN _ARRAY_MEDIAN := pt^[SHR(stop,1)]; ELSE i := SHR(stop,1); _ARRAY_MEDIAN := (pt^[i] + pt^[i+1]) * 0.5; END_IF; (* old code stop := (size - SIZEOF(pt)) / SIZEOF(pt); FOR i := 0 TO stop - 1 DO FOR m := i + 1 TO stop DO IF pt^[i] > pt^[m] THEN temp := pt^[i]; pt^[i] := pt^[m]; pt^[m] := temp; END_IF; END_FOR; END_FOR; IF even(stop) THEN _array_median := pt^[stop/2]; ELSE i := stop/2; _array_median := (pt^[i] + pt^[i+1])/2; END_IF; *) (* revision history hm 3.3.2007 rev 1.1 corrected an error, changed the statement line 14 i := TRUNC(stop/2); to i := stop/2; hm 22. sep 2007 rev 1.2 changed algorithm to use _array_soft for performance reasons hm 8. oct 2007 rev 1.3 deleted unused variables m and temp hm 14. nov 2007 rev 1.4 corrected a problem with size calculation hm 16.3. 2008 rev 1.5 changed type of input size to uint performance improvements *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION _ARRAY_MUL : BOOL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; X : REAL; END_VAR VAR i: UINT; stop: UINT; END_VAR (* version 1.0 2. apr. 2008 programmer hugo tested by tobias this function will multiply each element of the array and stroe the result instead of the element. Array[i] := ARRAY[i] * X. the function needs to be called: array_avg(adr("array"),sizeof("array")); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) stop := SHR(size,2)-1; FOR i := 0 TO stop DO PT^[i] := PT^[i] * X; END_FOR; _ARRAY_MUL := TRUE; (* revision history hm 2. apr 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION _ARRAY_SHUFFLE : BOOL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR VAR temp : REAL; pos : INT; i: INT; stop: INT; END_VAR (* version 1.2 30. mar. 2008 programmer kurt tested by hugo this function will randomly shuffle the elements of an array *) (* @END_DECLARATION := '0' *) stop := UINT_TO_INT(SHR(size,2)-1); FOR i := 0 TO stop DO pos := RDM2(i+pos,0,stop); (* swap elements *) temp := pt^[i]; pt^[i] := pt^[pos]; pt^[pos] := temp; END_FOR; _ARRAY_SHUFFLE := TRUE; (* revision History hm 4. mar 2008 rev 1.0 original version hm 16. mar. 2008 rev 1.1 changed type of input size to uint hm 30. mar. 2008 rev 1.2 changed last in rdm2 fromm pos to i + pos to allow for more randomness *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION _ARRAY_SORT : BOOL VAR_INPUT PT : POINTER TO ARRAY[1..32000] OF REAL; SIZE : UINT; END_VAR VAR stack_count: UINT; (* Laufvariable Stack*) stack: ARRAY[1..32] OF UINT; (* Stackgröße~ 1,6*Log(n)/log(2) *) (* Stackgröße 32 => ~1 Mio Elemente *) links: UINT; (* Anfangselement des Arrays *) rechts: UINT; (* Endelement des Arrays *) pivot: REAL; (* temporärer Schwellwert für Tauschbedingung *) i: UINT; (* Laufvariable1 *) j: UINT; (* Laufvariable2 *) ende_innen: BOOL; (* Ende innere Schleife *) ende_aussen: BOOL; (* Ende äußere Schleife *) tmp: REAL; (* Hilfsvariable zum Tauschen von Werten *) END_VAR (* version 2.0 19. jan. 2011 programmer Alexander Trikitis tested by oscat this function will sort a given array. the function needs to be called: _array_sort(adr("array"),sizeof("array")); this function will manipulate a given array. the function will not return anything except true, it will instead manipulate the original array. because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) (* Startwerte zur Arraygröße *) links := 1; (* Anfangselement des Arrays *) rechts := SHR(size,2); (* Endelement des Arrays *) stack_count := 1; (* Laufvariable Stack *) WHILE NOT ende_aussen DO (* äußere Schleife *) IF links < rechts THEN pivot := PT^[SHR(rechts+links,1)]; (* Wert des mittleren Elements als Pivot-Wert *) i := links -1; j := rechts +1; (* innere Schleife, teile Feld *) ende_innen := FALSE; REPEAT (* steigende Reihenfolge *) REPEAT i := i+1; UNTIL (PT^[i] >= pivot) OR NOT (i < rechts) END_REPEAT; REPEAT j := j-1; UNTIL (PT^[j] <= pivot) OR NOT (j > links) END_REPEAT; (*sinkende Reihenfolge *) (* REPEAT i := i+1; UNTIL (PT^[i] <= pivot) OR NOT (i < rechts) END_REPEAT *) (* REPEAT j := j-1; UNTIL (PT^[j] >= pivot) OR NOT (j > links) END_REPEAT *) IF i >= j THEN ende_innen := TRUE; ELSE tmp := PT^[j]; PT^[j] := PT^[i]; PT^[i] := tmp; END_IF; UNTIL ende_innen END_REPEAT; (* Push stack *) stack[stack_count] := rechts; (* rechten Teil später sortieren *) IF Stack_count < 32 THEN stack_count := stack_count +1; ELSE ende_aussen := TRUE; _ARRAY_SORT := FALSE; (* Fehler: Stack zu klein *) END_IF; (* linken Teil sortiern *) rechts := MAX(links, i-1); ELSE IF stack_count = 1 THEN ende_aussen := TRUE; ELSE links := rechts+1; (* pop stack *) stack_count := stack_count -1; (* jetzt rechten Teil sortierne *) rechts:= stack[stack_count]; END_IF; END_IF; END_WHILE; _ARRAY_SORT := TRUE; (* Sortierung beendet *) (* algorithm used before rev 2.0 size := SHR(size,2); size2 := UINT_TO_INT(SHR(size,1)); FOR i := SIZE2 TO 1 BY -1 DO j:=i; WHILE j <= SIZE2 DO stop := j * 2 + 1; IF stop > UINT_TO_INT(SIZE) THEN stop := stop - 1; ELSIF pt^[stop-1] > pt^[stop] THEN stop := stop - 1; END_IF; IF pt^[j] < pt^[stop] THEN temp := pt^[j]; pt^[j] := pt^[stop]; pt^[stop] := temp; END_IF; j := stop; END_WHILE; END_FOR; *) (* heapsort FOR heapsize := UINT_TO_INT(size) TO 1 BY -1 DO popmax := pt^[1]; pt^[1] := pt^[heapsize]; i := 1; hs2 := SHR(heapsize,1); WHILE i <= hs2 DO stop := i * 2 + 1; IF stop > heapsize THEN stop := stop - 1; ELSIF pt^[stop-1] > pt^[stop] THEN stop := stop - 1; END_IF; IF pt^[i] < pt^[stop] THEN temp := pt^[i]; pt^[i] := pt^[stop]; pt^[stop] := temp; END_IF; i := stop; END_WHILE; pt^[heapsize] := popmax; END_FOR; _ARRAY_SORT2 := TRUE; *) (* old algorithm bubble sort used before rev 1.2 stop := (size - SIZEOF(pt)) / SIZEOF(pt); FOR i := 0 TO stop - 1 DO FOR m := i + 1 TO stop DO IF pt^[i] > pt^[m] THEN temp := pt^[i]; pt^[i] := pt^[m]; pt^[m] := temp; END_IF; END_FOR; END_FOR; _array_sort := TRUE; *) (* revision history hm 6.1.2007 rev 1.1 changed return value to true hm 22. sep 2007 rev 1.2 changed sorting algorithm to heap sort for performance reasons hm 14. nov 2007 rev 1.3 changed calculation of stop to be more efficient hm 15 dec 2007 rev 1.4 added function return true hm 5. jan 2008 rev 1.5 improved performance hm 16. mar. 2008 rev 1.6 changed type of input size to uint hm 28. mar. 2008 rev 1.7 changed input f to pt hm 19. jan. 2011 rev 2.0 new faster algorithm using qucksort (Alexander Drikitis) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ARRAY_AVG : REAL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR VAR i: UINT; stop: UINT; END_VAR (* version 1.3 10. mar. 2009 programmer hugo tested by tobias this function will calculate the average of a given array. the function needs to be called: array_avg(adr("array"),sizeof("array")); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) stop := SHR(size,2)-1; ARRAY_AVG := pt^[0]; FOR i := 1 TO stop DO ARRAY_AVG := ARRAY_AVG + pt^[i]; END_FOR; ARRAY_AVG := ARRAY_AVG / UINT_TO_REAL(stop + 1); (* revision history hm 2. oct 2007 rev 1.0 original version hm 12. dec 2007 rev 1.1 chaged code for better performance hm 16. mar. 2008 rev 1.2 changed input size to uint hm 10. mar. 2009 rev 1.3 added type conversion for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ARRAY_GAV : REAL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR VAR i: UINT; stop: UINT; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function will calculate the geometric average of a given array. the function needs to be called: array_avg(adr("array"),sizeof("array")); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) stop := SHR(size,2)-1; ARRAY_GAV := 1.0; FOR i := 0 TO stop DO IF pt^[i] > 0.0 THEN ARRAY_GAV := ARRAY_GAV * pt^[i]; ELSE ARRAY_GAV := 0.0; RETURN; END_IF; END_FOR; ARRAY_GAV := SQRTN(ARRAY_GAV,UINT_TO_INT(stop)+1); (* revision history hm 2. apr 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ARRAY_HAV : REAL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR VAR i: UINT; stop: UINT; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function will calculate the harmonic average of a given array. the function needs to be called: array_avg(adr("array"),sizeof("array")); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) stop := SHR(size,2)-1; FOR i := 0 TO stop DO IF pt^[i] <> 0.0 THEN ARRAY_HAV := ARRAY_HAV + 1.0 / pt^[i]; ELSE ARRAY_HAV := 0.0; RETURN; END_IF; END_FOR; ARRAY_HAV := UINT_TO_REAL(stop + 1) / ARRAY_HAV; (* revision history hm 2. apr 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ARRAY_MAX : REAL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR VAR i: UINT; stop: UINT; END_VAR (* version 1.1 16. mar. 2008 programmer hugo tested by tobias this function will calculate the max value of a given array. the function needs to be called: array_max(adr("array"),sizeof("array")); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) stop := (size -SIZEOF(pt)) / SIZEOF(pt); array_max := pt^[0]; FOR i := 1 TO stop DO IF pt^[i] > array_max THEN array_max := pt^[i]; END_IF; END_FOR; (* revision history hm 2. oct 2006 rev 1.0 original version hm 16. mar. 2008 rev 1.1 changed input size to uint *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ARRAY_MIN : REAL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR VAR i: UINT; stop: UINT; END_VAR (* version 1.1 16. mar. 2008 programmer hugo tested by tobias this function will calculate the min value of a given array. the function needs to be called: array_min(adr("array"),sizeof("array")); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) stop := (size - SIZEOF(pt)) / SIZEOF(pt); array_min := pt^[0]; FOR i := 1 TO stop DO IF pt^[i] < array_min THEN array_min := pt^[i]; END_IF; END_FOR; (* revision history hm 2. oct. 2006 original version hm 16. mar. 2008 changed type of input size to uint *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ARRAY_SDV : REAL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR (* version 1.1 16. mar 2008 programmer hugo tested by tobias this function will calculate the standard deviation of a given array. the function needs to be called: array_sdv(adr("array"),sizeof("array")); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) (* standard deviation is simply the square root of the variance *) array_sdv := SQRT(array_var(pt, size)); (* revision history hm 1.4.2007 rev 1.0 function created hm 16. mar. 2008 rev 1.1 changed type of input size to uint *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ARRAY_SPR : REAL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR VAR i: UINT; stop: UINT; array_max : REAL; array_min : REAL; END_VAR (* version 1.1 16. mar. 2008 programmer hugo tested by tobias this function will calculate the spread of a given array. the function needs to be called: array_spr(adr("array"),sizeof("array")); for example: spread of [12,0,4,1,7] is 12 - 0 = 12. because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) stop := (size -SIZEOF(pt)) / SIZEOF(pt); array_min := pt^[0]; array_max := pt^[0]; FOR i := 1 TO stop DO IF pt^[i] > array_max THEN array_max := pt^[i]; ELSIF pt^[i] < array_min THEN array_min := pt^[i]; END_IF; END_FOR; array_spr := array_max - array_min; (* revision history hm 2. oct. 2006 rev 1.0 original version hm 16. mar. 2008 rev 1.1 changed type of input size to uint *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ARRAY_SUM : REAL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR VAR i: UINT; stop: UINT; END_VAR (* version 1.1 16. mar. 2008 programmer hugo tested by tobias this function will calculate the sum of a given array. the function needs to be called: array_sum(adr("array"),sizeof("array")); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) stop := (size -SIZEOF(pt)) / SIZEOF(pt); array_sum := pt^[0]; FOR i := 1 TO stop DO array_sum := array_sum + pt^[i]; END_FOR; (* revision history hm 2. oct. 2006 rev 1.0 function created hm 16. mar. 2008 rev 1.1 changed type of input size to uint *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ARRAY_TREND : REAL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR VAR i: UINT; stop: UINT; x: REAL; stop2: UINT; END_VAR (* version 1.3 10. mar. 2009 programmer hugo tested by tobias this function will calculate the trend of a given array. trend will calculate the avg of the first half of the array and then the avg of the second half, trend = avg2 - avg1. for example: [0,1,4,5,3,4,6,3] = 4 - 2.5 = 1.5 the function needs to be called: array_trend(adr("array"),sizeof("array")); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) stop := SHR(size,2)-1; stop2 := SHR(stop,1); FOR i := 0 TO stop2 DO x := x - pt^[i]; END_FOR; IF EVEN(UINT_TO_INT(stop)) THEN FOR i := stop2 TO stop DO X := X + pt^[i]; END_FOR; ELSE FOR i := stop2 + 1 TO stop DO X := X + pt^[i]; END_FOR; END_IF; ARRAY_TREND := x / UINT_TO_REAL(stop2 + 1); (* revision history hm 2 oct 2007 rev 1.0 original version hm 12 dec 2007 rev 1.1 changed code for better performance hm 16. mar. 2008 rev 1.2 changed type of input size to uint hm 10. mar. 2009 rev 1.3 added type conversions for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ARRAY_VAR : REAL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR VAR avg : REAL; i: UINT; stop: UINT; END_VAR (* version 1.3 10. mar. 2009 programmer hugo tested by tobias this function will calculate the variance of a given array. the function needs to be called: array_var(adr("array"),sizeof("array")); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) (* at first we calualte the arithmetic average of the array *) stop := SHR(size,2)-1; avg := pt^[0]; FOR i := 1 TO stop DO avg := avg + pt^[i]; END_FOR; avg := avg / UINT_TO_REAL(stop + 1); (* in a second run we calculate the variance of the array *) array_var := (pt^[0] - avg) * (pt^[0] - avg); FOR i := 1 TO stop DO array_var := array_var + (pt^[i] - avg) * (pt^[i] - avg); END_FOR; ARRAY_VAR := ARRAY_VAR / UINT_TO_REAL(stop); (* revision history hm 1.4.2007 rev 1.0 function created hm 12.12.2007 rev 1.1 changed code for better performance hm 16. mar. 2008 rev 1.2 changed type of input size to uint hm 10. mar. 2009 rev 1.3 added type conversions for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Array' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION IS_SORTED : BOOL VAR_INPUT pt : POINTER TO ARRAY[0..32000] OF REAL; size : UINT; END_VAR VAR stop: INT; i: INT; END_VAR (* version 1.1 4. apr. 2008 programmer hugo tested by tobias this function will return true if the given array is sorted in an ascending order. the function needs to be called: array_sorted(adr("array"),sizeof("array")); because this function works with pointers its very time efficient and it needs no extra memory. *) (* @END_DECLARATION := '0' *) stop := UINT_TO_INT(SHR(size,2)-2); FOR i := 0 TO stop DO IF pt^[i] > pt^[i+1] THEN IS_SORTED := FALSE; RETURN; END_IF; END_FOR; IS_SORTED := TRUE; (* revision history hm 29. mar. 2008 rev 1.0 original version hm 4. apr. 2008 rev 1.1 added type conversion to avoid warnings under codesys 3.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CABS : REAL VAR_INPUT X : complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function the aboslute value of a complex number *) (* @END_DECLARATION := '0' *) CABS := HYPOT(x.re, x.im); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CACOS : complex VAR_INPUT X : complex; END_VAR VAR Y : complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the arcus cosinus function of a complex number *) (* @END_DECLARATION := '0' *) y := CACOSH(x); CACOS.re := y.im; CACOS.im := -y.re; (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CACOSH : complex VAR_INPUT X : complex; END_VAR VAR Y : complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the hyperbolic arcus cosinus function of a complex number *) (* @END_DECLARATION := '0' *) y.re := (X.re - X.im)*(X.re + X.im) - 1.0; y.im := 2.0 * X.re * X.im; y := CSQRT(y); y.re := y.re + x.re; y.im := y.im + x.im; CACOSH := CLOG(y); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CADD : complex VAR_INPUT X, Y : complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function add two complex numbers *) (* @END_DECLARATION := '0' *) CADD.re := x.re + y.re; CADD.im := x.im + y.im; (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CARG : REAL VAR_INPUT X : complex; END_VAR (* version 1.1 20. apr. 2008 programmer hugo tested by oscat this function calculates the phase angle (argument) of a complex number *) (* @END_DECLARATION := '0' *) CARG := ATAN2(X.im, X.re); (* revision history hm 21. feb. 2008 rev 1.0 original version hm 20. apr. 2008 rev 1.1 use ATAN2 instead of ATAN *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CASIN : complex VAR_INPUT X : complex; END_VAR VAR Y : complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the arcus sinus function of a complex number *) (* @END_DECLARATION := '0' *) y.re := -x.im; y.im := x.re; y := CASINH(y); CASIN.re := y.im; CASIN.im := -y.re; (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CASINH : complex VAR_INPUT X : complex; END_VAR VAR Y : complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the hyperbolic arcus sinus function of a complex number *) (* @END_DECLARATION := '0' *) y.re := (X.re - X.im)*(X.re + X.im) + 1.0; y.im := 2.0 * X.re * X.im; y := CSQRT(y); y.re := y.re + x.re; y.im := y.im + x.im; CASINH := CLOG(y); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CATAN : complex VAR_INPUT X : complex; END_VAR VAR r2: REAL; num, den : REAL; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the complex arcus tangens *) (* @END_DECLARATION := '0' *) r2 := x.re * x.re; den := 1.0 - r2 - x.im * x.im; CATAN.re := 0.5 * ATAN(2.0 * x.re / den); num := x.im + 1.0; num := r2 + num * num; den := x.im - 1.0; den := r2 + den * den; CATAN.im := 0.25 * (LN(num)-LN(den)); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CATANH : complex VAR_INPUT X : complex; END_VAR VAR i2: REAL; num, den : REAL; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the complex arcus hyperbolic tangens *) (* @END_DECLARATION := '0' *) i2 := x.im * x.im; num := 1.0 + x.re; num := i2 + num * num; den := 1.0 - x.re; den := i2 + den * den; CATANH.re := 0.25 * (LN(num) - LN(den)); den := 1 - x.re * x.re - i2; CATANH.im := 0.5 * ATAN(2.0 * x.im / den); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CCON : complex VAR_INPUT X : complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calcucates the conjugation of a complex number *) (* @END_DECLARATION := '0' *) CCON.re := x.re; CCON.im := -x.im; (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CCOS : complex VAR_INPUT X : complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the cosinus function of a complex number *) (* @END_DECLARATION := '0' *) CCOS := CCOSH(CSET(-X.im, X.re)); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CCOSH : complex VAR_INPUT X : complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the arcus tangens function of a complex number *) (* @END_DECLARATION := '0' *) CCOSH.re := cosH(x.re) * COS(x.im); CCOSH.im := sinH(x.re) * SIN(x.im); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CDIV : complex VAR_INPUT X, Y : complex; END_VAR VAR Temp : REAL; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function divides two complex numbers *) (* @END_DECLARATION := '0' *) temp := Y.re * Y.re + Y.im * Y.im; CDIV.re := (X.re * Y.re + X.im * Y.im) / temp; CDIV.im := (X.im * Y.re - X.re * Y.im) / temp; (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CEXP : complex VAR_INPUT X : complex; END_VAR VAR Temp : REAL; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the complex exponent *) (* @END_DECLARATION := '0' *) temp := EXP(X.re); CEXP.re := temp * COS(X.im); CEXP.im := temp * SIN(X.im); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CINV : complex VAR_INPUT X : complex; END_VAR VAR temp : REAL; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the inverse of a complex numbers (1 / X) *) (* @END_DECLARATION := '0' *) temp := X.re * X.re + X.im * X.im; CINV.re := X.re / temp; CINV.im := -X.im / temp; (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CLOG : complex VAR_INPUT X : complex; END_VAR (* version 1.1 20. apr. 2008 programmer hugo tested by oscat this function calculates the complex natural (base e) logarithm *) (* @END_DECLARATION := '0' *) CLOG.re := LN(HYPOT(X.re, X.im)); CLOG.im := ATAN2(X.im, X.re); (* revision history hm 21. feb 2008 rev 1.0 original version hm 20. apr. 2008 rev 1.1 use ATAN2 instead of ATAN *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CMUL : complex VAR_INPUT X, Y : complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function multiplies two complex numbers *) (* @END_DECLARATION := '0' *) CMUL.re := X.re * Y.re - X.im * Y.im; CMUL.im := X.re * Y.im + X.im * Y.re; (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CPOL : complex VAR_INPUT L : REAL; A : REAL; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function creates a complex numbers for the polar form with the inputs L (length) an A for Angle. *) (* @END_DECLARATION := '0' *) CPOL.re := L * COS(A); CPOL.im := L * SIN(A); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CPOW : complex VAR_INPUT X : complex; Y : Complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the complex power function x to the power of y *) (* @END_DECLARATION := '0' *) CPOW := CEXP(CMUL(Y,CLOG(X))); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CSET : complex VAR_INPUT RE : REAL; IM : REAL; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function creates a complex number from two real inputs *) (* @END_DECLARATION := '0' *) CSET.re := RE; CSET.im := IM; (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CSIN : complex VAR_INPUT X : complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the sinus function of a complex number *) (* @END_DECLARATION := '0' *) CSIN.re := cosH(X.im) * SIN(X.re); CSIN.im := sinH(X.im) * COS(X.re); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CSINH : complex VAR_INPUT X : complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the sinus function of a complex number *) (* @END_DECLARATION := '0' *) CSINH.re := sinH(X.re) * COS(X.im); CSINH.im := cosH(X.re) * SIN(X.im); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CSQRT : complex VAR_INPUT X : complex; END_VAR VAR temp : REAL; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the complex sqare root *) (* @END_DECLARATION := '0' *) temp := HYPOT(x.re, x.im); CSQRT.re := SQRT(0.5 * (temp + x.re)); CSQRT.im := sgn(x.im) * SQRT(0.5 * (temp - x.re)); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CSUB : complex VAR_INPUT X, Y : complex; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function subtracts two complex numbers *) (* @END_DECLARATION := '0' *) CSUB.re := X.re - Y.re; CSUB.im := X.im - Y.im; (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CTAN : complex VAR_INPUT X : complex; END_VAR VAR temp : REAL; xi2: REAL; xr2: REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by oscat this function calculates the tangens function of a complex number *) (* @END_DECLARATION := '0' *) xi2 := 2.0 * x.im; xr2 := 2.0 * x.re; temp := 1.0 / (COS(xr2) + COSH( xi2)); CTAN.re := temp * SIN(xr2); CTAN.im := temp * SINH(xi2); (* revision history hm 21. feb 2008 rev 1.0 original version hm 10. mar 2009 rev 1.1 faster code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Complex' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CTANH : complex VAR_INPUT X : complex; END_VAR VAR temp : REAL; xi2: REAL; xr2: REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by oscat this function calculates the complex hyperbolictangens *) (* @END_DECLARATION := '0' *) xi2 := 2.0 * x.im; xr2 := 2.0 * x.re; temp := 1.0 / (COSH(xr2) + COS(xi2)); CTANH.re := temp * SINH(xr2); CTANH.im := temp * SIN(xi2); (* revision history hm 21. feb 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 new faster code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Double Precision' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION R2_ABS : REAL2 VAR_INPUT X : REAL2; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias R2_abs returns the absulute value of a double precision real. *) (* @END_DECLARATION := '0' *) IF X.RX >= 0.0 THEN R2_ABS.RX := X.RX; R2_ABS.R1 := X.R1; ELSE R2_ABS.RX := -X.RX; R2_ABS.R1 := -X.R1; END_IF; (* revision history hm 21. mar. 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Double Precision' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION R2_ADD : REAL2 VAR_INPUT X : Real2; Y : REAL; END_VAR VAR temp: REAL; END_VAR (* version 1.0 20. mar. 2008 programmer hugo tested by tobias R2_add adds a real to a double real which extends the accuracy of a real to twice as many digits *) (* @END_DECLARATION := '0' *) temp := X.RX; R2_ADD.RX := Y + X.R1 + X.RX; R2_ADD.R1 := temp - R2_ADD.RX + Y + X.R1; (* revision history hm 20.3.2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Double Precision' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION R2_ADD2 : REAL2 VAR_INPUT X : Real2; Y : REAL2; END_VAR (* version 1.0 20. mar. 2008 programmer hugo tested by tobias R2_add2 adds a double precision real to a double precision real which extends the accuracy of a real to twice as many digits *) (* @END_DECLARATION := '0' *) R2_ADD2.R1 := X.R1 + Y.R1; R2_ADD2.RX := X.RX + Y.RX; (* revision history hm 20.3.2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Double Precision' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION R2_MUL : REAL2 VAR_INPUT X : REAL2; Y : REAL; END_VAR (* version 1.0 20. mar. 2008 programmer hugo tested by tobias R2_mul multiplies a real with a double real which extends the accuracy of a real to twice as many digits *) (* @END_DECLARATION := '0' *) R2_MUL.RX := X.RX * Y; R2_MUL.R1 := X.R1 * Y; (* revision history hm 20.3.2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Double Precision' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION R2_SET : REAL2 VAR_INPUT X : REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias R2_set sets a double precision real to a single real value. *) (* @END_DECLARATION := '0' *) R2_SET.RX := X; R2_SET.R1 := 0.0; (* revision history hm 2. jun. 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/functions' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION F_LIN : REAL VAR_INPUT X : REAL; A : REAL; B : REAL; END_VAR (* version 1.0 1 sep 2006 programmer hugo tested by tobias this function calculates the linear equation f_lin = a*x + b *) (* @END_DECLARATION := '0' *) F_lin := A * X + B; END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/functions' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION F_LIN2 : REAL VAR_INPUT X : REAL; X1: REAL; Y1 : REAL; X2 : REAL; Y2 : REAL; END_VAR (* version 1.0 1 jan 2007 programmer hugo tested by tobias this function calculates the linear equation f_lin = a*x + b given by two points x1/y1 and x2/y2. *) (* @END_DECLARATION := '0' *) F_LIN2 := (Y2 - Y1) / (X2 - X1) * (X - X1) + Y1; (* revision history hm 1. jan. 2007 rev 1.0 original release hm 17. dec. 2008 rev 1.1 optimized formula *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/functions' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION F_POLY : REAL VAR_INPUT X : REAL; C : ARRAY[0..7] OF REAL; END_VAR VAR END_VAR (* version 1.1 18. mar. 2011 programmer hugo tested by tobias this function calculates the polynom C[0] + C[1]*X^1 + C[2]*X^2 * C[3]*X^3 + C[4]*X^4 + C[5]*X^5 + C[6]*X^6 + C[7]*X^7 *) (* @END_DECLARATION := '0' *) F_POLY := ((((((( c[7] * x + c[6] ) * x + c[5] ) * x + c[4] ) * x + c[3] ) * x + c[2] ) * x + c[1] ) * x + c[0] ) ; (* revision history hm 20. may. 2008 rev 1.0 original version hm 18. mar. 2011 rev 1.1 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/functions' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION F_POWER : REAL VAR_INPUT a : REAL; x : REAL; n : REAL; END_VAR (* version 1.0 1 sep 2006 programmer hugo tested by tobias this function calculates the power equation f_power = a*x^n *) (* @END_DECLARATION := '0' *) F_POWER := a * EXPT(X, N); END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/functions' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION F_QUAD : REAL VAR_INPUT X : REAL; A : REAL; B : REAL; C : REAL; END_VAR (* version 1.1 18. Mar. 2011 programmer hugo tested by tobias this function calculates the quadratic equation f_lin = a*x + b *) (* @END_DECLARATION := '0' *) F_QUAD := (A * X + B) * X + C; (* revision history hm 1. sep. 2006 rev 1.0 original version hm 18. mar. 2001 rev 1.1 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/functions' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FRMP_B : BYTE VAR_INPUT START : BYTE; DIR : BOOL; TD : TIME; TR : TIME; END_VAR VAR END_VAR (* version 1.0 17. feb. 2011 programmer hugo tested by oscat this function calculates a ramp and limits the output to 0 .. 255 without overflow problems *) (* @END_DECLARATION := '0' *) IF TD < TR THEN FRMP_B := MIN(DWORD_TO_BYTE(SHL(TIME_TO_DWORD(TD), 8) / TIME_TO_DWORD(TR)), SEL(DIR, START, BYTE#255 - START)); IF DIR THEN FRMP_B := START + FRMP_B; ELSE FRMP_B := START - FRMP_B; END_IF; ELSE FRMP_B := SEL(DIR, 0, 255); END_IF; (* revision history 17. feb. 2011 rev 1.0 new module *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/functions' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_AVG VAR_INPUT IN : REAL; E : BOOL := TRUE; N : INT := 32; RST : BOOL; END_VAR VAR_OUTPUT AVG : REAL; END_VAR VAR buff : DELAY; i: INT; init : BOOL; END_VAR (* version 1.5 10. mar. 2009 programmer hugo tested by oscat this function calculates the moving average over n samples from a sequential input the input values are shifted into an N deep buffer and the avg of this buffer is diplayed at anytime on the output. a rst will load the buffer with the current in value.. *) (* @END_DECLARATION := '0' *) (* limit n to a max of 32 because delay can do max 32 cycles *) buff.N := LIMIT(0, N, 32); IF NOT init OR rst THEN FOR i := 1 TO N DO buff(in := in); END_FOR; avg := in; init := TRUE; ELSIF E THEN buff(in := in); avg := avg + (in - buff.out ) / INT_TO_REAL(N); END_IF; (* revision history hm 7. jan. 2007 rev 1.1 chaged rst logic to load the buffer with the actual input value instead of 0. added en input to allow better control of signal flow added init to load the buffer with in at startup to avoid rampup at beginning. deleted unused variable cnt. hm 14. jun. 2008 rev 1.2 set default for input en = TRUE and N = 32 hm 10. oct. 2008 rev 1.3 improved performance hm 18. oct. 2008 rev 1.4 changed input en to e for compatibility reasons hm 10. mar. 2009 rev 1.5 added type conversion for compatibility reasons *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/functions' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_MIN_MAX VAR_INPUT in : REAL; rst : BOOL; END_VAR VAR_OUTPUT mx : REAL; mn : REAL; END_VAR VAR init: BOOL; END_VAR (* version 1.0 1 sep 2006 programmer hugo tested by tobias this function block stores the min and max value of an input signal. when rst is true the mn and mx outputs are set to the in value. when a rst is never active the function autoresets to the in value at startup. since the input might not be present at first cycle the mn and mx are set during second cycle. *) (* @END_DECLARATION := '0' *) IF rst OR NOT init THEN mn := in; mx := in; init := TRUE; ELSIF in < mn THEN mn := in; ELSIF in > mx THEN mx := in; END_IF; END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/functions' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FT_RMP VAR_INPUT Rmp : BOOL := TRUE; in : REAL; KR : REAL; KF : REAL; END_VAR VAR_OUTPUT out : REAL; busy : BOOL; UD : BOOL; END_VAR VAR tx: TIME; last: TIME; init: BOOL; END_VAR (* version 1.4 25 jan 2008 programmer oscat tested BY oscat this ramp function follows an input signal with a linear ramp up or down, the up or down speed can be set with KF and KR. a K factor of 1 means 1 unit per second on the output. K factors can only be positive and not negative. a busy output signals the ramp is running or busy false means the in value is present on the output. a rmp input false means the output follows the input dierctly while a rmp = true means the output follows the input with a ramp. a updn output signal the directon of the ramp (up or down). *) (* @END_DECLARATION := '0' *) (* read system time *) tx := DWORD_TO_TIME(T_PLC_MS()) - last; IF NOT init THEN init := TRUE; last := tx; tx := t#0s; out := in; END_IF; IF NOT rmp THEN out := in; busy := FALSE; ELSIF out > in THEN (* ramp down *) out := out - TIME_TO_REAL(tx) * 0.001 * KF; out := MAX(in, out); ELSIF out < in THEN (* ramp up *) out := out + TIME_TO_REAL(tx) * 0.001 * KR; out := MIN(in, out); END_IF; (* set busy and dir flags *) IF out < in THEN busy := TRUE; ud := TRUE; ELSIF out > in THEN busy := TRUE; ud := FALSE; ELSE busy := FALSE; END_IF; last := last + tx; (* revision history: hm 8.10.2006 rev 1.1 added ud output hm 12. feb 2007 rev 1.2 added init variable and corrected a possible startup problem hm 17. sep 2007 rev 1.3 replaced time() with t_plc_ms() for compatibility reasons hm 25. jan 2008 rev 1.4 performance improvements allow KR and KF to be 0 *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/functions' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LINEAR_INT : REAL VAR_INPUT X : REAL; XY : ARRAY[1..20,0..1] OF REAL; Pts : INT; END_VAR VAR i : INT; END_VAR (* version 1.1 27 dec 2007 programmer oscat tested BY oscat LINEAR_INT calculates an output based on a linear interpolation of up to 20 coordinates given in an array the input coordinates have to start at the lowest array position and must be sorted ba ascending X values. *) (* @END_DECLARATION := '0' *) (*make sure n is bound within the array size *) pts := MIN(pts,20); (* calculate the linear segement interpolation *) i := 2; (* search for segment and calculate output below and above the defined segments we interpolate the last segment *) WHILE (i < pts) AND (XY[i,0] < X) DO i := i + 1; END_WHILE; (* calculate the output value on the corresponding segment coordinates *) LINEAR_INT := ((XY[i,1] - XY[i-1,1]) * X - XY[i,1] * XY[i-1,0] + XY[i-1,1] * XY[i,0]) / (XY[i,0] - XY[i-1,0]); (* revision history hm 7. oct 2007 rev 1.0 original version hm 27 dec 2007 rev 1.1 changed code for better performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/functions' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION POLYNOM_INT : REAL VAR_INPUT X : REAL; XY : ARRAY[1..5,0..1] OF REAL; Pts : INT; END_VAR VAR I, J : INT; stop: INT; END_VAR (* version 1.3 10. mar. 2009 programmer oscat tested BY oscat POLYNOM_INT calculates an output based on a Polynom interpolation of up to 5 coordinates given in an array the indut coordinates have to start at the lowest array position and must be sorted ba ascending X values. *) (* @END_DECLARATION := '0' *) (*make sure n is bound within the array size *) pts := MIN(pts, 5); (* this part is only to calculate the polynom parameters, which are then stores in the Y array the array values, it is not needed during runtime, unless the parameters will change during runtime the remaining code without this setup code can be used within a function to calculate specific functions the content of the arrays is then used as constant values within the function *) FOR i := 1 TO pts DO stop := i + 1; FOR j := pts TO stop BY -1 DO XY[j,1] := (XY[j,1] - XY[j-1,1]) / (XY[j,0] - XY[j-i,0]); END_FOR; END_FOR; (* this part is the actual calculation *) POLYNOM_INT := 0.0; FOR i := pts TO 1 BY -1 DO POLYNOM_INT := POLYNOM_INT * (X - XY[i,0]) + XY[i,1]; END_FOR; (* revision history hm 8. okt 2007 rev 1.0 original version hm 17. dec 2007 rev 1.1 init makes no sense for a function hm 22. feb 2008 rev 1.2 improved performance hm 10. mar. 2009 rev 1.3 changed syntax of real constants to 0.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Geometry' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CIRCLE_A : REAL VAR_INPUT rx : REAL; ax : REAL; END_VAR (* version 1.2 22. feb 2008 programmer hugo tested by tobias circle_A calculates the Area of a circle segement if ax = 360 the whole circle is calculated *) (* @END_DECLARATION := '0' *) circle_A := Rx * RX * 8.726646E-3 * Ax; (* revision histroy hm 16. oct 2007 rev 1.0 original version hm 4. dec 2007 rev 1.1 changed code for better performance hm 22. feb 2008 rev 1.2 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Geometry' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CIRCLE_C : REAL VAR_INPUT Rx : REAL; Ax : REAL; END_VAR (* version 1.1 22 feb 2008 programmer hugo tested by tobias circle_C calculates the Circumference of a circle if ax = 360 the whole circle is calculated *) (* @END_DECLARATION := '0' *) circle_C := 1.7453293E-2 * Rx * Ax; (* revision histroy hm 16. oct 2007 rev 1.0 original version hm 22. feb 2008 rev 1.1 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Geometry' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CIRCLE_SEG : REAL VAR_INPUT RX : REAL; HX : REAL; END_VAR (* version 1.0 10. Mar 2010 programmer hugo tested by tobias CIRCLE_SEG calculates the Area of a circle segement enclosed between a sectant line and the circumference. *) (* @END_DECLARATION := '0' *) IF RX > 0.0 THEN CIRCLE_SEG := 2.0 * ACOS(1.0 - LIMIT(0.0, HX / RX, 2.0)); CIRCLE_SEG := (CIRCLE_SEG - SIN(CIRCLE_SEG)) *RX * RX / 2.0; END_IF; (* revision histroy hm 10. mar 2010 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Geometry' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CONE_V : REAL VAR_INPUT rx : REAL; hx : REAL; END_VAR (* version 1.1 4 dec 2007 programmer hugo tested by tobias cone_V calculates the Volume of a cone *) (* @END_DECLARATION := '0' *) cone_V := 1.047197551 * RX * RX * hx; (* revision histroy hm 17. oct 2007 rev 1.0 original version hm 4. dec 2007 rev 1.1 changed code for better performance hm 22. feb 2008 rev 1.2 changed code for better performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Geometry' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ELLIPSE_A : REAL VAR_INPUT R1 : REAL; R2 : REAL; END_VAR (* version 1.1 18. oct. 2008 programmer hugo tested by oscat ellipse_A calculates the Area of an ellipe based on the two radians R1 and R2 *) (* @END_DECLARATION := '0' *) ELLIPSE_A := math.PI * R1 * R2; (* revision histroy hm 16. oct 2007 rev 1.0 original version hm 18. oct. 2008 rev 1.1 using math constants *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Geometry' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ELLIPSE_C : REAL VAR_INPUT R1 : REAL; R2 : REAL; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by oscat ellipse_C calculates the circumference of an ellipe based on the two radians R1 and R2 *) (* @END_DECLARATION := '0' *) ELLIPSE_C := math.PI * (3.0 * (R1+R2) - SQRT((3.0 * r1 + R2) * (3.0 * r2 + r1))); (* revision histroy hm 16. oct 2007 rev 1.0 original version hm 18. oct. 2008 rev 1.1 using math constants hm 10. mar. 2009 rev 1.2 changed syntax of real constants to 0.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Geometry' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SPHERE_V : REAL VAR_INPUT rx : REAL; END_VAR (* version 1.2 22. feb 2008 programmer hugo tested by tobias sphere_V calculates the Volume of a Sphere *) (* @END_DECLARATION := '0' *) sphere_V := 4.188790205 * Rx * RX * RX; (* revision histroy hm 16. oct 2007 rev 1.0 original version hm 4. dec 2007 rev 1.1 changed code for better performance hm 22. feb 2008 rev 1.2 changed code for better performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Geometry' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TRIANGLE_A : REAL VAR_INPUT S1 : REAL; A : REAL; S2 : REAL; S3 : REAL; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by tobias triangle_A calculates the Area of a triangle. if any input is 0 it will be neglected and the are will be calculated from eiter: two sides and an angle (s1 and S2 and the angle A1) or 3 sides. *) (* @END_DECLARATION := '0' *) IF A = 0.0 THEN TRIANGLE_A := SQRT((s1+s2+S3) * (s1+s2-S3) * (S2+S3-S1) * (S3+S1-S2)) * 0.25; ELSE TRIANGLE_A := S1 * S2 * SIN(RAD(A)) * 0.5; END_IF; (* revision histroy hm 16. oct 2007 rev 1.0 original version hm 22. feb 2008 rev 1.1 improved performance hm 10. mar. 2009 rev 1.2 changed syntax of real constants to 0.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_ABS : REAL VAR_INPUT A : Vector_3; END_VAR (* version 1.0 11 dec 2007 programmer hugo tested by tobias this function calculates the length of a vectors in a 3 dimensional space *) (* @END_DECLARATION := '0' *) V3_ABS := SQRT(A.X * A.X + A.Y * A.Y + A.Z * A.Z); (* revision history hm 11 dec 2007 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_ADD : vector_3 VAR_INPUT A : Vector_3; B : Vector_3; END_VAR (* version 1.0 11 dec 2007 programmer hugo tested by tobias this function adds two vectors in a 3 dimensional space *) (* @END_DECLARATION := '0' *) V3_ADD.X := A.X + B.X; V3_ADD.Y := A.Y + B.Y; V3_ADD.Z := A.Z + B.Z; (* revision history hm 11 dec 2007 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_ANG : REAL VAR_INPUT A : Vector_3; B : Vector_3; END_VAR VAR d : REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function calculates the angle between two vectors in a 3 dimensional space *) (* @END_DECLARATION := '0' *) d := V3_ABS(A) * V3_ABS(B); IF d > 0 THEN V3_ANG := ACOS(LIMIT(-1.0, V3_DPRO(A, B) / d,1.0)); END_IF; (* revision history hm 11. dec. 2007 rev 1.0 original version hm 10. mar. 2009 rev 1.1 changed syntax of real constants to 0.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_DPRO : REAL VAR_INPUT A : Vector_3; B : Vector_3; END_VAR (* version 1.0 11 dec 2007 programmer hugo tested by tobias this function calculates the dot product of two vectors in 3 dimensional space *) (* @END_DECLARATION := '0' *) V3_DPRO := A.X * B.X + A.Y * B.Y + A.Z * B.Z; (* revision history hm 11 dec 2007 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_NORM : vector_3 VAR_INPUT A : Vector_3; END_VAR VAR la: REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function generates a vectors with absolute length 1 from a vector A in a 3 dimensional space *) (* @END_DECLARATION := '0' *) la := V3_ABS(A); IF la > 0.0 THEN V3_NORM := V3_SMUL(A, 1.0 / la); END_IF; (* revision history hm 11 dec 2007 rev 1.0 original version hm 10. mar. 2009 rev 1.1 changed syntax of real constants to 0.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_NUL : BOOL VAR_INPUT A : Vector_3; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function checks if a vectors in a null Vector *) (* @END_DECLARATION := '0' *) V3_NUL := A.X = 0.0 AND A.Y = 0.0 AND A.Z = 0.0; (* revision history hm 12 dec 2007 rev 1.0 original version hm 10. mar. 2009 rev 1.1 changed syntax of real constants to 0.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_PAR : BOOL VAR_INPUT A : Vector_3; B : Vector_3; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function checks if two vectors in a 3 dimensional space are parallel. which means the two vectors have the same direction *) (* @END_DECLARATION := '0' *) V3_PAR := V3_ABS(V3_XPRO(A, B)) = 0.0; (* revision history hm 11 dec 2007 rev 1.0 original version hm 10. mar. 2009 rev 1.1 changed syntax of real constants to 0.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_REV : vector_3 VAR_INPUT A : Vector_3; END_VAR (* version 1.0 11 dec 2007 programmer hugo tested by tobias this function reverses a vector in a 3 dimensional space *) (* @END_DECLARATION := '0' *) V3_REV.X := -A.X; V3_REV.Y := -A.Y; V3_REV.Z := -A.Z; (* revision history hm 11 dec 2007 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_SMUL : vector_3 VAR_INPUT A : Vector_3; M : REAL; END_VAR (* version 1.0 11 dec 2007 programmer hugo tested by tobias this function multiplies a vectors in a 3 dimensional space with a skalar M *) (* @END_DECLARATION := '0' *) V3_SMUL.X := A.X * M; V3_SMUL.Y := A.Y * M; V3_SMUL.Z := A.Z * M; (* revision history hm 11 dec 2007 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_SUB : vector_3 VAR_INPUT A : Vector_3; B : Vector_3; END_VAR (* version 1.0 11 dec 2007 programmer hugo tested by tobias this function subtracts two vectors in a 3 dimensional space V3_SUB = A - B *) (* @END_DECLARATION := '0' *) V3_SUB.X := A.X - B.X; V3_SUB.Y := A.Y - B.Y; V3_SUB.Z := A.Z - B.Z; (* revision history hm 11 dec 2007 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_XANG : REAL VAR_INPUT A : Vector_3; END_VAR VAR la: REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function calculates the angle between the X-axis and a vectors in a 3 dimensional space *) (* @END_DECLARATION := '0' *) la := V3_ABS(a); IF la > 0.0 THEN V3_XANG := ACOS(A.X / la); END_IF; (* revision history hm 11 dec 2007 rev 1.0 original version hm 10. mar. 2009 rev 1.1 changed syntax of real constants to 0.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_XPRO : vector_3 VAR_INPUT A : Vector_3; B : Vector_3; END_VAR (* version 1.0 11 dec 2007 programmer hugo tested by tobias this function adds two vectors in a 3 dimensional space *) (* @END_DECLARATION := '0' *) V3_XPRO.X := A.Y * B.Z - A.Z * B.Y; V3_XPRO.Y := A.Z * B.X - A.X * B.Z; V3_XPRO.Z := A.X * B.Y - A.Y * B.X; (* revision history hm 11 dec 2007 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_YANG : REAL VAR_INPUT A : Vector_3; END_VAR VAR la: REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function calculates the angle between the Y-axis and a vectors in a 3 dimensional space *) (* @END_DECLARATION := '0' *) la := V3_ABS(a); IF la > 0.0 THEN V3_YANG := ACOS(A.Y / la); END_IF; (* revision history hm 11 dec 2007 rev 1.0 original version hm 10. mar. 2009 rev 1.1 changed syntax of real constants to 0.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical\/Vektormathematik' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION V3_ZANG : REAL VAR_INPUT A : Vector_3; END_VAR VAR la: REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function calculates the angle between the Z-axis and a vectors in a 3 dimensional space *) (* @END_DECLARATION := '0' *) la := V3_ABS(a); IF la > 0.0 THEN V3_ZANG := ACOS(A.Z / la); END_IF; (* revision history hm 11 dec 2007 rev 1.0 original version hm 10. mar. 2009 rev 1.1 changed syntax of real constants to 0.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ACOSH : REAL VAR_INPUT X : REAL; END_VAR (* version 1.3 10. mar. 2009 programmer hugo tested by tobias this function calculates the arcus cos hyperbolicus *) (* @END_DECLARATION := '0' *) ACOSH := LN(SQRT(X * X - 1.0) + X); (* revision history hm 12 jan 2007 rev 1.0 original version hm 2. dec 2007 rev 1.1 changed code for better performance hm 16. mar. 2007 rev 1.2 changed sequence of calculations to improve performance hm 10. mar. 2009 rev 1.3 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ACOTH : REAL VAR_INPUT X : REAL; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by tobias this function calculates the arcus cotangens hyperbolicus *) (* @END_DECLARATION := '0' *) ACOTH := LN((x+1.0)/(x-1.0))*0.5; (* revision history hm 12 jan 2007 rev 1.0 original version hm 5. jan 2008 rev 1.1 improved code for better performance hm 10. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION AGDF : REAL VAR_INPUT X : REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function calculates the inverse Gudermannian function. *) (* @END_DECLARATION := '0' *) AGDF := LN((1.0 + SIN(X)) / COS(X)); (* comment the current implementation gives sufficient accuracy only up to X = 1.57 or an output > 10. is X closer to PI/2 then the function is more and more unreliable *) (* revision history hm 27. apr. 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ASINH : REAL VAR_INPUT X : REAL; END_VAR (* version 1.3 10. mar 2009 programmer hugo tested by tobias this function calculates the arcus sin hyperbolicus *) (* @END_DECLARATION := '0' *) ASINH := LN(SQRT(X * X + 1.0)+X); (* revision history hm 12 jan 2007 rev 1.0 original version hm 2. dec 2007 rev 1.1 changed code for better performance hm 16.3. 2007 rev 1.2 changed sequence of calculations to improve performance hm 10. mar. 2009 rev 1.3 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ATAN2 : REAL VAR_INPUT Y : REAL; X : REAL; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by oscat this function calculates the angle in a coordinate system in rad. *) (* @END_DECLARATION := '0' *) IF X > 0.0 THEN ATAN2 := ATAN(Y/X); ELSIF X < 0.0 THEN IF Y >=0.0 THEN ATAN2 := ATAN(Y/X) + MATH.PI; ELSE ATAN2 := ATAN(Y/X) - MATH.PI; END_IF; ELSIF Y > 0.0 THEN ATAN2 := MATH.PI05; ELSIF Y < 0.0 THEN ATAN2 := -MATH.PI05; ELSE ATAN2 := 0.0; END_IF; (* revision history hm 20. apr. 2008 rev 1.0 original version hm 18. oct. 2008 rev 1.1 changed to use math constants hm 10. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ATANH : REAL VAR_INPUT X : REAL; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by tobias this function calculates the arcus tangens hyperbolicus *) (* @END_DECLARATION := '0' *) ATANH := LN((1.0 + x)/(1.0 - x)) * 0.5; (* revision history hm 12 jan 2007 rev 1.0 original version hm 5. jan 2008 rev 1.1 improved code for better performance hm 10. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BETA : REAL VAR_INPUT X : REAL; Y : REAL; END_VAR (* version 1.0 26. oct. 2008 programmer hugo tested by oscat this function calculates the beta function for real number > 0. *) (* @END_DECLARATION := '0' *) BETA := GAMMA(X) * GAMMA(Y) / GAMMA(x + y); (* revision history hm 26. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BINOM : DINT VAR_INPUT N : INT; K : INT; END_VAR VAR i: INT; END_VAR (* version 1.0 25. oct. 2008 programmer hugo tested by tobias this function calculates the binomialkoefficient (N over k) *) (* @END_DECLARATION := '0' *) IF 2 * K > n THEN k := n - k; END_IF; IF k > n THEN RETURN; ELSIF k = 0 OR k = n THEN BINOM := 1; ELSIF k = 1 THEN BINOM := n; ELSE BINOM := n; n := n + 1; FOR i := 2 TO k DO BINOM := BINOM * (n - i) / i; END_FOR; END_IF; (* binomialkoeffizient(n, k) 1 wenn k = 0 return 1 2 wenn 2k > n 3 dann führe aus ergebnis \leftarrow binomialkoeffizient(n, n-k) 4 sonst führe aus ergebnis \leftarrow n 5 von i \leftarrow 2 bis k 6 führe aus ergebnis \leftarrow ergebnis \cdot (n + 1 - i) 7 ergebnis \leftarrow ergebnis : i 8 return ergebnis *) (* revision history hm 25. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CAUCHY : REAL VAR_INPUT X : REAL; T : REAL; U : REAL; END_VAR VAR tmp: REAL; END_VAR (* version 1.0 26. oct. 2008 programmer hugo tested by tobias this function calculates the Cauchy distribution function *) (* @END_DECLARATION := '0' *) tmp := x - t; CAUCHY := math.PI_INV * U / (U*U + tmp*tmp); (* revision hisdtory hm 26. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CAUCHYCD : REAL VAR_INPUT X : REAL; T : REAL; U : REAL; END_VAR (* version 1.0 26. oct. 2008 programmer hugo tested by tobias this function calculates the Cauchy distribution function *) (* @END_DECLARATION := '0' *) CAUCHYCD := 0.5 + math.PI_INV * ATAN((X - T) / U); (* revision hisdtory hm 26. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CEIL : INT VAR_INPUT X : REAL; END_VAR (* version 1.1 21. mar. 2008 programmer hugo tested by tobias This is a rounding function which returns the smallest possible integer which is greater or equal to X. ceil(3.14) = 4 ceil(-3.14) = -3 *) (* @END_DECLARATION := '0' *) CEIL := REAL_TO_INT(x); IF CEIL < X THEN CEIL := CEIL + 1; END_IF; (* revision history hm 7. feb. 2007 rev 1.0 original version hm 21. mar. 2008 rev 1.1 use REAL_TO_INT instead of trunc for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CEIL2 : DINT VAR_INPUT X : REAL; END_VAR (* version 1.3 10. mar. 2009 programmer hugo tested by tobias This is a rounding function which returns the smallest possible integer which is greater or equal to X. ceil2(3.14) = 4 ceil2(-3.14) = -3 *) (* @END_DECLARATION := '0' *) CEIL2 := REAL_TO_DINT(X); IF DINT_TO_REAL(CEIL2) < X THEN CEIL2 := CEIL2 + 1; END_IF; (* revision history hm 21. mar. 2008 rev 1.0 original version hm 4. apr. 2008 rev 1.1 added type conversion to avoid warnings under codesys 3.0 hm 30. jun. 2008 rev 1.2 added type conversion to avoid warnings under codesys 3.0 hm 10. mar. 2009 rev 1.3 use correct statement real_to_DINT *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CMP : BOOL VAR_INPUT X,Y : REAL; N : INT; END_VAR VAR tmp : REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function checks two inputs x and y if they are identical with the first N digits example : cmp(3.141516,3.141517,6 is true. *) (* @END_DECLARATION := '0' *) tmp := ABS(x); IF tmp > 0.0 THEN tmp := EXP10(INT_TO_REAL(FLOOR(LOG(tmp))-N+1)); ELSE tmp := EXP10(tmp); END_IF; CMP := ABS(X - Y) < tmp; (* revision history hm 12. mar. 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 added type conversion for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION COSH : REAL VAR_INPUT X : REAL; END_VAR VAR t: REAL; END_VAR (* version 1.3 10. mar. 2009 programmer hugo tested by tobias this function calculates the cos hyperbolicus *) (* @END_DECLARATION := '0' *) T := EXP(X); COSH := (1.0 / T + T) * 0.5; (* revision histroy hm 12.1.2007 rev 1.0 original version hm 1.12.2007 rev 1.1 changed code to improve performance hm 5. jan 2008 rev 1.2 further performance improvement hm 10. mar. 2009 rev 1.3 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION COTH : REAL VAR_INPUT X : REAL; END_VAR (* version 1.4 10. mar. 2009 programmer hugo tested by tobias this function calculates the cotangens hyperbolicus *) (* @END_DECLARATION := '0' *) IF X > 20.0 THEN COTH :=1.0; ELSIF X < -20.0 THEN COTH := -1.0; ELSE COTH := 1.0 + 2.0 / (EXP(X * 2.0) - 1.0); END_IF; (* revision histroy hm 12.1.2007 rev 1.0 original version hm 1.12.2007 rev 1.1 changed code to improve performance hm 8. jan 2008 rev 1.2 further improvement in performance hm 10. mar 2008 rev 1.3 extended range of valid inputs to +/- INV changed formula for better accuracy hm 10. mar. 2009 rev 1.4 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION D_TRUNC : DINT VAR_INPUT X : REAL; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by oscat d_trunc truncates a real to a dint 1.5 will be 1 and -1.5 will be -1 d_trunc is necessary because many systems do not offer a trunc to a dint also real_to_dint will not deliver the same result on different systems *) (* @END_DECLARATION := '0' *) D_TRUNC := REAL_TO_DINT(X); IF X > 0.0 THEN IF DINT_TO_REAL(D_TRUNC) > X THEN D_TRUNC := D_TRUNC - 1; END_IF; ELSE IF DINT_TO_REAL(D_TRUNC) < X THEN D_TRUNC := D_TRUNC + 1; END_IF; END_IF; (* for systems that support a dint truncation this routine can be replaced by trunc() *) (* revision history hm 21. mar. 2008 rev 1.0 original version hm 31. oct. 2008 rev 1.1 optimized performance hm 10. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DEC1 : INT VAR_INPUT X : INT; N : INT; END_VAR (* version 1.1 27. oct. 2008 programmer hugo tested by oscat This is a decrement function which decrements the variable X by 1 and if 0 is reached, it begins with N-1 again. _dec1(X,3) will generate 2,1,0,2,... *) (* @END_DECLARATION := '0' *) IF X = 0 THEN DEC1 := N - 1; ELSE DEC1 := X - 1; END_IF; (* this is a very elegant version but 50% slower X := (X - 1 + N) MOD N; *) (* revision history hm 13. oct. 2008 rev 1.0 original version hm 27. oct. 2008 rev 1.1 added statement to return value for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DEG : REAL VAR_INPUT rad : REAL; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by tobias this function converts degrees into Radiant execution time on wago 750 - 841 = 10 us *) (* @END_DECLARATION := '0' *) DEG := MODR(57.29577951308232 * RAD, 360.0); (* revision history hm 4. aug 2006 rev 1.0 original version hm 16. oct 2007 rev 1.1 added modr statement which prohibits deg to become bigger than 360 hm 10. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DIFFER : BOOL VAR_INPUT in1 : REAL; in2 : REAL; X : REAL; END_VAR (* version 1.1 16 mar 2008 programmer hugo tested by tobias this function checks if in1 differs more then x from in2 the output is true if abs(in1-in2) > X *) (* @END_DECLARATION := '0' *) DIFFER := ABS(in1 - in2) > X; (* revision history hm 8. oct 2006 rev 1.0 original version hm 16. mar 2008 rev 1.1 improverd code for performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ERF : REAL VAR_INPUT X : REAL; END_VAR VAR x2 : REAL; ax2 : REAL; END_VAR (* version 1.3 10. mar. 2009 programmer hugo tested by tobias this function calculates the erf (error) function. *) (* @END_DECLARATION := '0' *) x2 := X*X; ax2 := 0.147 * x2 + 1.0; ERF := SQRT(1.0 - EXP(-X2 * ((0.27323954473516 + aX2)/(ax2)))) * SGN(x); (* revision history hm 7. apr. 2008 rev 1.0 original version hm 30. jun. 2008 rev 1.1 added type conversions to avoid warnings under codesys 3.0 hm 25. oct. 2008 rev 1.2 new code using new algorithm hm 10. mar. 2009 rev 1.3 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ERFC : REAL VAR_INPUT X : REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function calculates the inverse erf (error) function. *) (* @END_DECLARATION := '0' *) ERFC := 1.0 - ERF(X); (* revision history hm 7. apr. 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION EVEN : BOOL VAR_INPUT in : DINT; END_VAR (* version 1.1 1 dec 2007 programmer hugo tested by tobias this function chacks an input for even value the output is true if the input is even. execution time on wago 750 - 841 = 10 us *) (* @END_DECLARATION := '0' *) EVEN := NOT in.0; (* revision history hm 1. oct 2006 rev 1.0 ORIGINAL VERSION hm 01.12.2007 rev 1.1 changed code for improved performance hm 21. mar. 2008 rev 1.2 changed type of input IN from INT to DINT *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION EXP10 : REAL VAR_INPUT X : REAL; END_VAR (* version 1.0 2 dec 2007 programmer hugo tested by tobias this function calculates the exponent to the basis 10 exp10(3) = 1000 *) (* @END_DECLARATION := '0' *) EXP10 := EXP(X * 2.30258509299405); (* revision history hm 2. dec 2007 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION EXPN : REAL VAR_INPUT X : REAL; N : INT; END_VAR VAR sign: BOOL; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by oscat this function calculates X to the power of N (Y = X^N) whilke N is an integer especially on CPU's without a floating point unit this algorythm is about 30 times faster then the IEC standard EXPT() Function *) (* @END_DECLARATION := '0' *) sign := n.15; N := ABS(N); IF N.0 THEN EXPN := X; ELSE EXPN := 1.0; END_IF; N := SHR(N,1); WHILE N > 0 DO X := X * X; IF N.0 THEN EXPN := EXPN * X; END_IF; N := SHR(N,1); END_WHILE; IF sign THEN EXPN := 1.0 / EXPN; END_IF; (* revision history hm 4. dec 2007 rev 1.0 original version hm 22. oct. 2008 rev 1.1 optimized code hm 10. mar. 2009 rev 1.2 removed nested comments real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FACT : DINT VAR_INPUT X : INT; END_VAR (* version 1.5 26. mar. 2011 programmer hugo tested by tobias this function calculates the factorial of x if the input is negative or higher then 12 the output will be -1. *) (* @END_DECLARATION := '0' *) IF X >= 0 AND X <= 12 THEN FACT := math.FACTS[X]; ELSE FACT := -1; END_IF; (* working code without array IF X > 12 OR X < 0 THEN FACT := -1; ELSIF X < 7 THEN FACT := 1; FOR i := 2 TO X DO FACT := FACT * i; END_FOR; ELSE FACT := 5040; FOR i := 8 TO X DO FACT := FACT * i; END_FOR; END_IF; *) (* revision history hm 4.3.2007 rev 1.1 changed a critical error where while loop was indefinite. hm 10.12.2007 rev 1.2 start value for i has changed to 2 for better performance hm 10. mar 2008 rev 1.3 changed output of fact to dint to allow bigger values hm 27. oct. 2008 rev 1.4 optimized code hm 26. mar. 2011 rev 1.5 using array math.facts *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FIB : DINT VAR_INPUT X : INT; END_VAR VAR t1, t2: DINT; END_VAR (* version 1.3 26. mar. 2008 programmer hugo tested by tobias this function calculates the fibonacci sequence *) (* @END_DECLARATION := '0' *) IF X < 0 OR X > 46 THEN FIB := -1; ELSIF x < 2 THEN FIB := X; RETURN; ELSE (* the fibonacci number is the sum of the two suceeding fibonaci numbers *) (* we store the numbers alternatively in t1 and t2 depending on pt *) t2 := 1; WHILE X > 3 DO X := X-2; t1 := t1 + t2; t2 := t1 + t2; END_WHILE; IF X > 2 THEN t1 := t1 + t2; END_IF; fib := t1 + t2; END_IF; (* alternative code for very big numbers IF X < 31 THEN fib := 0.4472136 * (expn(1.618034,X) - expn(-0.618034,X)); ELSE fib := 0.4472133 * expn(1.618034,X); END_IF; *) (* revision history hm 27. dec 2007 rev 1.0 original version hm 2. jan 2008 rev 1.1 deleted unused variable pt hm 10. mar 2008 rev 1.2 changed output to dint instead of real hm 26. mar. 2008 rev 1.3 function now returns -1 for input < 0 or > 46 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FLOOR : INT VAR_INPUT X : REAL; END_VAR (* version 1.1 21. mar. 2008 programmer hugo tested by tobias This is a rounding function which returns the biggest possible integer which is less or equal to X. floor(3.14) = 3 floor(-3.14) = -4 *) (* @END_DECLARATION := '0' *) FLOOR := REAL_TO_INT(X); IF FLOOR > X THEN FLOOR := FLOOR - 1; END_IF; (* revision history hm 7. feb 2007 rev 1.0 originlal version hm 21. mar. 2008 rev 1.1 replaced trunc with real_to_int for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FLOOR2 : DINT VAR_INPUT X : REAL; END_VAR (* version 1.1 4. apr. 2008 programmer hugo tested by tobias This is a rounding function which returns the biggest possible integer which is less or equal to X. floor2(3.14) = 3 floor2(-3.14) = -4 *) (* @END_DECLARATION := '0' *) FLOOR2 := REAL_TO_DINT(X); IF DINT_TO_REAL(FLOOR2) > X THEN FLOOR2 := FLOOR2 - 1; END_IF; (* revision history hm 21. mar. 2008 rev 1.0 originlal version hm 4. apr. 2008 rev 1.1 added type conversion to avoid warnings under codesys 3.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FRACT : REAL VAR_INPUT x : REAL; END_VAR (* version 1.3 10. mar. 2009 programmer hugo tested by tobias this function returns the fraction of a real number fract(3.14) = 0.14 *) (* @END_DECLARATION := '0' *) IF ABS(x) < 2.0E9 THEN FRACT := x - DINT_TO_REAL(D_TRUNC(x)); ELSE FRACT := 0.0; END_IF; (* revision history hm 4. aug 2006 rev 1.0 original version hm 11. mar 2008 rev 1.1 added dint_to_real for compatibility reasons now returns 0 for number > 2e9 changed input to x hm 21. mar. 2008 rev 1.2 use D_trunc instead of TRUNC for compatibility reasons hm 10. mar. 2009 rev 1.3 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION GAMMA : REAL VAR_INPUT X : REAL; END_VAR (* version 1.3 10. mar. 2009 programmer hugo tested by oscat this function calculates the stirling function which is an approximation for the gamma function *) (* @END_DECLARATION := '0' *) IF x > 0.0 THEN GAMMA := SQRT(math.PI2 / X) * EXPT(math.E_INV * (x + 1.0 / (12.0 * x - 0.1 / X)), X); END_IF; (* the stirling formula is not very accurate for small values of X IF X >=0 THEN GAMMA := SQRT(math.PI2 * X) * EXPT(X / math.E, X); END_IF; *) (* revision history hm 10.12.2007 rev 1.0 original version hm 18. oct. 2008 rev 1.1 using math constants hm 26. oct. 2008 rev 1.2 using new formula with better accuracy hm 10. mar. 2009 rev 1.3 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION GAUSS : REAL VAR_INPUT X : REAL; U : REAL; SI: REAL; END_VAR VAR temp: REAL; si_inv: REAL; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by tobias this function calculates the gaussian density function *) (* @END_DECLARATION := '0' *) temp := X - U; si_inv := 1.0 / si; GAUSS := EXP(Temp * Temp * si_inv * si_inv * - 0.5) * 0.39894228 * si_inv; (* revision hisdtory hm 6. apr. 2008 rev 1.0 original version hm 27. oct. 2008 rev 1.1 optimized performance hm 10. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION GAUSSCD : REAL VAR_INPUT X : REAL; U : REAL; SI: REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function calculates the gaussian cumulative distribution function *) (* @END_DECLARATION := '0' *) GAUSSCD := (ERF((X - U) / (SI * 1.414213562)) + 1.0) * 0.5; (* revision hisdtory hm 6. apr. 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION GCD : INT VAR_INPUT A : DINT; B : DINT; END_VAR VAR t: DINT; END_VAR (* version 1.0 19. jan. 2011 programmer hugo tested by tobias this function calculates the gretaest common divisor of two numbers A and B *) (* @END_DECLARATION := '0' *) IF A = 0 THEN GCD := DINT_TO_INT(ABS(B)); ELSIF B = 0 THEN GCD := DINT_TO_INT(ABS(A)); ELSE A := ABS(A); B := ABS(B); GCD := 1; WHILE NOT(A.0 OR B.0) DO A := SHR(A,1); B := SHR(B,1); GCD := SHL(GCD,1); END_WHILE; WHILE A > 0 DO IF NOT(A.0) THEN A := SHR(A,1); ELSIF NOT(B.0) THEN B := SHR(B,1); ELSE t:= SHR(ABS(A-B),1); IF A < B THEN B := t; ELSE A := T; END_IF; END_IF; END_WHILE; GCD := GCD * DINT_TO_INT(B); END_IF; (* revision history hm 19. jan. 2011 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION GDF : REAL VAR_INPUT X : REAL; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by tobias this function calculates the Gudermannian function. *) (* @END_DECLARATION := '0' *) IF X = 0.0 THEN GDF := 0.0; ELSIF X > 15.0 THEN GDF := math.PI05; ELSIF X < -15.0 THEN GDF := -math.PI05; ELSE GDF := ATAN(EXP(X)) * 2.0 - math.PI05; END_IF; (* revision history hm 27. apr. 2008 rev 1.0 original version hm 18. oct. 2008 rev 1.1 using math constants hm 10. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION GOLD : REAL VAR_INPUT X : REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias this function calculates the golden function. *) (* @END_DECLARATION := '0' *) GOLD := (X + SQRT(X*X + 4.0)) * 0.5; (* revision history hm 27. apr. 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION HYPOT : REAL VAR_INPUT X : REAL; Y : REAL; END_VAR (* version 1.0 21 feb 2008 programmer hugo tested by oscat this function calculates the pythagorean function *) (* @END_DECLARATION := '0' *) HYPOT := SQRT(x*x + y*y); (* revision history hm 21. feb 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION INC : INT VAR_INPUT X : INT; D : INT; M : INT; END_VAR (* version 1.1 15. jan 2008 programmer hugo tested by oscat This is a increment function which increments the input X by the value D and compares the result with M. if the output exceeds M it will continue to count from 0 again. *) (* @END_DECLARATION := '0' *) INC := (X + D + M + 1) MOD (M + 1); (* revision history hm 7. feb 2007 REV 1.0 original version hm 15. jan 2008 rev 1.1 allow for neagtive increment *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION INC1 : INT VAR_INPUT X : INT; N : INT; END_VAR (* version 1.2 23. feb. 2009 programmer hugo tested by oscat This is a increment function which increments the variable X by 1 and if N is reached, it begins with 0 instead of N again. inc1(X,3) will generate 0,1,2,0,1,..... *) (* @END_DECLARATION := '0' *) IF X >= N - 1 THEN INC1 := 0; ELSE INC1 := X + 1; END_IF; (* revision history hm 13. oct. 2008 rev 1.0 original version hm 22. oct. 2008 rev 1.1 added statement to return value for compatibility reasons hm 23. feb. 2009 rev 1.2 when inc1 is called with X >= N inc will continue with 0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION INC2 : INT VAR_INPUT X : INT; D : INT; L : INT; U : INT; END_VAR VAR tmp: INT; END_VAR (* version 1.0 29. jun. 2008 programmer hugo tested by oscat This function increments the input X by the value D and compares the result with U. if the output exceeds U it will continue to count from L again. *) (* @END_DECLARATION := '0' *) tmp := U - L + 1; INC2 := (X + D - L + tmp) MOD tmp + L; (* revision history hm 29. jun. 2008 REV 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION INV : REAL VAR_INPUT X : REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by tobias This function calculates the result of 1 / X *) (* @END_DECLARATION := '0' *) IF X <> 0.0 THEN INV := 1.0 / X; END_IF; (* revision history hm 26. oct. 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LAMBERT_W : REAL VAR_INPUT X : REAL; END_VAR VAR w : REAL; i : INT; we: REAL; w1e: REAL; last: DWORD; ewx: REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by oscat this function calculates the lambert_w function. *) (* @END_DECLARATION := '0' *) (* check for valid input and return -1000 if too low *) IF x < -0.367879441171442 THEN LAMBERT_W := -1000.0; RETURN; (* return 0 if x = 0 *) ELSIF x = 0.0 THEN RETURN; (* first an estimate is calculated *) ELSIF x <= 500.0 THEN w := LN(x + 1.0); w := 0.665 * (1.0 + 0.0195 * w) * w + 0.04; ELSE w := LN(x - 4.0) - (1.0 - 1.0/LN(x)) * LN(LN(x)); END_IF; (* use estimate to calculate exact result *) FOR i := 0 TO 5 DO ewx := EXP(w); we := w * ewx - x; w1e := (w+1.0) * ewx; last := REAL_TO_DW(w) AND 16#FFFF_FFFC; w := w - (we / (w1e - (w+2.0) * we / (2.0 * w + 2.0))); IF (REAL_TO_DW(w) AND 16#FFFF_FFFC) = last THEN EXIT; END_IF; END_FOR; LAMBERT_W := w; (* revision hisdtory hm 26. oct. 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LANGEVIN : REAL VAR_INPUT X : REAL; END_VAR (* version 1.2 10.mar. 2009 programmer hugo tested by tobias this function calculates the langevin function *) (* @END_DECLARATION := '0' *) IF X = 0.0 THEN LANGEVIN := 0.0; ELSE LANGEVIN := COTH(X) - 1.0 / X; END_IF; (* revision history hm 10.12.2007 rev 1.0 original version hm 11. mar 2008 rev 1.1 changed formula to avoid problems when x = 0 hm 10. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MAX3 : REAL VAR_INPUT in1 : REAL; in2 : REAL; in3 : REAL; END_VAR (* version 1.1 18. mar. 2011 programmer hugo tested by tobias this function returns the highest of 3 real values *) (* @END_DECLARATION := '0' *) MAX3 := MAX(MAX(IN1,IN2),IN3); (* revision history hm 1.1.2007 rev 1.0 original release hm 18. mar. 2011 rev 1.1 improve performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MID3 : REAL VAR_INPUT IN1 : REAL; IN2 : REAL; IN3 : REAL; END_VAR (* version 1.2 18. mar. 2011 programmer hugo tested by tobias returns the mid value of 3 real inputs. *) (* @END_DECLARATION := '0' *) IF IN1 > IN2 THEN MID3 := IN1; IN1 := IN2; IN2 := MID3; END_IF; IF IN2 > IN3 THEN IN2 := IN3; END_IF; MID3 := SEL(IN1 > IN2, IN2, IN1); (* hm 1.1.2007 rev 1.1 rewrote the function for better performance hm 18. mar. 2011 rev 1.2 improve performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MIN3 : REAL VAR_INPUT in1 : REAL; in2 : REAL; in3 : REAL; END_VAR (* version 1.1 18. mar. 2011 programmer hugo tested by tobias this function returns the lowest of 3 real values *) (* @END_DECLARATION := '0' *) MIN3 := MIN(MIN(IN1,IN2),IN3); (* revision history hm 1.1.2007 rev 1.0 original release hm 18. mar. 2011 rev 1.1 improve performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MODR : REAL VAR_INPUT IN : REAL; DIVI : REAL; END_VAR (* version 1.5 10. mar. 2009 programmer hugo tested by tobias this is a modulo funtion for real numbers modr(5.5,2.5) = 0.5 *) (* @END_DECLARATION := '0' *) IF divi = 0.0 THEN MODR := 0.0; ELSE MODR := in - DINT_TO_REAL(FLOOR2(in / divi)) * divi; END_IF; (* revision history hm 4. aug.2006 rev 1.0 hm 28. jan.2007 rev 1.1 modr(x,0) will deliver the result 0 hm 21. mar 2008 rev 1.2 use D_trunc for compatibility reasons hm 4. apr. 2008 rev 1.3 added type conversion to avoid warnings under codesys 3.0 hm 31. oct. 2008 rev 1.4 changed algorithm to the more common version using floor instead of TRUNC hm 10. mar. 2009 rev 1.5 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MUL_ADD : REAL VAR_INPUT X : REAL; K : REAL; O : REAL; END_VAR (* version 1.1 11 nov 2007 programmer hugo tested by tobias this function multiplies an input X with K and adds Offset O to the result. *) (* @END_DECLARATION := '0' *) MUL_ADD := X * K + O; (* revision history hm 28. feb 2007 original version hm 11.nov 2007 rev 1.1 deleted preset values for K and O this makes no sense for a function *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION NEGX : REAL VAR_INPUT X : REAL; END_VAR (* version 1.0 26. oct. 2008 programmer hugo tested by tobias This function returns -X *) (* @END_DECLARATION := '0' *) NEGX := -X; (* revision history hm 26. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION RAD : REAL VAR_INPUT DEG : REAL; END_VAR (* version 1.2 18. oct. 2008 programmer hugo tested by tobias this function converts Radiant to degrees *) (* @END_DECLARATION := '0' *) RAD := MODR(0.0174532925199433 * DEG, math.PI2); (* revision history hm 4. aug 2006 rev 1.0 original version hm 16. oct 2007 rev 1.1 added modr statement which prohibits rad to become bigger than 2PI hm 18. oct 2008 rev 1.2 using math constants *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION RDM : REAL VAR_INPUT last : REAL; END_VAR VAR tn : DWORD; tc : INT; END_VAR (* version 1.9 10. mar. 2009 programmer hugo tested by tobias this function calculates a pseudo random number to generate the number it reads the sps timer and calculates a random number between 0 and 1: in order to use rdm more then once within one sps cycle it need to be called with different seed numbers LAST. *) (* @END_DECLARATION := '0' *) tn := T_PLC_MS(); tc := BIT_COUNT(tn); tn.31 := tn.2; tn.30 := tn.5; tn.29 := tn.4; tn.28 := tn.1; tn.27 := tn.0; tn.26 := tn.7; tn.25 := tn.6; tn.24 := tn.3; tn := ROL(tn,BIT_COUNT(tn)) OR 16#80000001; tn := tn MOD 71474513 + INT_TO_DWORD(tc + 77); RDM := FRACT(DWORD_TO_REAL(tn) / 10000000.0 * (math.E - LIMIT(0.0,last,1.0))); (* pt := ADR(temp); pt^ := (T_PLC_MS() AND 16#007FFFFF) OR 16#3D000000; RDM := fract(modR(temp*e+pi1, PI1-last) + modR(temp*PI1+e + last,e-last)); *) (* revision history hm 16. jan 2007 rev 1.0 original version hm 11. nov 2007 rev 1.1 changed time() into t_plc_ms() hm 20. nov 2007 rev 1.2 changed code of temp calculation to avoid overflow in modr due to resuclt would not fit DINT for high timeer values hm 5. jan 2008 rev 1.3 changed calculation of temp to avoid problem with high values of t_plc_ms hm 2. feb 2008 rev 1.4 changed algorithm to avoind non iec functions and guarantee more randomness hm 10. mar. 2008 rev 1.5 make sure last will be between 0 and 1 to avoid invalid results hm 16. mar. 2008 rev 1.6 added conversion for tc to avoid warnings under codesys 3.0 hm 18. may. 2008 rev 1.7 changed constant E to E1 hm 18. oct. 2008 rev 1.8 using math constants hm 10. mar. 2009 rev 1.9 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION RDM2 : INT VAR_INPUT last : INT; low : INT; high : INT; END_VAR (* version 1.1 18. oct. 2008 programmer hugo tested by tobias this function calculates an integer pseudo random number the random number will be in the range of low <= rdm2 <= high. *) (* @END_DECLARATION := '0' *) RDM2 := TRUNC(RDM(FRACT(last * math.PI)) * (high - low + 1)) + low; (* revision history hm 29. feb 2008 rev 1.0 original version hm 18. oct. 2008 rev 1.1 using math constants *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION RDMDW : DWORD VAR_INPUT last : DWORD; END_VAR VAR RX: REAL; M: REAL; END_VAR (* version 1.2 18. oct. 2008 programmer hugo tested by tobias this function calculates a DWORD pseudo random number. *) (* @END_DECLARATION := '0' *) M := BIT_COUNT(last); RX := RDM(FRACT(M * math.PI)); RDMDW := SHL(REAL_TO_DWORD(rx*65535),16); RX := RDM(FRACT(M * math.E)); RDMDW := RDMDW OR (REAL_TO_DWORD(rx*65535) AND 16#0000FFFF); (* revision history hm 14. mar 2008 rev 1.0 original version hm 18. may. 2008 rev 1.1 changed constant E to E1 hm 18. oct. 2008 rev 1.2 using math constants *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION REAL_TO_FRAC : FRACTION VAR_INPUT X : REAL; (* in einen Bruch umzuwandelnder Wert *) N : INT; (* maximale Größe des Nenners *) END_VAR VAR temp : DINT; (* Merker für Berechnungen *) sign: BOOL; (* Vorzeichen vom Eingangswert *) X_gerundet: DINT; (* Wert von Bruch, auf ganze Zahl gerundet *) X_ohne_Nachkomma: REAL; (* Wert von Bruch, ohne Nachkommastellen *) Numerator: DINT := 1; (* Initialwert Zaehler *) Denominator: DINT := 0; (* Initialwert Nenner *) Numerator_old: DINT := 0; (* Initialwert Zaehler der letzten Berechnung *) Denominator_old: DINT := 1; (* Initialwert Zaehler der letzten Berechnung *) END_VAR (* version 1.1 06. apr. 2011 programmer alexander tested by hugo this function calculates the closest fraction for a real number *) (* @END_DECLARATION := '0' *) IF X < 0.0 THEN sign := TRUE; (* Vorzeichen merken *) X := ABS(X); (* Absolutwert berechnen *) END_IF; REPEAT X_gerundet := REAL_TO_DINT(X); (* Zaehler berechnen *) temp := numerator * X_gerundet + numerator_old; (* Zaehler um Vorkammawert erweitern *) numerator_old := numerator; (* Zaehler der letzten Berechnung speichern *) numerator := temp; (* Zaehler dieser Berechnung speichern *) (* Nenner berechnen *) temp := denominator * X_gerundet + denominator_old; (* Nenner um Vorkammawert erweitern *) denominator_old := denominator; (* Nenner der letzten Berechnung speichern *) denominator := temp; (* Nenner dieser Berechnung speichern *) (* Restwert berechnen *) X_ohne_Nachkomma := DINT_TO_REAL(X_gerundet); IF X = X_ohne_Nachkomma THEN (* Bruch geht ohne Rest auf *) IF ABS(denominator) <= N THEN (* kein Rundungsfehler *) numerator_old := numerator; (* Numerator_old wird von Funktion zurückgegeben *) denominator_old := denominator; (* Denominator_old wird von Funktion zurückgegeben *) END_IF EXIT; (* keine weitere Berechnung notwendig *) ELSE X := 1.0 / (X - X_ohne_Nachkomma); (* Kehrwert vom Rest -> Neuer Bruch *) END_IF UNTIL ABS(Denominator) > N END_REPEAT (* correct sign if X was negative *) IF sign THEN REAL_TO_FRAC.NUMERATOR := -1 * ABS(DINT_TO_INT(numerator_old)); ELSE REAL_TO_FRAC.NUMERATOR := ABS(DINT_TO_INT(numerator_old)); END_IF REAL_TO_FRAC.DENOMINATOR := ABS(DINT_TO_INT(denominator_old)); (* revision history hm 19. jan. 2011 rev 1.0 original version ad 06. apr. 2011 rev 1.1 optimized code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION RND : REAL VAR_INPUT X : REAL; N : INT; END_VAR VAR M : REAL; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by tobias this function rounds a real down to n digits total. round(3.1415,2) = 3.1 *) (* @END_DECLARATION := '0' *) IF X = 0.0 THEN RND := 0.0; ELSE M := EXPN(10.0,N - CEIL(LOG(ABS(X)))); RND := DINT_TO_REAL(REAL_TO_DINT(X * M)) / M; END_IF; (* revision history hm 11. mar 2008 rev 1.0 original version hm 26. oct. 2008 rev 1.1 code optimization hm 10. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ROUND : REAL VAR_INPUT in : REAL; N : INT; END_VAR VAR X: REAL; END_VAR (* version 1.5 25. oct. 2008 programmer hugo tested by tobias this function rounds a real down to n digits behind the comma. *) (* @END_DECLARATION := '0' *) X := setup.DECADES[LIMIT(0,N,8)]; ROUND := DINT_TO_REAL(REAL_TO_DINT(in * X)) / X; (* revision history hm 1. sep 2006 rev 1.0 original version hm 2. dec 2007 rev 1.1 changed code for better performance hm 8. jan 2008 rev 1.2 further improvement in performance hm 11. mar. 2008 rev 1.3 corrected an error with negative numbers use real_to_dint instead of trunc hm 16. mar 2008 rev 1.4 added type conversion to avoid warning under codesys 3.0 hm 25. oct. 2008 rev 1.5 new code using global constants decades *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SGN : INT VAR_INPUT X : REAL; END_VAR (* version 1.1 11. nov. 2008 programmer hugo tested by tobias sgn returns 0 when X = 0 , -1 when X < 1 and +1 when X > 1 *) (* @END_DECLARATION := '0' *) IF X > 0 THEN SGN := 1; ELSIF X < 0 THEN SGN := -1; ELSE SGN := 0; END_IF; (* revision histroy hm 16. oct 2007 rev 1.0 original version hm 11. nov 2007 rev 1.1 changed type of function from real to int *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SIGMOID : REAL VAR_INPUT X : REAL; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by tobias this function calculates the sigmoid function *) (* @END_DECLARATION := '0' *) IF X > 20.0 THEN SIGMOID := 1.0; ELSIF x < -85.0 THEN SIGMOID := 0.0; ELSE SIGMOID := 1.0 / (1.0 + EXP(-X)); END_IF; (* revision history hm 10.12.2007 rev 1.0 original version hm 11. mar. 2008 rev 1.1 extended range of valid inputs to +inv / -inv hm 10. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SIGN_I : BOOL VAR_INPUT IN : DINT; END_VAR (* version 1.3 27. oct. 2008 programmer hugo tested by tobias this function return true if the integer input is negative *) (* @END_DECLARATION := '0' *) SIGN_I := in.31; (* revision history hm 3.3.2007 rev 1.1 changed method of function for better compatibility to other systems hm 1.12.2007 rev 1.2 changed code to improve performance hm 27. oct. 2008 rev 1.3 changed type of input to dint *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SIGN_R : BOOL VAR_INPUT in : REAL; END_VAR (* version 1.4 10. mar. 2009 programmer hugo tested by tobias this function return true if the real input is negative *) (* @END_DECLARATION := '0' *) SIGN_R := in < 0.0; (* revision history hm 19.1.2007 rev 1.1 changed method of function for better compatibility to other systems hm 1.12.2007 rev 1.2 changed code to improve performance hm 14. jun. 2008 rev 1.3 improved performace hm 10. mar. 2009 rev 1.4 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SINC : REAL VAR_INPUT X : REAL; END_VAR (* version 1.2 10. mar. 2009 programmer hugo tested by tobias this function calculates the sinc function. *) (* @END_DECLARATION := '0' *) IF X = 0.0 THEN SINC := 1.0; ELSE SINC := SIN(x) / x; END_IF; (* revision histroy hm 11. mar. 2008 rev 1.0 original version hm 1.12.2007 rev 1.1 changed code to improove performance hm 10. mar. 2009 rev 1.2 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SINH : REAL VAR_INPUT X : REAL; END_VAR (* version 1.3 11. mar 2008 programmer hugo tested by tobias this function calculates the sin hyperbolicus *) (* @END_DECLARATION := '0' *) IF ABS(x) < 2E-3 THEN SINH := X; ELSE SINH := (EXP(x) - EXP(-x)) * 0.5; END_IF; (* revision history hm 12.1.2007 rev 1.0 original version hm 1.12.2007 rev 1.1 changed code to improve performance hm 5. jan 2008 rev 1.2 further performance improvement hm 11. mar 2008 rev 1.3 improved accuracy around 0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SQRTN : REAL VAR_INPUT X : REAL; N : INT; END_VAR (* version 1.3 10. mar. 2009 programmer hugo tested by tobias this function calculates the nth root function of X according to the formula sqrtn = x^(1/n). *) (* @END_DECLARATION := '0' *) IF N > 0 THEN SQRTN := EXP(LN(x) / INT_TO_REAL(n)); ELSE SQRTN := 0.0; END_IF; (* revision history hm 12 jan 2007 rev 1.0 original version hm 2. dec 2007 rev 1.1 changed code for better performance hm 11. mar 2008 rev 1.2 added result 0 for compatibility reasons hm 10. mar. 2009 rev 1.3 added type conversions for compatibility reasons *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TANC : REAL VAR_INPUT X : REAL; END_VAR (* version 1.1 10. mar. 2009 programmer hugo tested by oscat this function calculates the tanc function. *) (* @END_DECLARATION := '0' *) IF X = 0.0 THEN TANC := 1.0; ELSE TANC := TAN(x) / x; END_IF; (* revision histroy hm 23. oct. 2008 rev 1.0 original version hm 10. mar. 2009 rev 1.1 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TANH : REAL VAR_INPUT X : REAL; END_VAR (* version 1.3 10. mar. 2009 programmer hugo tested by tobias this function calculates the tangens hyperbolicus *) (* @END_DECLARATION := '0' *) IF X > 20.0 THEN TANH := 1.0; ELSIF X < -20.0 THEN TANH := -1.0; ELSE TANH := 1.0 - 2.0 / (EXP(2.0 * x) + 1.0); END_IF; (* revision hisdtory hm 12.1.2007 rev 1.0 original version hm 1.12.2007 rev 1.1 changed code to improve performance hm 10. mar 2008 rev 1.2 corrected an error in formula extended range of valid inputs to +/- INV hm 10. mar. 2009 rev 1.3 real constants updated to new systax using dot *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION WINDOW : BOOL VAR_INPUT low : REAL; in : REAL; high : REAL; END_VAR (* version 1.1 22 jan 2007 programmer hugo tested BY tobias this checks a signal if the signal is between the upper and lower limit *) (* @END_DECLARATION := '0' *) WINDOW := in < high AND in > low; (* revision history hm 22.1.2007 rev 1.1 changed the order of inputs to low, in, high *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Mathematical' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION WINDOW2 : BOOL VAR_INPUT LOW : REAL; IN : REAL; HIGH : REAL; END_VAR (* version 1.0 31 oct 2007 programmer hugo tested BY tobias this checks a signal if the signal is between the upper and lower limit including the two limits *) (* @END_DECLARATION := '0' *) WINDOW2 := IN >= LOW AND IN <= HIGH; (* revision history hm 31.10.2007 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Other' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK ESR_COLLECT VAR_INPUT ESR_0, ESR_1, ESR_2, ESR_3, ESR_4, ESR_5, ESR_6, ESR_7 : ARRAY [0..3] OF esr_data; rst : BOOL; END_VAR VAR_IN_OUT pos : INT; END_VAR VAR_OUTPUT ESR_OUT : ARRAY[0..31] OF esr_data; END_VAR VAR CONSTANT max_in : INT := 3; (* max limit of input array *) max_out : INT := 32; (* number of elements in array *) END_VAR VAR cnt : INT := -1; END_VAR (* version 1.4 1. dec. 2009 programmer hugo tested by tobias ESR_collect collects esr data from up to 8 esr_mon modules and stroes them in an output array. the output pos will display the position of the last element in the array. if the array is empty, pos = -1 when to buffer is read by followon modules. pos has to be reset to -1 if the array will be full, the buffer will be refilled starting at position 0. *) (* @END_DECLARATION := '0' *) IF rst OR cnt < 0 THEN pos := -1; ELSE FOR cnt := 0 TO max_in DO IF esr_0[cnt].typ > 0 THEN pos := INC1(pos, max_out); esr_out[pos] := esr_0[cnt]; END_IF; IF esr_1[cnt].typ > 0 THEN pos := INC1(pos, max_out); esr_out[pos] := esr_1[cnt]; END_IF; IF esr_2[cnt].typ > 0 THEN pos := INC1(pos, max_out); esr_out[pos] := esr_2[cnt]; END_IF; IF esr_3[cnt].typ > 0 THEN pos := INC1(pos, max_out); esr_out[pos] := esr_3[cnt]; END_IF; IF esr_4[cnt].typ > 0 THEN pos := INC1(pos, max_out); esr_out[pos] := esr_4[cnt]; END_IF; IF esr_5[cnt].typ > 0 THEN pos := INC1(pos, max_out); esr_out[pos] := esr_5[cnt]; END_IF; IF esr_6[cnt].typ > 0 THEN pos := INC1(pos, max_out); esr_out[pos] := esr_6[cnt]; END_IF; IF esr_7[cnt].typ > 0 THEN pos := INC1(pos, max_out); esr_out[pos] := esr_7[cnt]; END_IF; END_FOR; END_IF; (* revision history hm 26.jan 2007 rev 1.0 original version hm 8. dec 2007 rev 1.1 added reset input ks 27. oct. 2008 rev 1.2 optimized code for performance ks 12. nov. 2009 rev 1.3 output pos was not pointing to last value hm 1. dec. 2009 rev 1.4 changed pos to be I/O reduced output array size to 32 elements *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Other' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK ESR_MON_B8 VAR_INPUT s0, s1, s2, s3, s4, s5, s6, s7 : BOOL; DT_in : DT; END_VAR VAR_INPUT CONSTANT a0, a1, a2, a3, a4, a5, a6, a7 : STRING(10); END_VAR VAR_OUTPUT ESR_Flag : BOOL; END_VAR VAR_IN_OUT ESR_Out: ARRAY [0..3] OF esr_data; END_VAR VAR x0, x1, x2, x3, x4, x5, x6, x7 : BOOL; tx: TIME; cnt : INT; END_VAR (* version 1.3 1. dec. 2009 programmer hugo tested by tobias ESR_mon_B8 monitores up to 8 binary inputs and reports changes with time stamd and adress label. the module checks 8 inputs for a change and reports all changes with time and adress stamp to the output. 4 events maximum can be collected at once within the same cycle *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := DWORD_TO_TIME(T_PLC_MS()); ESR_Flag := FALSE; esr_out[3].typ := 0; esr_out[2].typ := 0; esr_out[1].typ := 0; esr_out[0].typ := 0; cnt := 0; (* check if inputs have chaged and fill buffer *) IF s0 <> X0 THEN esr_out[cnt].typ := 10 + BOOL_TO_BYTE(s0); esr_out[cnt].adress := a0; esr_out[cnt].DS := DT_in; esr_out[cnt].TS := TX; X0 := S0; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF s1 <> X1 THEN esr_out[cnt].typ := 10 + BOOL_TO_BYTE(s1); esr_out[cnt].adress := a1; esr_out[cnt].DS := DT_in; esr_out[cnt].TS := TX; X1 := S1; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF s2 <> X2 THEN esr_out[cnt].typ := 10 + BOOL_TO_BYTE(s2); esr_out[cnt].adress := a2; esr_out[cnt].DS := DT_in; esr_out[cnt].TS := TX; X2 := S2; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF s3 <> X3 THEN esr_out[cnt].typ := 10 + BOOL_TO_BYTE(s3); esr_out[cnt].adress := a3; esr_out[cnt].DS := DT_in; esr_out[cnt].TS := TX; X3 := S3; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF s4 <> X4 AND cnt < 4 THEN esr_out[cnt].typ := 10 + BOOL_TO_BYTE(s4); esr_out[cnt].adress := a4; esr_out[cnt].DS := DT_in; esr_out[cnt].TS := TX; X4 := S4; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF s5 <> X5 AND cnt < 4 THEN esr_out[cnt].typ := 10 + BOOL_TO_BYTE(s5); esr_out[cnt].adress := a5; esr_out[cnt].DS := DT_in; esr_out[cnt].TS := TX; X5 := S5; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF s6 <> X6 AND cnt < 4 THEN esr_out[cnt].typ := 10 + BOOL_TO_BYTE(s6); esr_out[cnt].adress := a6; esr_out[cnt].DS := DT_in; esr_out[cnt].TS := TX; X6 := S6; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF s7 <> X7 AND cnt < 4 THEN esr_out[cnt].typ := 10 + BOOL_TO_BYTE(s7); esr_out[cnt].adress := a7; esr_out[cnt].DS := DT_in; esr_out[cnt].TS := TX; X7 := S7; cnt := cnt + 1; esr_flag := TRUE; END_IF; (* revision history hm 26. jan 2007 rev 1.0 original version hm 17. sep 2007 rev 1.1 replaced time() with T_PLC_MS() for compatibility reasons hm 22. oct. 2008 rev 1.2 optimized code hm 1.dec. 2009 rev 1.3 changed esr_out to be I/O *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Other' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK ESR_MON_R4 VAR_INPUT R0, R1, R2, R3 : REAL; DT_in : DT; END_VAR VAR_INPUT CONSTANT a0, a1, a2, a3 : STRING(10); s0, s1, s2, s3 : REAL; END_VAR VAR_OUTPUT ESR_Flag : BOOL; END_VAR VAR_IN_OUT ESR_Out: ARRAY [0..3] OF esr_data; END_VAR VAR p0, p1, p2, p3 : POINTER TO DWORD; x0, x1, x2, x3 : REAL; tx: TIME; cnt : INT; END_VAR (* version 1.4 1. dec. 2009 programmer hugo tested by tobias ESR_mon_R4 monitores up to 4 Real inputs and reports changes with time stamd and adress label. the module checks 4 inputs for a change of more than the specified sensitivity S and reports all changes with time and adress stamp to the output. *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := DWORD_TO_TIME(T_PLC_MS()); P0 := ADR(R0); P1 := ADR(R1); P2 := ADR(R2); P3 := ADR(R3); ESR_Flag := FALSE; esr_out[3].typ := 0; esr_out[2].typ := 0; esr_out[1].typ := 0; esr_out[0].typ := 0; cnt := 0; (* check if inputs have chaged and fill buffer *) IF DIFFER(R0, X0, S0) THEN esr_out[cnt].typ := 20; esr_out[cnt].adress := a0; esr_out[cnt].DS := DT_in; esr_out[cnt].TS := TX; esr_out[cnt].data[0] := Byte_of_Dword(P0^,0); esr_out[cnt].data[1] := Byte_of_Dword(P0^,1); esr_out[cnt].data[2] := Byte_of_Dword(P0^,2); esr_out[cnt].data[3] := Byte_of_Dword(P0^,3); X0 := R0; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF differ(R1, X1, S1) THEN esr_out[cnt].typ := 20; esr_out[cnt].adress := a1; esr_out[cnt].DS := DT_in; esr_out[cnt].TS := TX; esr_out[cnt].data[0] := Byte_of_Dword(P1^,0); esr_out[cnt].data[1] := Byte_of_Dword(P1^,1); esr_out[cnt].data[2] := Byte_of_Dword(P1^,2); esr_out[cnt].data[3] := Byte_of_Dword(P1^,3); X1 := R1; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF differ(R2, X2, S2) THEN esr_out[cnt].typ := 20; esr_out[cnt].adress := a2; esr_out[cnt].DS := DT_in; esr_out[cnt].TS := TX; esr_out[cnt].data[0] := Byte_of_Dword(P2^,0); esr_out[cnt].data[1] := Byte_of_Dword(P2^,1); esr_out[cnt].data[2] := Byte_of_Dword(P2^,2); esr_out[cnt].data[3] := Byte_of_Dword(P2^,3); X2 := R2; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF DIFFER(R3, X3, S3) THEN esr_out[cnt].typ := 20; esr_out[cnt].adress := a3; esr_out[cnt].DS := DT_in; esr_out[cnt].TS := TX; esr_out[cnt].data[0] := Byte_of_Dword(P3^,0); esr_out[cnt].data[1] := Byte_of_Dword(P3^,1); esr_out[cnt].data[2] := Byte_of_Dword(P3^,2); esr_out[cnt].data[3] := Byte_of_Dword(P3^,3); X3 := R3; cnt := cnt + 1; esr_flag := TRUE; END_IF; (* revision history hm 26. jan 2007 rev 1.0 original version hm 17. sep. 2007 rev 1.1 replaced time() with T_PLC_MS() for compatibility reasons hm 8. dec. 2007 rev 1.2 corrected an error while esr typ would not be set hm 16. mar. 2008 rev 1.3 deleted wrong conversion real_to_dword hm 1. dec 2009 rev 1.4 changed esr_out to be I/O *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Other' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK ESR_MON_X8 VAR_INPUT s0, s1, s2, s3, s4, s5, s6, s7 : BYTE; DT_in : DT; Mode : BYTE := 3; END_VAR VAR_INPUT CONSTANT a0, a1, a2, a3, a4, a5, a6, a7 : STRING(10); END_VAR VAR_OUTPUT ESR_Flag : BOOL; END_VAR VAR_IN_OUT ESR_Out: ARRAY [0..3] OF esr_data; END_VAR VAR x0, x1, x2, x3, x4, x5, x6, x7 : BYTE; tx: TIME; cnt : INT; END_VAR (* version 1.2 1. dec. 2009 programmer hugo tested by tobias ESR_MON_X8 is a status and error collector. the module checks 8 status inputs for a change and reports up to 4 input changes with time and adress stamp to the output. the mode can be 1 for error only 2 for error and status 3 for error, status and debug the adress label of the 8 inputs can be configured individually. *) (* @END_DECLARATION := '0' *) (* read system timer *) tx := DWORD_TO_TIME(T_PLC_MS()); ESR_Flag := FALSE; esr_out[3].typ := 0; esr_out[2].typ := 0; esr_out[1].typ := 0; esr_out[0].typ := 0; cnt := 0; (* check if inputs have chaged and fill buffer *) IF s0 <> X0 AND ((s0 < 100) OR (S0 > 99 AND S0 < 200 AND mode >= 2) OR (S0 > 199 AND mode = 3)) THEN esr_out[cnt] := status_to_ESR(s0, a0, DT_in, TX); X0 := S0; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF s1 <> X1 AND ((s1 < 100) OR (S1 > 99 AND S1 < 200 AND mode >= 2) OR (S1 > 199 AND mode = 3)) THEN esr_out[cnt] := status_to_ESR(s1, a1, DT_in, TX); X1 := S1; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF s2 <> X2 AND ((s2 < 100) OR (S2 > 99 AND S2 < 200 AND mode >= 2) OR (S2 > 199 AND mode = 3)) THEN esr_out[cnt] := status_to_ESR(s2, a2, DT_in, TX); X2 := S2; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF s3 <> X3 AND ((s3 < 100) OR (S3 > 99 AND S3 < 200 AND mode >= 2) OR (S3 > 199 AND mode = 3)) THEN esr_out[cnt] := status_to_ESR(s3, a3, DT_in, TX); X3 := S3; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF cnt < 4 AND s4 <> X4 AND ((s4 < 100) OR (S4 > 99 AND S4 < 200 AND mode >= 2) OR (S4 > 199 AND mode = 3)) THEN esr_out[cnt] := status_to_ESR(s4, a4, DT_in, TX); X4 := S4; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF cnt < 4 AND s5 <> X5 AND ((s5 < 100) OR (S5 > 99 AND S5 < 200 AND mode >= 2) OR (S5 > 199 AND mode = 3)) THEN esr_out[cnt] := status_to_ESR(s5, a5, DT_in, TX); X5 := S5; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF cnt < 4 AND s6 <> X6 AND ((s6 < 100) OR (S6 > 99 AND S6 < 200 AND mode >= 2) OR (S6 > 199 AND mode = 3)) THEN esr_out[cnt] := status_to_ESR(s6, a6, DT_in, TX); X6 := S6; cnt := cnt + 1; esr_flag := TRUE; END_IF; IF cnt < 4 AND s7 <> X7 AND ((s7 < 100) OR (S7 > 99 AND S7 < 200 AND mode >= 2) OR (S7 > 199 AND mode = 3)) THEN esr_out[cnt] := status_to_ESR(s7, a7, DT_in, TX); X7 := S7; cnt := cnt + 1; esr_flag := TRUE; END_IF; (* revision history hm 26. jan 2007 rev 1.0 original version hm 17. sep 2007 rev 1.1 replaced time() with T_PLC_MS() for compatibility reasons hm 1. dec. 2009 rev 1.2 changed esr_out to be I/O *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Other' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION OSCAT_VERSION : DWORD VAR_INPUT IN : BOOL; END_VAR (* version 1.1 16 dec 2007 programmer hugo tested by oscat oscat_version returns the version number in dword format 132 is library version 1.32 if IN = true, the release date will be returned *) (* @END_DECLARATION := '0' *) IF in THEN OSCAT_VERSION := DATE_TO_DWORD(D#2012-01-02); ELSE OSCAT_VERSION := 333; END_IF; (* revision history hm 6. oct 2006 rev 1.0 original version hm 16. dec 2007 rev 1.1 added possibility to return date and version depending on IN. *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Other' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION STATUS_TO_ESR : esr_data VAR_INPUT status : BYTE; adress : STRING(10); DT_in : DT; TS : TIME; END_VAR (* version 1.0 6 oct 2006 programmer hugo tested by tobias status_to_esr creates esr data from a status byte. *) (* @END_DECLARATION := '0' *) IF status < 100 THEN status_to_ESR.typ := 1; ELSIF status < 200 THEN status_to_ESR.typ := 2; ELSE status_to_ESR.typ := 3; END_IF; status_to_ESR.adress:= adress; status_to_ESR.DS := DT_in; status_to_ESR.TS := TS; status_to_ESR.data[0] := status; END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BIN_TO_BYTE : BYTE VAR_INPUT BIN : STRING(12); END_VAR VAR pt : POINTER TO BYTE; i: INT; X: BYTE; stop: INT; END_VAR (* version 1.2 26. jul 2009 programmer hugo tested by oscat BINARY_TO_byte converts a binary string into a byte. *) (* @END_DECLARATION := '0' *) pt := ADR(bin); stop := LEN(bin); FOR I := 1 TO stop DO x := pt^; (* calculate the value of the digit *) IF X = 48 THEN BIN_TO_BYTE := SHL(BIN_TO_BYTE,1); ELSIF X = 49 THEN BIN_TO_BYTE := SHL(BIN_TO_BYTE,1) OR 1; END_IF; pt := pt + 1; END_FOR; (* revision histroy hm 18. jun. 2008 rev 1.0 original release hm 20. sep. 2008 rev 1.1 changed length of input string from 20 to 12 hm 26. jul. 2009 rev 1.2 optimized code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BIN_TO_DWORD : DWORD VAR_INPUT BIN : STRING(40); END_VAR VAR pt : POINTER TO BYTE; i: INT; X: BYTE; stop: INT; END_VAR (* version 1.2 26. jul. 2009 programmer hugo tested by oscat BINARY_TO_DWORD converts a binary string into a dword. *) (* @END_DECLARATION := '0' *) pt := ADR(bin); stop := LEN(bin); FOR I := 1 TO stop DO x := pt^; (* calculate the value of the digit *) IF X = 48 THEN BIN_TO_DWORD := SHL(BIN_TO_DWORD,1); ELSIF X = 49 THEN BIN_TO_DWORD := SHL(BIN_TO_DWORD,1) OR 1; END_IF; pt := pt + 1; END_FOR; (* revision histroy hm 18. jun. 2008 rev 1.0 original release hm 20. sep. 2008 rev 1.1 changed length of input dtring from 20 to 40 hm 26. jul 2009 rev 1.2 optimized code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BYTE_TO_STRB : STRING(8) VAR_INPUT IN : BYTE; END_VAR VAR i: INT; pt : POINTER TO BYTE; END_VAR (* version 1.3 20. jun. 2008 programmer hugo tested by oscat BYTE_TO_STRINGB converts a Byte to a String of Bits represented by '0' and '1' s. The lowest order bit will be on the right and the high order bit on the left. *) (* @END_DECLARATION := '0' *) (* pointer für die ausgabe ermitteln *) pt := ADR(BYTE_TO_STRB); (* die 8 ausgabecharacter ermitteln und schreiben *) FOR i := 1 TO 8 DO pt^ := BOOL_TO_BYTE(in.7) + 48; in := SHL(in,1); pt := pt + 1; END_FOR; (* der ausgabestring muss noch mit 0 abgeschlossen werden *) pt^ := 0; (* code before rev 1.1 FOR i := 1 TO 8 DO IF in.0 = 0 THEN temp := CONCAT('0', temp); ELSE temp := CONCAT('1', temp); END_IF; in := SHR(in, 1); END_FOR; BYTE_TO_STRB := temp; *) (* revision history hm 9.6.2007 rev 1.0 original version hm 15. dec 2007 rev 1.1 inprooved code for higher performance hm 29. mar. 2008 rev 1.2 changed STRING to STRING(8) hm 20. jun. 2008 rev 1.3 performance improvement *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION BYTE_TO_STRH : STRING(2) VAR_INPUT IN : BYTE; END_VAR VAR temp : BYTE; PT : POINTER TO BYTE; END_VAR (* version 1.3 29 mar. 2008 programmer hugo tested by tobias BYTE_TO_STRINGH converts a Byte to a String of Hexadecimal represented by '0' .. '9' and 'A' .. 'F'. The lowest order Character will be on the right and the high order Character on the left. *) (* @END_DECLARATION := '0' *) (* read pointer to output string *) PT := ADR(BYTE_TO_STRH); (* calculate high order hex value *) temp := SHR(in,4); (* convert value to hex character *) IF temp <= 9 THEN temp := temp + 48; ELSE temp := temp + 55; END_IF; (* write friat character to output string *) PT^ := temp; (* calculate low order hex value *) temp := in AND 2#00001111; IF temp <= 9 THEN temp := temp + 48; ELSE temp := temp + 55; END_IF; (* increment pointer and wirte low order character *) pt := pt + 1; pt^ := temp; (* set pointer at the end of the output string and close the string with 0 *) pt := pt + 1; pt^:= 0; (* code before rev 1.2 FOR i := 1 TO 2 DO X := in AND 2#1111; IF X <= 9 THEN X := X + 48; ELSE X := X + 55; END_IF; Cx := CHR(X); temp := CONCAT(Cx, temp); in := SHR(in,4); END_FOR; BYTE_TO_STRH := temp; *) (* revision history hm 9.6.2007 rev 1.0 original version hm 11.9.2007 rev 1.1 changed coding for compatibility with twincat, under twincat concat cannot have a function as argument. hm 15 dec 2007 REV 1.2 changed code for higher performance hm 29. mar. 2008 rev 1.3 changed STRING to STRING(2) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CAPITALIZE : STRING(STRING_LENGTH) VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR PT : POINTER TO BYTE; pos : INT; L : INT; first : BOOL := TRUE; END_VAR (* version 1.2 20. jun. 2008 programmer hugo tested by tobias capitalize returns str with all first letters after a blank or beginning of the string are converted to uppercase. *) (* @END_DECLARATION := '0' *) PT := ADR(CAPITALIZE); CAPITALIZE := str; L := LEN(str); FOR pos := 1 TO l DO IF first THEN pt^ := TO_UPPER(pt^); END_IF; (* remember in first if the next char needs to capitalized *) first := pt^= 32; PT := pt + 1; END_FOR; (* revision histroy hm 4. mar 2008 rev 1.0 original release hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) hm 20. jun. 2008 rev 1.2 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CHARCODE : BYTE VAR_INPUT STR : STRING(10); END_VAR VAR found: STRING(1); search : STRING(10); pos: INT; i: INT; END_VAR (* version 1.2 24. oct. 2008 programmer hugo tested by hugo CHARCODE converts a HTML Character NAME INTO ITS code 'äuml' is convterted to ä 'euro' is converted to € *) (* @END_DECLARATION := '0' *) IF LEN(str) = 1 THEN CHARCODE := CODE(STR, 1); ELSIF str <> '' THEN (* construct search string *) search := CONCAT('&', str); search := CONCAT(search, ';'); WHILE pos = 0 AND (i < 4) DO i := i + 1; pos := FIND(setup.CHARNAMES[i], search); END_WHILE; found := MID(setup.CHARNAMES[i], 1, pos - 1); CHARCODE := CODE(found, 1); END_IF; (* revision history hm 13. may. 2008 rev 1.0 original version hm 19. oct. 2008 rev 1.1 changed setup constants hm 24. oct. 2008 rev 1.2 optimized code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CHARNAME : STRING(10) VAR_INPUT C : BYTE; END_VAR VAR pos: INT; i : INT; END_VAR (* version 1.4 17. dec. 2008 programmer hugo tested by oscat CHARNAME converts a Character code into its HTML character name ä is convterted to 'äuml' € is converted to 'euro' the character itself is returned if no name is available for the character *) (* @END_DECLARATION := '0' *) IF C <> 0 THEN (* construct search string from code followed by $ sign, also clear charname string*) CHARNAME := CHR_TO_STRING(C); CHARNAME := CONCAT(CHARNAME,'&'); CHARNAME := CONCAT(';', CHARNAME); WHILE pos = 0 AND (i < 4) DO i := i + 1; pos := FIND(setup.CHARNAMES[i], CHARNAME); END_WHILE; IF pos > 0 THEN CHARNAME := MID(setup.CHARNAMES[i], 10, pos + 3); (* search for end of name and truncate *) pos := FIND(CHARNAME, ';'); CHARNAME := LEFT(CHARNAME,pos - 1); ELSE CHARNAME := CHR_TO_STRING(C); END_IF; ELSE CHARNAME := ''; END_IF; (* revision history hm 13. may. 2008 rev 1.0 original version hm 16. jun. 2008 rev 1.1 changed nested call of concat for better compatibility hm 19. oct. 2008 rev 1.2 changes setup constants hm 24. oct. 2008 rev 1.3 new code for high performance hm 17. dec. 2008 rev 1.4 changed function chr to chr_to_string *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CHR_TO_STRING : STRING(1) VAR_INPUT C : BYTE; END_VAR VAR PT : POINTER TO BYTE; END_VAR (* version 1.3 17. dec. 2008 programmer hugo tested by oscat CHR creates a character from a byte input and stuffs it in a one character length string. *) (* @END_DECLARATION := '0' *) PT := ADR(CHR_TO_STRING); PT^ := C; pt := pt + 1; pt^ := 0; (* revision history hm 16 jan 2007 rev 1.0 original version hm 4. feb. 2008 rev 1.1 return string would not be terminated properly hm 29. mar. 2008 rev 1.2 changed STRING to STRING(1) hm 17. dec. 2008 rev 1.3 changed name of function from chr to chr_to_string *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CLEAN : STRING(STRING_LENGTH) VAR_INPUT IN : STRING(STRING_LENGTH); CX : STRING(80); END_VAR VAR pos: INT := 1; stop: INT; END_VAR (* version 1.0 18. jun. 2008 programmer hugo tested by oscat Clean deletes all characters from a string except the ones specified in CX. *) (* @END_DECLARATION := '0' *) (* copy input string *) CLEAN := IN; stop := LEN(in); WHILE pos <= stop DO IF FIND(cx, MID(CLEAN, 1, pos)) > 0 THEN (* charcter found skip to next one *) pos := pos + 1; ELSE (* wrong chracter needs to be deleted *) CLEAN := DELETE(CLEAN, 1, pos); stop := stop - 1; (* the string is one character shorter now *) END_IF; END_WHILE; (* revision history hm 18. jun. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION CODE : BYTE VAR_INPUT STR : STRING(STRING_LENGTH); POS : INT; END_VAR VAR PT : POINTER TO BYTE; END_VAR (* version 1.1 29. mar. 2008 programmer hugo tested by tobias code extracts the code of a character at position POS of a string STR. *) (* @END_DECLARATION := '0' *) IF pos < 1 OR pos > LEN(str) THEN CODE := 0; RETURN; ELSE PT := ADR(STR) + INT_TO_DWORD(pos - 1); CODE := pt^; END_IF; (* revision history hm 9. mar. 2008 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION COUNT_CHAR : INT VAR_INPUT str : STRING(STRING_LENGTH); chr : BYTE; END_VAR VAR l: INT; pt : POINTER TO BYTE; pos: INT; END_VAR (* version 1.1 29. mar. 2008 programmer kurt tested by hugo COUNT_CHAR counts how often a character CHAR occurs within a string STR. *) (* @END_DECLARATION := '0' *) PT := ADR(str); l := LEN(str); COUNT_CHAR := 0; FOR pos := 1 TO l DO IF pt^ = CHR THEN COUNT_CHAR := COUNT_CHAR + 1; END_IF; PT := PT + 1; END_FOR; (* revision history hm 29. feb 2008 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION COUNT_SUBSTRING : INT VAR_INPUT SEARCH : STRING; STR : STRING; END_VAR VAR pos : INT; size : INT; END_VAR (* version 1.0 20. jan. 2011 programmed kurt tested by tobias count_substring returns the number of occurences of a substring in a string *) (* @END_DECLARATION := '0' *) COUNT_SUBSTRING := 0; size := LEN(SEARCH); REPEAT pos := FIND(STR,SEARCH); IF pos > 0 THEN STR := REPLACE(STR, '', size,pos); COUNT_SUBSTRING := COUNT_SUBSTRING + 1; END_IF; UNTIL pos = 0 END_REPEAT; (* revision history ks 20. jan. 2011 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DEC_TO_BYTE : BYTE VAR_INPUT DEC : STRING(10); END_VAR VAR pt : POINTER TO BYTE; i: INT; X: BYTE; stop: INT; END_VAR (* version 1.1 30. sep 2008 programmer hugo tested by oscat DEC_TO_byte converts a decimal string into a byte. *) (* @END_DECLARATION := '0' *) pt := ADR(dec); stop := LEN(DEC); FOR I := 1 TO stop DO (* read the first character and subtract 48 to get value in decimal 0 = 48 *) x := pt^; (* calculate the value of the digit *) IF X > 47 AND x < 58 THEN DEC_TO_BYTE := DEC_TO_BYTE * 10 + X - 48; END_IF; pt := pt + 1; END_FOR; (* revision histroy hm 20. jun. 2008 rev 1.0 original release hm 30. sep.2008 rev 1.1 changed length of input string from 20 to 10 corrected an error where decoding of characters 8 and 9 would fail *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DEC_TO_DWORD : DWORD VAR_INPUT DEC : STRING(20); END_VAR VAR pt : POINTER TO BYTE; i: INT; X: BYTE; stop: INT; END_VAR (* version 1.1 30. sep. 2008 programmer hugo tested by oscat DEC_TO_DWORD converts a decimal string into a DWORD. *) (* @END_DECLARATION := '0' *) pt := ADR(dec); stop := LEN(dec); FOR I := 1 TO stop DO (* read the first character and subtract 48 to get value in decimal 0 = 48 *) x := pt^; (* calculate the value of the digit *) IF X > 47 AND x < 58 THEN DEC_TO_DWORD := DEC_TO_DWORD * 10 + X - 48; END_IF; pt := pt + 1; END_FOR; (* revision histroy hm 20. jun. 2008 rev 1.0 original release hm 30. sep. 2008 rev 1.1 corrected an error where decoding of characters 8 and 9 would fail *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DEC_TO_INT : INT VAR_INPUT DEC : STRING(10); END_VAR VAR pt : POINTER TO BYTE; i: INT; X: BYTE; sign: BOOL; stop: INT; END_VAR (* version 1.1 30. sep. 2008 programmer hugo tested by oscat DEC_TO_INT converts a decimal string into an Integer. *) (* @END_DECLARATION := '0' *) pt := ADR(dec); stop := LEN(dec); FOR I := 1 TO stop DO (* read the first character and subtract 48 to get value in decimal 0 = 48 *) x := pt^; (* calculate the value of the digit *) IF X > 47 AND x < 58 THEN DEC_TO_INT := DEC_TO_INT * 10 + X - 48; ELSIF X = 45 AND DEC_TO_INT = 0 THEN sign := TRUE; END_IF; pt := pt + 1; END_FOR; IF sign THEN DEC_TO_INT := -DEC_TO_INT; END_IF; (* revision histroy hm 20. jun. 2008 rev 1.0 original release hm 30. sep. 2008 rev 1.1 changed length of input string from 20 to 10 corrected an error where decoding of characters 8 and 9 would fail *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DEL_CHARS : STRING(STRING_LENGTH) VAR_INPUT IN : STRING(STRING_LENGTH); CX : STRING(80); END_VAR VAR pos: INT := 1; stop: INT; END_VAR (* version 1.0 18. jun. 2008 programmer hugo tested by oscat del_chars deletes all characters from a string which are specified in CX. *) (* @END_DECLARATION := '0' *) (* copy input string *) DEL_CHARS := IN; stop := LEN(in); WHILE pos <= stop DO IF FIND(cx, MID(DEL_CHARS, 1, pos)) > 0 THEN (* wrong chracter needs to be deleted *) DEL_CHARS := DELETE(DEL_CHARS, 1, pos); stop := stop - 1; (* the string is one character shorter now *) ELSE (* charcter not found skip to next one *) pos := pos + 1; END_IF; END_WHILE; (* revision history hm 18. jun. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DT_TO_STRF : STRING VAR_INPUT DTI : DT; MS : INT; FMT : STRING; LANG : INT; END_VAR VAR CONSTANT FILL : STRING(1) := '0'; BLANK : STRING(1) := ' '; END_VAR VAR ly : INT; dx: DATE; fs: STRING(10); td: TOD; tmp : INT; pos : INT; f : INT; END_VAR (* version 1.1 19. oct. 2008 programmer hugo tested by oscat DT_TO_STRINGF converts a DATETIME input to a formatted string *) (* @END_DECLARATION := '0' *) IF LANG < 1 THEN ly := language.DEFAULT; ELSE ly := MIN(language.LMAX, LANG); END_IF; (* decode date and time information *) dx := DT_TO_DATE(DTI); td := DT_TO_TOD(DTI); (* parse the format string *) DT_TO_STRF := FMT; pos := FIND(DT_TO_STRF, '#'); WHILE pos > 0 DO (* retrieve format identifier *) f := CODE(DT_TO_STRF, pos + 1); (* generate the return string according to the format character *) fs := ''; CASE f OF 65 : (* letter A retunrs the year in 4 digits *) fs := INT_TO_STRING(YEAR_OF_DATE(dx)); 66 : (* letter B returns the year in exactly 2 digits *) fs := RIGHT(INT_TO_STRING(YEAR_OF_DATE(dx)),2); 67 : (* letter C returns the month with 1 or 2 digits *) fs := INT_TO_STRING(MONTH_OF_DATE(dx)); 68 : (* letter D returns the month with exactly 2 digits *) fs := INT_TO_STRING(MONTH_OF_DATE(dx)); IF LEN(fs) < 2 THEN fs := CONCAT('0', fs); END_IF; 69 : (* letter E returns the month with 3 characters *) fs := MONTH_TO_STRING(MONTH_OF_DATE(dx), ly, 3); 70 : (* letter F returns the month with all characters *) fs := MONTH_TO_STRING(MONTH_OF_DATE(dx), ly, 0); 71 : (* letter G returns the day with up to 2 digits *) fs := INT_TO_STRING(DAY_OF_MONTH(dx)); 72 : (* letter H returns the day of the month with exactly 2 digits *) fs := INT_TO_STRING(DAY_OF_MONTH(dx)); IF LEN(fs) < 2 THEN fs := CONCAT(FILL, fs); END_IF; 73 : (* letter I returns the weekday as the number 1..7 1 = monday *) fs := INT_TO_STRING(DAY_OF_WEEK(dx)); 74 : (* letter J returns the weekday in 2 character writing *) fs := WEEKDAY_TO_STRING(DAY_OF_WEEK(dx), ly, 2); 75 : (* letter K returns the weekday with all characters *) fs := WEEKDAY_TO_STRING(DAY_OF_WEEK(dx), ly, 0); 76 : (* letter L returns AM or PM for the given DateTime *) IF td >= TOD#12:00 THEN fs := 'PM'; ELSE fs := 'AM'; END_IF; 77 : (* letter M returns the hour in 1 or 2 digit form 0..24h *) fs := INT_TO_STRING(HOUR(td)); 78 : (* letter N returns the hour in exactly 2 digit form 0..24h *) fs := INT_TO_STRING(HOUR(td)); IF LEN(fs) < 2 THEN fs := CONCAT(FILL, fs); END_IF; 79 : (* letter O returns the hour in 1 or 2 digit form 0..12h *) tmp := HOUR(td) MOD 12; IF tmp = 0 THEN tmp := 12; END_IF; fs := INT_TO_STRING(tmp); 80 : (* letter P returns the hour in exactly 2 digit form 0..12h *) tmp := HOUR(td) MOD 12; IF tmp = 0 THEN tmp := 12; END_IF; fs := INT_TO_STRING(tmp); IF LEN(fs) < 2 THEN fs := CONCAT(FILL, fs); END_IF; 81 : (* letter Q returns the minute of the hour in 1 or two digit form *) fs := INT_TO_STRING(MINUTE(td)); 82 : (* letter R returns the minute of the hour in exactly two digit form *) fs := INT_TO_STRING(MINUTE(td)); IF LEN(fs) < 2 THEN fs := CONCAT(FILL, fs); END_IF; 83 : (* letter S returns the second of the minute in 1 or two digit form *) fs := INT_TO_STRING(REAL_TO_INT(SECOND(td))); 84 : (* letter T returns the second of the minute in exactly two digit form *) fs := INT_TO_STRING(REAL_TO_INT(SECOND(td))); IF LEN(fs) < 2 THEN fs := CONCAT(FILL, fs); END_IF; 85 : (* letter U returns the milliseconds in 1 to 3 digits *) fs := INT_TO_STRING(MS); 86 : (* letter V returns the milliseconds in exactly 3 digit form *) fs := INT_TO_STRING(MS); fs := CONCAT('00',fs); fs := RIGHT(fs, 3); 87 : (* letter W returns the day of the month with exactly 2 digits first digit is filled with blank if necessary *) fs := INT_TO_STRING(DAY_OF_MONTH(dx)); IF LEN(fs) < 2 THEN fs := CONCAT(BLANK, fs); END_IF; 88 : (* letter X returns the month with exactly 2 digits first digit is filled with blank if necessary *) fs := INT_TO_STRING(MONTH_OF_DATE(dx)); IF LEN(fs) < 2 THEN fs := CONCAT(BLANK, fs); END_IF; END_CASE; DT_TO_STRF := REPLACE(DT_TO_STRF, fs, 2, pos); pos := FIND(DT_TO_STRF, '#'); END_WHILE; (* revision history hm 7. oct. 2008 rev 1.0 original version hm 19. oct. 2008 rev 1.1 changed language setup constants *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DWORD_TO_STRB : STRING(32) VAR_INPUT IN : DWORD; END_VAR VAR pt : POINTER TO BYTE; i: INT; END_VAR (* version 1.3 20. jun. 2008 programmer hugo tested by oscat DWORD_TO_STRINGB converts a DWORD to a String of Bits represented by '0' and '1' s. The lowest order bit will be on the right and the high order bit on the left. *) (* @END_DECLARATION := '0' *) (* pointer für die ausgabe ermitteln *) pt := ADR(DWORD_TO_STRB); (* die 8 ausgabecharacter ermitteln und schreiben *) FOR i := 1 TO 32 DO pt^:= BOOL_TO_BYTE(in.31) + 48; in := SHL(in,1); pt := pt + 1; END_FOR; (* der ausgabestring muss nochg mit 0 abgeschlossen werden *) pt^ := 0; (* revision history hm 9.6.2007 rev 1.0 original version hm 15.dec 2007 rev 1.1 changed code for better performance hm 29. mar. 2008 rev 1.2 changed STRING to STRING(32) hm 20. jun. 2008 rev 1.3 performance improvement *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DWORD_TO_STRF : STRING(20) VAR_INPUT IN : DWORD; N : INT; END_VAR (* version 1.0 26 jan 2007 programmer hugo tested by tobias dword_to_strF converts a DWORD, BYTE or Word to a fixed length String. the string will be filled with leading zeroes to achieve the fixed length, or if too long, the lowest digits will be used. the maximum allowed length is 20 mdigits. for example: dword_to_strf(123,4) = '0123' dword_to_strf(123,2) = '23' *) (* @END_DECLARATION := '0' *) (* limit N to max 20 characters *) (* convert dword to string first and cut to length N *) DWORD_TO_STRF := FIX(DWORD_TO_STRING(in),LIMIT(0,N,20),48,1); (* revision history hm 26. jan.2007 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(20) limit the output string to max 20 digits *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DWORD_TO_STRH : STRING(8) VAR_INPUT IN : DWORD; END_VAR VAR i : INT; temp : BYTE; pt : POINTER TO BYTE; END_VAR (* version 1.3 29. mar. 2008 programmer hugo tested by tobias DWORD_TO_STRINGH converts a DWORD to a String of Hexadecimal represented by '0' .. '9' and 'A' .. 'F'. The lowest order Character will be on the right and the high order Character on the left. *) (* @END_DECLARATION := '0' *) (* read output adress to pointer *) pt := ADR(DWORD_TO_STRH) + 8; (* wirte the closing byte for the string *) pt^:= 0; (* write the 8 hex characters backwards *) FOR i := 1 TO 8 DO; (* decrement the pointer *) pt := pt - 1; (* read the lowest order hex value *) temp := DWORD_TO_BYTE(in AND 16#0000000F); (* convert value to hex character *) IF temp <= 9 THEN temp := temp + 48; ELSE temp := temp + 55; END_IF; (* write character to output string *) PT^ := temp; (* shift in for nect hex character *) in := SHR(in,4); END_FOR; (* code beofre rev 1.2 FOR i := 1 TO 8 DO X := DWORD_TO_BYTE(in AND 2#1111); IF X <= 9 THEN X := X + 48; ELSE X := X + 55; END_IF; Cx := CHR(X); temp := CONCAT(Cx, temp); in := SHR(in,4); END_FOR; DWORD_TO_STRH := temp; *) (* revision history hm 9. jun. 2007 rev 1.0 original version hm 11. sep. 2007 rev 1.1 changed coding for compatibility with twincat, concat cannot support a function as an argument. hm 15. dec. 2007 rev 1.2 changed code for better performance hm 29. mar. 2008 rev 1.3 changed STRING to STRING(8) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION EXEC : STRING(STRING_LENGTH) VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR pos: INT; R1: REAL; R2: REAL; operator: STRING(10); END_VAR (* version 1.4 29. mar. 2008 programmer hugo tested by tobias exec executes a mathematical term and returns the result as a string the term can only be a simple term without brackets and only one operator allowed operastors are: (+, -, *, /, ^, sqrt, sin, cos, tan). *) (* @END_DECLARATION := '0' *) (* extract both numbers and operator *) EXEC := UPPERCASE(TRIM(str)); pos := FINDB_NONUM(EXEC); IF pos > 1 THEN R1 := STRING_TO_REAL(LEFT(EXEC,pos-1)); END_IF; R2 := STRING_TO_REAL(RIGHT(EXEC,LEN(EXEC)-pos)); EXEC := LEFT(EXEC,pos); pos := FINDB_NUM(EXEC); operator := RIGHT(EXEC,LEN(EXEC) - pos); IF operator = '' AND LEN(str) = 0 THEN EXEC := ''; RETURN; ELSIF operator = '' THEN EXEC := str; RETURN; END_IF; IF operator = '^' THEN EXEC := REAL_TO_STRING(EXPT(R1, R2)); ELSIF operator = 'SQRT' THEN EXEC := REAL_TO_STRING(SQRT(R2)); ELSIF operator = 'SIN' THEN EXEC := REAL_TO_STRING(SIN(r2)); ELSIF operator = 'COS' THEN EXEC := REAL_TO_STRING(COS(r2)); ELSIF operator = 'TAN' THEN EXEC := REAL_TO_STRING(TAN(R2)); ELSIF operator = '*' THEN EXEC := REAL_TO_STRING(R1 * R2); ELSIF operator = '/' THEN IF R2 <> 0 THEN EXEC := REAL_TO_STRING(R1 / R2); ELSE EXEC := 'ERROR'; END_IF; ELSIF operator = '+' THEN EXEC := REAL_TO_STRING(r1 + r2); ELSIF operator = '-' THEN EXEC := REAL_TO_STRING(r1 - r2); ELSE EXEC := 'ERROR'; END_IF; IF EXEC = 'ERROR' THEN RETURN; (* some systems deliver integer instead of real *) ELSIF FIND(EXEC,'.') = 0 THEN EXEC := CONCAT(EXEC, '.0'); (* some systems deliver n. instead of n.0 ! *) ELSIF RIGHT(EXEC,1) = '.' THEN EXEC := CONCAT(EXEC,'0'); END_IF; (* revision history hm 6.feb 2007 rev 1.1 cos has to be written in uppercase divide by 0 will return an error hm 5. mar. 2008 rev 1.2 add a 0 to the string if a '.' is at the end of the string hm 20. mar. 2008 rev 1.3 make sure the function always returns a real value in the form of x.y hm 29. mar. 2008 rev 1.4 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FILL : STRING(STRING_LENGTH) VAR_INPUT C : BYTE; L : INT; END_VAR VAR i: INT; sx: STRING(1); END_VAR (* version 1.1 17. dec. 2008 programmer hugo tested by tobias the function fill creates a string at length L of characters C. *) (* @END_DECLARATION := '0' *) Sx := CHR_TO_STRING(c); (* limit L to maximum 80 characters the length of a standard string *) L := LIMIT(0,L,STRING_LENGTH); (* create a string of characters to be connected to str *) FOR i := 1 TO 8 DO FILL := CONCAT(FILL,FILL); IF L.7 THEN FILL := CONCAT(FILL,Sx); END_IF; L := SHL(L,1); END_FOR; (* revision histroy hm 29. mar. 2008 rev 1.0 original release hm 17. dec. 2008 rev 1.1 changed function chr to chr_to_string *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FIND_CHAR : INT VAR_INPUT str : STRING(STRING_LENGTH); pos : INT; END_VAR VAR i: INT; pt : POINTER TO ARRAY[1..255] OF BYTE; stop: INT; X: BYTE; END_VAR (* version 1.3 21. oct. 2008 programmer hugo tested by tobias find_char searches str and returns the starting position of the first character that is not a control character. control characters are the ascii character 00 .. 31 and 127 *) (* @END_DECLARATION := '0' *) pt := ADR(str); stop := LEN(str); FOR i := MAX(pos,1) TO stop DO; X := pt^[i]; IF X > 31 AND ((setup.EXTENDED_ASCII AND X <> 127) OR (NOT setup.EXTENDED_ASCII AND X < 127)) THEN FIND_CHAR := i; RETURN; END_IF; END_FOR; FIND_CHAR := 0; (* revision history hm 29. feb 2008 rev 1.0 original version hm 26. mar. 2008 rev 1.1 char will now accept extended ascii hm 29. mar. 2008 rev 1.2 changed STRING to STRING(STRING_LENGTH) hm 21. oct. 2008 rev 1.3 changes setup constants optimized code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FIND_CTRL : INT VAR_INPUT str : STRING(STRING_LENGTH); pos : INT; END_VAR VAR i: INT; pt : POINTER TO ARRAY[1..255] OF BYTE; stop: INT; x: BYTE; END_VAR (* version 1.2 29. mar. 2008 programmer hugo tested by tobias find_ctrl searches str and returns the starting position of a control character control characters are the ascii character 00 .. 31 and 127. *) (* @END_DECLARATION := '0' *) pt := ADR(str); stop := LEN(str); FOR i := MAX(pos,1) TO stop DO; x := PT^[I]; IF x < 32 OR X = 127 THEN FIND_CTRL := i; RETURN; END_IF; END_FOR; FIND_CTRL := 0; (* revision history hm 29. feb 2008 rev 1.0 original version hm 26. mar. 2008 rev 1.1 character 127 is now recognized as control hm 29. mar. 2008 rev 1.2 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FIND_NONUM : INT VAR_INPUT str : STRING(STRING_LENGTH); pos : INT; END_VAR VAR i: INT; pt : POINTER TO ARRAY[1..255] OF BYTE; end: INT; X: BYTE; END_VAR (* version 1.3 21. oct. 2008 programmer hugo tested by tobias find_noNum searches str and returns the first position which is not a number. a number is characterized by a letter "0..9" or "." *) (* @END_DECLARATION := '0' *) pt := ADR(str); end := LEN(str); FOR i := MAX(pos,1) TO end DO; X := pt^[i]; IF (X < 48 AND X <> 46) OR X > 57 THEN FIND_NONUM := i; RETURN; END_IF; END_FOR; FIND_NONUM := 0; (* revision history hm 6. oct. 2006 rev 1.0 original version hm 29. feb 2008 rev 1.1 added input pos to start search at position hm 29. mar. 2008 rev 1.2 changed STRING to STRING(STRING_LENGTH) hm 21. oct. 2008 rev 1.3 optimized code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FIND_NUM : INT VAR_INPUT str : STRING(STRING_LENGTH); pos : INT; END_VAR VAR i : INT; pt : POINTER TO ARRAY[1..255] OF BYTE; stop : INT; X: BYTE; END_VAR (* version 1.2 29. mar. 2008 programmer hugo tested by tobias find_Num searches str and returns the starting position of a number a number is characterized by a letter "0..9" or "." *) (* @END_DECLARATION := '0' *) pt := ADR(str); stop := LEN(str); FOR i := MAX(pos,1) TO stop DO; X := pt^[i]; IF (X > 47 AND X < 58) OR X = 46 THEN FIND_NUM := i; RETURN; END_IF; END_FOR; FIND_NUM := 0; (* revision history hm 6. oct. 2006 rev 1.0 original version hm 29. feb 2008 rev 1.1 added input pos to start search at position hm 29. mar. 2008 rev 1.2 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FINDB : INT VAR_INPUT str1 : STRING(STRING_LENGTH); str2 : STRING(STRING_LENGTH); END_VAR VAR pos: INT; length: INT; END_VAR (* version 1.3 29. mar. 2008 programmer hugo tested by tobias the function find searches an str1 for the presence of str2 and returns the first position of str1 of the last presence in instring. the function is similar to find except it searches from the right to left. a 0 is returned if the string is not found. *) (* @END_DECLARATION := '0' *) length := LEN(str2); FOR pos := LEN(str1) - length + 1 TO 1 BY -1 DO IF MID(str1,length,pos) = str2 THEN FindB := pos; RETURN; END_IF; END_FOR; FindB := 0; (* revision history hm 6. oct 2006 rev 1.0 original version hm 15 dec 2007 rev 1.1 changed code for better performance hm 29. feb 2008 rev 1.2 added findb := 0 for compatibility reasons hm 29. mar. 2008 rev 1.3 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FINDB_NONUM : INT VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR pos: INT; pt : POINTER TO BYTE; END_VAR (* version 1.3 21. oct. 2008 programmer hugo tested by oscat findB_noNum searches str backwards and returns the last position which is not a number. a number is characterized by a letter "0..9" or "." *) (* @END_DECLARATION := '0' *) pt := ADR(str) + LEN(str) - 1; FOR pos := LEN(str) TO 1 BY -1 DO; IF (PT^ < 48 AND PT^ <> 46) OR PT^ > 57 THEN FINDB_NONUM := pos; RETURN; END_IF; PT := PT - 1; END_FOR; FINDB_NONUM := 0; (* revision history hm 6. oct 2006 rev 1.0 original version hm 29. feb 2008 rev 1.1 improved performance hm 29. mar. 2008 rev 1.2 changed STRING to STRING(STRING_LENGTH) hm 21. oct. 2008 rev 1.3 optimized code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FINDB_NUM : INT VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR pos: INT; pt : POINTER TO BYTE; END_VAR (* version 1.2 29. mar. 2008 programmer hugo tested by tobias findB_Num searches str backward and returns the last position of a number a number is characterized by a letter "0..9" or "." *) (* @END_DECLARATION := '0' *) pt := ADR(str) + LEN(str) - 1; FOR pos := LEN(str) TO 1 BY -1 DO; IF (PT^ > 47 AND PT^ < 58) OR PT^ = 46 THEN FINDB_NUM := pos; RETURN; END_IF; PT := PT - 1; END_FOR; FINDB_NUM := 0; (* revision history hm 6. oct 2006 rev 1.0 original version hm 29. feb 2008 rev 1.1 improved performance hm 29. mar. 2008 rev 1.2 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FINDP : INT VAR_INPUT str : STRING(STRING_LENGTH); src : STRING(STRING_LENGTH); pos: INT; END_VAR VAR i: INT; ls: INT; lx: INT; stp: INT; END_VAR (* version 1.2 29. mar. 2008 programmer hugo tested by tobias the function findP searches a string str for the occurence of src beginning at the position pos. *) (* @END_DECLARATION := '0' *) ls := LEN(str); lx := LEN(src); IF ls < lx OR lx = 0 THEN RETURN; END_IF; stp := ls - lx + 1; FOR i := MAX(pos,1) TO stp DO IF MID(str,lx,i) = src THEN FINDP := i; RETURN; END_IF; END_FOR; FINDP := 0; (* revision histroy hm 4. feb. 2008 rev 1.0 original release hm 29. feb 2008 rev 1.1 ADDED MAX(pos,1) in loop initialization hm 29. mar. 2008 rev 1.2 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FIX : STRING(STRING_LENGTH) VAR_INPUT str : STRING(STRING_LENGTH); L : INT; C : BYTE; M : INT; END_VAR VAR N : INT; SX: STRING(STRING_LENGTH); END_VAR (* version 1.0 29. mar. 2008 programmer hugo tested by tobias the function fix truncates a string at length L or if the string is shorter then L it will be filled with ending Spaces till its length = N. *) (* @END_DECLARATION := '0' *) (* make sure L does not exceed the limits of a string *) N := LIMIT(0,L,STRING_LENGTH) - LEN(str); IF N <= 0 THEN (* truncate the string at length N *) IF M = 1 THEN FIX := RIGHT(str,L); ELSE FIX := LEFT(str,L); END_IF; ELSIF M = 1 THEN (* connect fill characters at the beginning *) sx := FILL(C,N); FIX := CONCAT(sx,str); ELSIF M = 2 THEN (* center str beween fill characters *) (* for an uneven number of fill characters, there is one more at the end *) sx := FILL(C,SHR(N+1,1)); FIX := CONCAT(str,sx); SX := LEFT(sx,SHR(N,1)); FIX := CONCAT(sx,FIX); ELSE (* connect fill characters at the end *) SX := FILL(C,N); FIX := CONCAT(str,SX); END_IF; (* revision histroy hm 29. mar. 2008 rev 1.0 original release *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FLOAT_TO_REAL : REAL VAR_INPUT FLT : STRING(20); END_VAR VAR pt : POINTER TO ARRAY[1..20] OF BYTE; i: INT; X: BYTE; sign: INT := 1; stop: INT; tmp: DINT; d : INT; END_VAR (* version 1.3 23. oct. 2008 programmer hugo tested by oscat FLOAT_TO_REAL converts a string to a REAL. the function ignores all wrong characters. the comma can be , or . the exponent has to start with capital E or lowercase e . *) (* @END_DECLARATION := '0' *) pt := ADR(FLT); stop := LEN(FLT); (* we first check for sign and exit if first number or dot is reached *) FOR i := 1 TO stop DO X := pt^[i]; IF X > 47 AND X < 58 OR X = 46 THEN EXIT; ELSIF X = 45 THEN (* code 45 is sign *) sign := -1; END_IF; END_FOR; (* now we scan numbers till end or dot or E is reached *) FOR i := i TO stop DO X := pt^[i]; IF X = 44 OR X = 46 OR X = 69 OR X = 101 THEN EXIT; (* calculate the value of the digit *) ELSIF X > 47 AND x < 58 THEN tmp := tmp * 10 + X - 48; END_IF; END_FOR; (* process the portion after the comma if comma or dot is reached exit if exponent starts *) IF x = 44 OR X = 46 THEN FOR i := i + 1 TO stop DO X := pt^[i]; IF X = 69 OR X = 101 THEN EXIT; ELSIF x > 47 AND x < 58 THEN tmp := tmp * 10 + X - 48; d := d - 1; END_IF; END_FOR; END_IF; (* process exponent if present *) IF X = 69 OR X = 101 THEN d := d + DEC_TO_INT(RIGHT(FLT, stop - i)); END_IF; FLOAT_TO_REAL := EXPN(10, d) * DINT_TO_REAL(TMP * SIGN); (* revision histroy hm 22. jun. 2008 rev 1.0 original release hm 2. oct. 2008 rev 1.1 fixed an error, characters 8 and 9 would not be converted hm 22. oct. 2008 rev 1.2 last fix was not done correctly hm 23. oct. 2008 rev 1.3 optimzed code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FSTRING_TO_BYTE : BYTE VAR_INPUT IN : STRING(12); END_VAR (* version 1.1 20. sep. 2008 programmer hugo tested by oscat FSTRING_TO_BYTE converts a formatted string into a byte. the string can be of the form 2#01010, 8#7234, 16#2AD3 and 1234 *) (* @END_DECLARATION := '0' *) IF LEFT(IN, 2) = '2#' THEN (* we need to convert binary *) FSTRING_TO_BYTE := BIN_TO_BYTE(RIGHT(in, LEN(in) - 2)); ELSIF LEFT(in, 2) = '8#' THEN (* weneed to convert octals *) FSTRING_TO_BYTE := OCT_TO_BYTE(RIGHT(in, LEN(in) - 2)); ELSIF LEFT(in, 3) = '16#' THEN (* we need to convert hexadecimal *) FSTRING_TO_BYTE := HEX_TO_BYTE(RIGHT(in, LEN(in) - 3)); ELSE (* we assume decimal representation *) FSTRING_TO_BYTE := DEC_TO_BYTE(CLEAN(in,'0123456789')); END_IF; (* revision histroy hm 18. jun. 2008 rev 1.0 original release hm 20. sep. 2008 rev 1.1 changed length of input string from 20 to 12 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FSTRING_TO_DT : DT VAR_INPUT SDT : STRING(60); FMT : STRING(60); END_VAR VAR CONSTANT ignore: STRING(1) := '*'; (* ignore character is * *) fchar : STRING(1) := '#'; (* format character is # *) END_VAR VAR c: STRING(1); tmp: STRING(20); end: INT; dy : INT := 1970; dm : INT := 1; dd : INT := 1; th : INT; tm : INT; ts : INT; END_VAR (* version 1.0 24. sep. 2008 programmer hugo tested by oscat FSTRING_TO_DT converts Formatted String into a DT value. *) (* @END_DECLARATION := '0' *) (* scan input string *) WHILE fmt <> '' DO c := LEFT(fmt,1); IF c = ignore THEN (* skip ignore characters *) fmt := DELETE(fmt,1,1); sdt := DELETE(sdt,1,1); ELSIF C = fchar THEN (* format chracter found skip format char and read identifier *) (* store format identifier in c and skip to next char in fmt *) c := MID(fmt,1,2); fmt := DELETE(fmt,2,1); (* extract the substring until the end of sdt or to next char of fmt *) IF fmt = '' THEN tmp := sdt; ELSE (* serach for end of substring *) end := FIND(sdt, LEFT(fmt,1))-1; tmp := LEFT(sdt, end); sdt := DELETE(sdt, end,1); END_IF; (* extract information from substring *) IF c = 'Y' THEN dy := STRING_TO_INT(tmp); IF dy < 100 THEN dy := dy + 2000; END_IF; ELSIF c = 'M' THEN dm := STRING_TO_INT(tmp); ELSIF c = 'N' THEN dm := FSTRING_TO_MONTH(tmp,0); ELSIF c = 'D' THEN dd := STRING_TO_INT(tmp); ELSIF c = 'h' THEN th := STRING_TO_INT(tmp); ELSIF c = 'm' THEN tm := STRING_TO_INT(tmp); ELSIF c = 's' THEN ts := STRING_TO_INT(tmp); END_IF; ELSIF c = LEFT(sdt,1) THEN (* skip matching characters *) fmt := DELETE(fmt,1,1); sdt := DELETE(sdt,1,1); ELSE RETURN; END_IF; END_WHILE; FSTRING_TO_DT := SET_DT(dy,dm,dd,th,tm,ts); (* revision histroy hm 24. sep. 2008 rev 1.0 original release *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FSTRING_TO_DWORD : DWORD VAR_INPUT IN : STRING(40); END_VAR (* version 1.1 20. sep. 2008 programmer hugo tested by oscat FSTRING_TO_BYTE converts a formatted string into a byte. the string can be of the form 2#01010, 8#7234, 16#2AD3 and 1234 *) (* @END_DECLARATION := '0' *) IF LEFT(IN, 2) = '2#' THEN (* we need to convert binary *) FSTRING_TO_DWORD := BIN_TO_DWORD(RIGHT(in, LEN(in) - 2)); ELSIF LEFT(in, 2) = '8#' THEN (* weneed to convert octals *) FSTRING_TO_DWORD := OCT_TO_DWORD(RIGHT(in, LEN(in) - 2)); ELSIF LEFT(in, 3) = '16#' THEN (* we need to convert hexadecimal *) FSTRING_TO_DWORD := HEX_TO_DWORD(RIGHT(in, LEN(in) - 3)); ELSE (* we assume decimal representation *) FSTRING_TO_DWORD := DEC_TO_DWORD(CLEAN(in,'0123456789')); END_IF; (* revision histroy hm 18. jun. 2008 rev 1.0 original release hm 20. sep. 2008 rev 1.1 changed length of input dtring from 20 to 40 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FSTRING_TO_MONTH : INT VAR_INPUT MTH : STRING(20); LANG : INT; END_VAR VAR lx: INT; END_VAR (* version 1.2 25. oct 2008 programmer hugo tested by oscat FSTRING_TO_MONTH converts a string into a month, the string can be a name for the month or a number. the function is language sensitve when LANG > 1 and checks for all languages when LANG = 0 *) (* @END_DECLARATION := '0' *) IF LANG = 0 THEN lx := LANGUAGE.DEFAULT; ELSE lx := MIN(LANG, LANGUAGE.LMAX); END_IF; MTH := TRIM(mth); MTH := CAPITALIZE(LOWERCASE(MTH)); FOR FSTRING_TO_MONTH := 1 TO 12 DO IF MTH = language.MONTHS[lx, FSTRING_TO_MONTH] THEN RETURN; END_IF; IF MTH = language.MONTHS3[lx, FSTRING_TO_MONTH] THEN RETURN; END_IF; END_FOR; (* since no name matched try to decode as integer *) FSTRING_TO_MONTH := STRING_TO_INT(MTH); (* revision histroy hm 25. sep. 2008 rev 1.0 original release hm 19. oct. 2008 rev 1.1 changed language setup constants hm 25. oct. 2008 rev 1.2 optimized code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FSTRING_TO_WEEK : BYTE VAR_INPUT WEEK : STRING(60); LANG : INT; END_VAR VAR pos: INT; END_VAR (* version 1.1 25. oct. 2008 programmer hugo tested by oscat FSTRING_TO_WEEK converts a list of weekdays into a byte where each bit represents a day of the week. bit 6 = mo, 0 = su; *) (* @END_DECLARATION := '0' *) pos := FIND(WEEK, ','); WHILE pos > 0 DO FSTRING_TO_WEEK := FSTRING_TO_WEEK OR SHR(BYTE#128, FSTRING_TO_WEEKDAY(MID(WEEK, pos - 1, 1), LANG)); WEEK := RIGHT(WEEK, LEN(Week) - pos); pos := FIND(WEEK, ','); END_WHILE; FSTRING_TO_WEEK := (FSTRING_TO_WEEK OR SHR(BYTE#128, FSTRING_TO_WEEKDAY(WEEK, LANG))) AND BYTE#127; (* revision histroy hm 18. jun. 2008 rev 1.0 original release hm 25. oct. 2008 rev 1.1 using language defauls and input lang *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION FSTRING_TO_WEEKDAY : INT VAR_INPUT WDAY : STRING(20); LANG : INT; END_VAR VAR tmp: STRING(2); i: INT; ly: INT; END_VAR (* version 1.2 25. oct. 2008 programmer hugo tested by oscat FSTRING_TO_WEEKDAY converts a weekday string into an integer 1..7. *) (* @END_DECLARATION := '0' *) IF LANG = 0 THEN ly := LANGUAGE.DEFAULT; ELSE ly := MIN(LANG, LANGUAGE.LMAX); END_IF; (* tmp needs to be calculated first otherwise find can return wrong values *) tmp := TRIM(wday); tmp := CAPITALIZE(LOWERCASE(tmp)); FOR i := 1 TO 7 DO IF language.WEEKDAYS2[ly, i] = tmp THEN FSTRING_TO_WEEKDAY := i; RETURN; END_IF; END_FOR; FSTRING_TO_WEEKDAY := STRING_TO_INT(WDAY); (* revision histroy hm 18. jun. 2008 rev 1.0 original release hm 18. jul. 2008 rev 1.1 changed nested call of left(trim()) for compatibility reasons hm 25. oct. 2008 rev 1.2 using language constants *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION HEX_TO_BYTE : BYTE VAR_INPUT HEX : STRING(5); END_VAR VAR pt : POINTER TO BYTE; i: INT; X: BYTE; stop: INT; END_VAR (* version 1.1 20. sep. 2008 programmer hugo tested by oscat HEX_TO_BYTE converts a Hexadecimal string into a byte. *) (* @END_DECLARATION := '0' *) pt := ADR(hex); stop := LEN(hex); FOR I := 1 TO stop DO (* read the first character and subtract 48 to get value in decimal 0 = 48 *) x := pt^; (* calculate the value of the digit *) IF X > 47 AND x < 58 THEN HEX_TO_BYTE := SHL(HEX_TO_BYTE,4) + X - 48; ELSIF X > 64 AND X < 71 THEN HEX_TO_BYTE := SHL(HEX_TO_BYTE,4) + X - 55; ELSIF X > 96 AND X < 103 THEN HEX_TO_BYTE := SHL(HEX_TO_BYTE,4) + X - 87; END_IF; pt := pt + 1; END_FOR; (* revision histroy hm 18. jun. 2008 rev 1.0 original release hm 20. sep.2008 rev 1.1 changed length of input string from 20 to 5 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION HEX_TO_DWORD : DWORD VAR_INPUT Hex : STRING(20); END_VAR VAR pt : POINTER TO BYTE; i: INT; X: BYTE; stop: INT; END_VAR (* version 1.4 18. jun. 2008 programmer hugo tested by tobias HEX_TO_DWORD converts a Hexadecimal string into a DWORD. *) (* @END_DECLARATION := '0' *) pt := ADR(hex); stop := LEN(hex); FOR I := 1 TO stop DO (* read the first character and subtract 48 to get value in decimal 0 = 48 *) x := pt^; (* calculate the value of the digit *) IF X > 47 AND x < 58 THEN HEX_TO_DWORD := SHL(HEX_TO_DWORD,4) + X - 48; ELSIF X > 64 AND X < 71 THEN HEX_TO_DWORD := SHL(HEX_TO_DWORD,4) + X - 55; ELSIF X > 96 AND X < 103 THEN HEX_TO_DWORD := SHL(HEX_TO_DWORD,4) + X - 87; END_IF; pt := pt + 1; END_FOR; (* revision histroy hm 2.10.2007 rev 1.0 original release hm 19.11.2007 rev 1.1 changed type of function from int to dword hm 4. mar 2008 rev 1.2 added support for a..f and return 0 for invalid string hm 29. mar. 2008 rev 1.3 changed STRING to STRING(8) hm 18. jun. 2008 rev 1.4 changed input hex to STRING(20) function now ignores wrong characters *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION IS_ALNUM : BOOL VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR L: INT; pt : POINTER TO BYTE; pos: INT; END_VAR (* version 1.1 29. mar. 2008 programmer kurt tested by hugo ISC_ALNUM testet ob in einem string nur Zahlen 0..9 vorkommen. *) (* @END_DECLARATION := '0' *) PT := ADR(str); L := LEN(str); FOR pos := 1 TO L DO IF NOT (ISC_ALPHA(pt^) OR ISC_NUM(pt^)) THEN IS_ALNUM := FALSE; RETURN; END_IF; PT := PT + 1; END_FOR; IS_ALNUM := L > 0; (* revision history hm 29. feb 2008 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION IS_ALPHA : BOOL VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR L: INT; pt : POINTER TO BYTE; pos: INT; END_VAR (* version 1.1 29. mar. 2008 programmer kurt tested by hugo IS_ALPHA testet ob in einem string nur Zeichen a-z oder A - Z vorkommen. *) (* @END_DECLARATION := '0' *) PT := ADR(str); L := LEN(str); FOR pos := 1 TO L DO IF NOT ISC_ALPHA(pt^) THEN IS_ALPHA := FALSE; RETURN; END_IF; PT := PT + 1; END_FOR; IS_ALPHA := L > 0; (* revision history hm 29. feb 2008 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION IS_CC : BOOL VAR_INPUT str : STRING(STRING_LENGTH); cmp : STRING(STRING_LENGTH); END_VAR VAR L: INT; pos: INT; END_VAR (* version 1.1 29. mar. 2008 programmer Tobias tested by hugo ISC_CC testet ob ein string nur aus Zeichen des Strings CMP besteht. *) (* @END_DECLARATION := '0' *) L := LEN(str); FOR pos := 1 TO L DO IF FIND(CMP,MID(str,1,pos)) = 0 THEN RETURN; END_IF; END_FOR; IS_CC := L > 0; (* revision history hm 19. mar 2008 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION IS_CTRL : BOOL VAR_INPUT STR : STRING(STRING_LENGTH); END_VAR VAR L: INT; pt : POINTER TO BYTE; pos: INT; END_VAR (* version 1.1 29. mar 2008 programmer kurt tested by hugo IS_CTRL testet ob in einem string nur Steuerzeichen (Char < 32) vorkommen. *) (* @END_DECLARATION := '0' *) PT := ADR(str); L := LEN(str); FOR pos := 1 TO L DO IF NOT (ISC_CTRL(pt^)) THEN IS_CTRL := FALSE; RETURN; END_IF; PT := PT + 1; END_FOR; IS_CTRL := L > 0; (* revision history hm 29. feb. 2008 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION IS_HEX : BOOL VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR L: INT; pt : POINTER TO BYTE; pos: INT; END_VAR (* version 1.1 29. mar. 2008 programmer kurt tested by hugo IS_HEX testet ob in einem string nur Zahlen 0..9 oder A..F oder a..f vorkommen. *) (* @END_DECLARATION := '0' *) PT := ADR(str); L := LEN(str); FOR pos := 1 TO L DO IF NOT (ISC_HEX(pt^)) THEN IS_HEX := FALSE; RETURN; END_IF; PT := PT + 1; END_FOR; IS_HEX := L > 0; (* revision history hm 29. feb 2008 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION IS_LOWER : BOOL VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR l: INT; pt : POINTER TO BYTE; pos: INT; END_VAR (* version 1.1 29. mar. 2008 programmer kurt tested by hugo IS_LOWER testet ob in einem string nur kleinbuchstaben vorkommen. *) (* @END_DECLARATION := '0' *) PT := ADR(str); L := LEN(str); FOR pos := 1 TO L DO IF NOT (ISC_LOWER(pt^)) THEN IS_LOWER := FALSE; RETURN; END_IF; PT := PT + 1; END_FOR; IS_LOWER := L > 0; (* revision history hm 29. feb 2008 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION IS_NCC : BOOL VAR_INPUT str : STRING(STRING_LENGTH); cmp : STRING(STRING_LENGTH); END_VAR VAR L: INT; pos: INT; END_VAR (* version 1.1 29. mar. 2008 programmer Tobias tested by hugo IS_NCC testet ob in einem string keine Zeichen des Strings CMP enthalten sind. *) (* @END_DECLARATION := '0' *) L := LEN(str); FOR pos := 1 TO L DO IF FIND(CMP,MID(str,1,pos)) > 0 THEN RETURN; END_IF; END_FOR; IS_NCC := TRUE; (* revision history hm 19. mar 2008 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION IS_NUM : BOOL VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR L : INT; pt : POINTER TO BYTE; pos: INT; END_VAR (* version 1.1 29. mar. 2008 programmer kurt tested by hugo IS_NUM testet ob in einem string nur Zahlen 0..9 vorkommen. *) (* @END_DECLARATION := '0' *) PT := ADR(str); L := LEN(str); FOR pos := 1 TO L DO IF NOT (ISC_NUM(pt^)) THEN IS_NUM := FALSE; RETURN; END_IF; PT := PT + 1; END_FOR; IS_NUM := L > 0; (* revision history hm 29. feb 2008 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION IS_UPPER : BOOL VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR L: INT; pt : POINTER TO BYTE; pos: INT; END_VAR (* version 1.1 29. mar. 2008 programmer kurt tested by hugo IS_UPPER testet ob in einem string keine Kleinbuchstaben vorkommen. *) (* @END_DECLARATION := '0' *) PT := ADR(str); L := LEN(str); FOR pos := 1 TO L DO IF NOT (ISC_UPPER(pt^)) THEN IS_UPPER := FALSE; RETURN; END_IF; PT := PT + 1; END_FOR; IS_UPPER := L > 0; (* revision history hm 29. feb 2008 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ISC_ALPHA : BOOL VAR_INPUT IN : BYTE; END_VAR (* version 1.1 19. oct. 2008 programmer oscat tested by oscat ISC_ALPHA checks if a character is a..z or A..Z. *) (* @END_DECLARATION := '0' *) IF setup.EXTENDED_ASCII THEN ISC_ALPHA := (in > 64 AND in < 91) OR (in > 191 AND in <> 215 AND in <> 247) OR (in > 96 AND in < 123); ELSE ISC_ALPHA := (IN > 64 AND IN < 91) OR (in > 96 AND in < 123); END_IF; (* revision history hm 6. mar. 2008 rev 1.0 original version hm 19. oct. 2008 rev 1.1 changes setup constants *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ISC_CTRL : BOOL VAR_INPUT IN : BYTE; END_VAR (* version 1.0 6. mar 2008 programmer oscat tested by hugo ISC_ALPHA checks if a character is a control character. *) (* @END_DECLARATION := '0' *) ISC_CTRL := IN < 32 OR IN = 127; (* revision history hm 6. mar. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ISC_HEX : BOOL VAR_INPUT IN : BYTE; END_VAR (* version 1.0 6. mar 2008 programmer oscat tested by hugo ISC_HEX checks if a character is 0..9, A..F, a..f. *) (* @END_DECLARATION := '0' *) ISC_HEX := (IN > 47 AND IN < 58) OR (IN > 64 AND IN < 71) OR (IN > 96 AND IN < 103); (* revision history hm 6. mar. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ISC_LOWER : BOOL VAR_INPUT IN : BYTE; END_VAR (* version 1.1 19. oct. 2008 programmer hugo tested by oscat isc_lower checks if a character is lowercase. *) (* @END_DECLARATION := '0' *) IF setup.EXTENDED_ASCII THEN ISC_LOWER := ((in > 96) AND (in < 123)) OR ((in > 222) AND (in <> 247)); ELSE ISC_LOWER := ((in > 96) AND (in < 123)); END_IF; (* revision history hm 6. mar. 2008 rev 1.0 original version hm 19. oct. 2008 rev 1.1 changes setup constants *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ISC_NUM : BOOL VAR_INPUT IN : BYTE; END_VAR (* version 1.0 6. mar 2008 programmer oscat tested by hugo ISC_NUM checks if a character is 0..9. *) (* @END_DECLARATION := '0' *) ISC_NUM := IN > 47 AND IN < 58; (* revision history hm 6. mar. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION ISC_UPPER : BOOL VAR_INPUT IN : BYTE; END_VAR (* version 1.1 19. oct. 2008 programmer hugo tested by oscat ISC_upper checks if a character is uppercase *) (* @END_DECLARATION := '0' *) IF setup.EXTENDED_ASCII THEN ISC_UPPER := ((in > 64) AND (in < 91)) OR (((in > 191) AND (in < 223)) AND (in <> 215)); ELSE ISC_UPPER := ((in > 64) AND (in < 91)); END_IF; (* revision history hm 6. mar. 2008 rev 1.0 original version hm 19. oct. 2008 rev 1.1 changes setup constants *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LOWERCASE : STRING(STRING_LENGTH) VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR PT : POINTER TO BYTE; pos: INT; l: INT; END_VAR (* version 1.3 29. mar. 2008 programmer hugo tested by tobias lowercase returns str while all letters A..Z and Ä.Ö,Ü are converted to lowercase. *) (* @END_DECLARATION := '0' *) PT := ADR(LOWERCASE); lowercase := str; l := LEN(str); FOR pos := 1 TO l DO pt^ := TO_LOWER(pt^); PT := PT + 1; END_FOR; (* revision histroy hm 6. oct. 2006 rev 1.0 original release hm 4. feb. 2008 rev 1.1 improved performance added Ä.Ö,Ü hm 6. mar. 2008 rev 1.2 added support for exteded Ascii hm 29. mar. 2008 rev 1.3 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK MESSAGE_4R VAR_INPUT M0 : STRING(STRING_LENGTH); M1 : STRING(STRING_LENGTH); M2 : STRING(STRING_LENGTH); M3 : STRING(STRING_LENGTH); MM : INT := 3; ENQ : BOOL := TRUE; CLK : BOOL := TRUE; T1 : TIME := T#3s; END_VAR VAR_OUTPUT MX : STRING(STRING_LENGTH); MN : INT; TR : BOOL; END_VAR VAR timer : TON; edge: BOOL; END_VAR (* version 1.1 27. oct. 2008 programmer hugo tested by tobias this function generates a rotation meassage with up to 4 strings. on each rising edge of EN the next message in line will be displayed. when EN stays high longer then one cycle, the next message will be displayed automatically after the time T1 is elapsed. the output MX is the generated message and CX is a counter 0..3 signaling the current message displayed. the displayed messages are 0 .. MM. *) (* @END_DECLARATION := '0' *) (* check for rising edge on EN *) TR := FALSE; IF ENQ THEN IF (NOT edge AND clk) OR timer.q THEN MN := INC1(MN, MM); TR := TRUE; timer(in := FALSE); CASE MN OF 0 : MX := M0; 1 : MX := M1; 2 : MX := M2; 3 : MX := M3; END_CASE; END_IF; edge := clk; timer( in := CLK, pt := T1); ELSE MX := ''; MN := 0; END_IF; (* revision history hm 8. oct. 2008 rev 1.0 original version hm 27. oct. 2008 rev 1.1 changed _INC to INC1 *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK MESSAGE_8 VAR_INPUT IN1 : BOOL; IN2 : BOOL; IN3 : BOOL; IN4 : BOOL; IN5 : BOOL; IN6 : BOOL; IN7 : BOOL; IN8 : BOOL; END_VAR VAR_INPUT CONSTANT S1 : STRING(STRING_LENGTH); S2 : STRING(STRING_LENGTH); S3 : STRING(STRING_LENGTH); S4 : STRING(STRING_LENGTH); S5 : STRING(STRING_LENGTH); S6 : STRING(STRING_LENGTH); S7 : STRING(STRING_LENGTH); S8 : STRING(STRING_LENGTH); END_VAR VAR_OUTPUT M : STRING(STRING_LENGTH); END_VAR (* version 1.1 29. mar. 2008 programmer hugo tested by tobias this function generates one out of 4 messages specified by S1 .. S8. the selected message will be presented at the output M. In1 has higher priority then In2 which has higher priority then IN3 and in8 has the lowest priority. *) (* @END_DECLARATION := '0' *) (* check if an alarm is present if yes set the output M otherwise clear M *) IF in1 THEN M := S1; ELSIF in2 THEN M := S2; ELSIF in3 THEN M := S3; ELSIF in4 THEN M := S4; ELSIF in5 THEN M := S5; ELSIF in6 THEN M := S6; ELSIF in7 THEN M := S7; ELSIF in8 THEN M := S8; ELSE M := ''; END_IF; (* revision history hm 26.12.2007 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MIRROR : STRING(STRING_LENGTH) VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR pi : POINTER TO ARRAY[1..255] OF BYTE; po : POINTER TO BYTE; lx: INT; i: INT; END_VAR (* version 1.1 29. mar. 2008 programmer hugo tested by tobias this function reverses an input string. *) (* @END_DECLARATION := '0' *) pi := ADR(str); po := ADR(mirror); lx := LEN(str); FOR i := lx TO 1 BY - 1 DO po^ := pi^[i]; po := po + 1; END_FOR; (* close output string *) po^:= 0; (* revision histroy hm 4. feb. 2008 rev 1.0 original release hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MONTH_TO_STRING : STRING(10) VAR_INPUT MTH : INT; LANG : INT; LX : INT; END_VAR VAR ly : INT; END_VAR (* version 1.1 19. oct. 2008 programmer hugo tested by oscat MONTH_TO_STRING converts an Integer 1..12 into a String with the Names of the corresponding Month. the decoding is according to the language setup in global vars *) (* @END_DECLARATION := '0' *) IF LANG = 0 THEN Ly := LANGUAGE.DEFAULT; ELSE ly := MIN(LANG, language.LMAX); END_IF; IF MTH < 1 OR MTH > 12 THEN RETURN; ELSIF LX = 0 THEN MONTH_TO_STRING := language.MONTHS[ly, MTH]; ELSIF Lx = 3 THEN MONTH_TO_STRING := language.MONTHS3[ly, MTH]; END_IF; (* revisison history hm 21. sep. 2008 rev 1.0 original version hm 19. oct. 2008 rev 1.1 changed language setup constants *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION OCT_TO_BYTE : BYTE VAR_INPUT OCT : STRING(10); END_VAR VAR pt : POINTER TO BYTE; i: INT; X: BYTE; stop: INT; END_VAR (* version 1.1 20. sep. 2008 programmer hugo tested by oscat OCT_TO_BYTE converts a octagonal string into a byte. *) (* @END_DECLARATION := '0' *) pt := ADR(oct); stop := LEN(oct); FOR I := 1 TO stop DO (* read the first character and subtract 48 to get value in decimal 0 = 48 *) x := pt^; (* calculate the value of the digit *) IF X > 47 AND x < 56 THEN OCT_TO_BYTE := SHL(OCT_TO_BYTE,3) + X - 48; END_IF; pt := pt + 1; END_FOR; (* revision histroy hm 18. jun. 2008 rev 1.0 original release hm 20. sep. 2008 rev 1.1 changed length of input string from 20 to 10 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION OCT_TO_DWORD : DWORD VAR_INPUT OCT : STRING(20); END_VAR VAR pt : POINTER TO BYTE; i: INT; X: BYTE; stop: INT; END_VAR (* version 1.0 18. jun 2008 programmer hugo tested by oscat OCT_TO_DWORD converts a octagonal string into a dword. *) (* @END_DECLARATION := '0' *) pt := ADR(oct); stop := LEN(oct); FOR I := 1 TO stop DO (* read the first character and subtract 48 to get value in decimal 0 = 48 *) x := pt^; (* calculate the value of the digit *) IF X > 47 AND x < 56 THEN OCT_TO_DWORD := SHL(OCT_TO_DWORD,3) + X - 48; END_IF; pt := pt + 1; END_FOR; (* revision histroy hm 18. jun. 2008 rev 1.0 original release *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION REAL_TO_STRF : STRING(20) VAR_INPUT IN : REAL; N : INT; D : STRING(1); END_VAR VAR O: REAL; i: INT; END_VAR (* version 1.8 2. jan 2012 programmer hugo tested by oscat Real_to_strf converts a Real to a fixed length String. the string will be filles with zeroes to achieve the fixed length N after the decimal separator. the input parameter specifies the decimal separator to be used in the output string. *) (* @END_DECLARATION := '0' *) (* LIMIT N to 0 .. 7 *) N := LIMIT(0,N,7); (* round the input to N digits and convert to string *) O := ABS(in) * EXP10(N); REAL_TO_STRF := DINT_TO_STRING(REAL_TO_DINT(O)); (* add zeroes in front to make sure sting is at least 8 digits long *) FOR i := LEN(REAL_TO_STRF) TO N DO REAL_TO_STRF := CONCAT('0', REAL_TO_STRF); END_FOR; (* add a dot if n > 0 *) IF n > 0 THEN REAL_TO_STRF := INSERT(REAL_TO_STRF, D, LEN(REAL_TO_STRF) - N); END_IF; (* add a minus sign if in is negative *) IF in < 0 THEN REAL_TO_STRF := CONCAT('-', REAL_TO_STRF); END_IF; (* revision history hm 26 jan 2007 rev 1.0 original version hm 20. nov. 2007 rev 1.1 when N=0 ther will be no dot at the end of the string. hm 15. dec. 2007 rev 1.2 changed code for better performance hm 4. mar. 2008 rev 1.3 result is now rounded instead of trunc hm 20. mar. 2008 rev 1.4 changed trunc to real_to_dint because trunc was generating wrong values on wago 842 hm 29. mar. 2008 rev 1.5 changed STRING to STRING(20) hm 4. apr. 2008 rev 1.6 added variable O to avoid an error uner CoDeSys SP PLCWinNT V2.4 hm 27. feb. 2009 rev 1.7 added a missing zero for IN < 1 hm 2. jan 2012 rev 1.8 added input parameter D to specify decimal separator *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION REPLACE_ALL : STRING(STRING_LENGTH) VAR_INPUT str : STRING(STRING_LENGTH); src : STRING(STRING_LENGTH); rep : STRING(STRING_LENGTH); END_VAR VAR pos: INT; lp : INT; lx : INT; END_VAR (* version 1.1 29. mar. 2008 programmer hugo tested by tobias the function replace_all replaces all occurences of src in str and replaces them by rep. *) (* @END_DECLARATION := '0' *) REPLACE_ALL := str; lx := LEN(src); lp := LEN(rep); pos := FINDP(REPLACE_ALL, src, 1); WHILE pos > 0 DO REPLACE_ALL := REPLACE(REPLACE_ALL,rep,lx, pos); pos := FINDP(REPLACE_ALL, src, pos + lp); END_WHILE; (* revision histroy hm 4. feb. 2008 rev 1.0 original release hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION REPLACE_CHARS : STRING(STRING_LENGTH) VAR_INPUT STR : STRING(STRING_LENGTH); SRC : STRING; REP : STRING; END_VAR VAR a, b : INT; c: STRING(1); stp: INT; END_VAR (* version 1.0 14. may. 2008 programmer hugo tested by hugo REPLACE_CHARS erstezt alle character die im string src aufgeführt sind mit dem an der selben stelle im string rep gelisteten character. *) (* @END_DECLARATION := '0' *) REPLACE_CHARS := STR; (* make sure rep and src are of same length and length is > 0 *) a := LEN(src); b := LEN(rep); IF a < b THEN rep := LEFT(rep, a); ELSIF b < a THEN src := LEFT(src, b); END_IF; (* search the string and replace if necessary *) stp := LEN(str); FOR a := 1 TO stp DO c := MID(REPLACE_CHARS, 1, a); b := FIND(src, c); IF b > 0 THEN REPLACE_CHARS := REPLACE(REPLACE_CHARS, MID(rep, 1, b), 1, a); END_IF; END_FOR; (* revision history hm 14. may. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION REPLACE_UML : STRING(STRING_LENGTH) VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR L : INT; pt : POINTER TO BYTE; pto : POINTER TO BYTE; ptm : POINTER TO BYTE; pt1, pt2 : POINTER TO BYTE; su : STRING(2); pos : INT; END_VAR (* version 1.1 29. mar. 2008 programmer kurt tested by hugo REPLACE_UML replaces all occurences of Ä,Ö,Ü and ä,ö,ü,ß with Ae, ae, Oe, oe, Ue, ue and ss. *) (* @END_DECLARATION := '0' *) PT := ADR(str); pto := ADR(REPLACE_UML); ptm := pto + INT_TO_DWORD(string_length); pt1 := ADR(su); pt2 := pt1 + 1; L := LEN(str); WHILE pos < L AND pos < string_length DO IF pt^ < 127 THEN (* no uml character simlply copy the character*) pto^ := pt^; ELSE (* convert the uml character *) su := TO_UML(pt^); (* we must make sure pointer are not out of range *) pto^ := pt1^; IF pto < ptm AND pt2^ > 0 THEN pto := pto + 1; pto^ := pt2^; END_IF; END_IF; (* increment pointers *) pt := pt + 1; pto := pto + 1; pos := pos + 1; END_WHILE; (* properly close the output string *) pto^ := 0; (* revision history hm 29. feb 2008 rev 1.0 original version hm 29. mar. 2008 rev 1.1 changed STRING to STRING(STRING_LENGTH) new code to avoid pointer out of range use new function to_uml *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK TICKER VAR_INPUT N : INT; PT : TIME; END_VAR VAR_IN_OUT Text : STRING(STRING_LENGTH); END_VAR VAR_OUTPUT Display : STRING(STRING_LENGTH); END_VAR VAR delay : TP; step : INT; END_VAR (* version 1.2 29. mar. 2008 programmer hugo tested by tobias Ticker sends a substring of text with length N every TD Milliseconds to generate a ticker. *) (* @END_DECLARATION := '0' *) (* generate next ticker when delay is low *) IF N < LEN(text) THEN IF NOT delay.Q THEN (* increase step for next tick *) step := step + 1; IF step > LEN(text) THEN step := 1; END_IF; (* extract dispay from text *) display := MID(text, N, step); (* set delay timer for next tick *) delay(in := TRUE, PT := PT); ELSE; (* execute delay timer *) delay(in := FALSE); END_IF; ELSE display := text; END_IF; (* revision history hm 4. dec. 2007 rev 1.0 original version hm 15. dec. 2007 rev 1.1 step now starts at 1 instaed of 0 hm 29. mar. 2008 rev 1.2 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TO_LOWER : BYTE VAR_INPUT IN : BYTE; END_VAR (* version 1.2 25. oct 2008 programmer hugo tested by tobias to_lower converts a character from uppercase to lowercase *) (* @END_DECLARATION := '0' *) IF in > 64 AND in < 91 THEN TO_LOWER := in OR 16#20; ELSIF (in > 191 AND in < 223) AND in <> 215 AND setup.EXTENDED_ASCII THEN TO_LOWER := in OR 16#20; ELSE TO_LOWER := in; END_IF; (* revision history hm 6. mar. 2008 rev 1.0 original version hm 19. oct. 2008 rev 1.1 changed setup constants ks 25. oct. 2008 rev 1.2 optimized code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TO_UML : STRING(2) VAR_INPUT IN : BYTE; END_VAR (* version 1.1 17. dec. 2008 programmer hugo tested by tobias to_uml converts a character above 127 to a two digit character below 127. the character Ä is converted to Ae *) (* @END_DECLARATION := '0' *) CASE in OF 196: (* Ä *) TO_UML := 'Ae'; 214: (* Ö *) TO_UML := 'Oe'; 220: (* Ü *) TO_UML := 'Ue'; 223: (* ß *) TO_UML := 'ss'; 228: (* ä *) TO_UML := 'ae'; 246: (* ö *) TO_UML := 'oe'; 252: (* ü *) TO_UML := 'ue'; ELSE TO_UML := CHR_TO_STRING(IN); END_CASE; (* revision history hm 29. mar. 2008 rev 1.0 original version hm 17. dec. 2008 rev 1.1 changes name of function chr to chr_to_string *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TO_UPPER : BYTE VAR_INPUT IN : BYTE; END_VAR (* version 1.3 16. jan. 2009 programmer hugo tested by oscat to_upper converts a character from lowercase to uppercase *) (* @END_DECLARATION := '0' *) IF in > 96 AND in < 123 THEN TO_UPPER := in AND 16#DF; ELSIF in > 223 AND in <> 247 AND in <> 255 AND setup.EXTENDED_ASCII THEN TO_UPPER := in AND 16#DF; ELSE TO_UPPER := in; END_IF; (* revision history hm 6. mar. 2008 rev 1.0 original version hm 19. oct. 2008 rev 1.1 changed setup constants ks 25. oct. 2008 rev 1.2 optimized code hm 16. jan 2009 rev 1.3 corrected an error in module *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TRIM : STRING(STRING_LENGTH) VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR pos: INT; END_VAR (* version 1.2 29. mar. 2008 programmer hugo tested by tobias the function deletes all blanks within a string. *) (* @END_DECLARATION := '0' *) TRIM := str; REPEAT pos := FIND(trim,' '); IF pos > 0 THEN TRIM := REPLACE(TRIM,'',1,pos); END_IF; UNTIL pos = 0 END_REPEAT; (* revision histroy hm 6.10.2006 rev 1.0 original release hm 20. mar. 2008 rev 1.1 avoid to call replace with pos = 0 because some systems will produce an error hm 29. mar. 2008 rev 1.2 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TRIM1 : STRING(STRING_LENGTH) VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR pos: INT; END_VAR (* version 1.2 29. mar. 2008 programmer hugo tested by tobias the function replaces all multiple blanks within a string by only one blank. leading and ending blanks will be deleted *) (* @END_DECLARATION := '0' *) TRIM1 := str; REPEAT pos := FIND(trim1,' '); IF pos > 0 THEN TRIM1 := REPLACE(TRIM1,' ',2,pos); END_IF; UNTIL pos = 0 END_REPEAT; (* beginning and ending blanks need to be stripped off *) IF LEFT(trim1,1) = ' ' THEN trim1 := DELETE(trim1,1,1); END_IF; IF RIGHT(trim1,1) = ' ' THEN trim1 := DELETE(trim1,1,LEN(trim1)); END_IF; (* revision histroy hm 4. feb. 2008 rev 1.0 original release hm 20. mar. 2008 rev 1.1 avoid to call replace with pos = 0 because some systems will produce an error hm 29. mar. 2008 rev 1.2 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TRIME : STRING(string_length) VAR_INPUT str : STRING(STRING_LENGTH); END_VAR (* version 1.0 28. mar. 2008 programmer hugo tested by tobias this function deletes all leading and ending blanks of a string. *) (* @END_DECLARATION := '0' *) TRIME := str; (* beginning blanks need to be stripped off *) WHILE LEFT(TRIME,1) = ' ' DO TRIME := DELETE(TRIME,1,1); END_WHILE; (* ending blanks need to be stripped off *) WHILE RIGHT(TRIME,1) = ' ' DO TRIME := DELETE(TRIME,1,LEN(TRIME)); END_WHILE; (* revision histroy hm 28. mar. 2008 rev 1.0 original release *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION UPPERCASE : STRING(STRING_LENGTH) VAR_INPUT str : STRING(STRING_LENGTH); END_VAR VAR pt : POINTER TO BYTE; pos: INT; l: INT; END_VAR (* version 1.3 29. mar. 2008 programmer hugo tested by tobias uppercase returns str while all letters a..z and ä,ö,ü are converted to uppercase *) (* @END_DECLARATION := '0' *) pt := ADR(UPPERCASE); UPPERCASE := str; l := LEN(str); FOR pos := 1 TO l DO pt^ := TO_UPPER(pt^); pt := pt + 1; END_FOR; (* revision histroy hm 6. oct. 2006 rev 1.0 original release hm 4. feb. 2008 rev 1.1 improved performance added Ä,Ö,Ü hm 6. mar. 2008 rev 1.2 added support for exteded Ascii hm 29. mar. 2008 rev 1.3 changed STRING to STRING(STRING_LENGTH) *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/String' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION WEEKDAY_TO_STRING : STRING(10) VAR_INPUT WDAY : INT; LANG : INT; LX : INT; END_VAR VAR ly : INT; END_VAR (* version 1.1 19. oct. 2008 programmer hugo tested by oscat WEEKDAY_TO_STRING converts an Integer 1..7 into a String with the Names of the corresponding weekday. the decoding is according to the language setup in global vars *) (* @END_DECLARATION := '0' *) IF LANG = 0 THEN ly := LANGUAGE.DEFAULT; ELSE ly := MIN(LANG, LANGUAGE.LMAX); END_IF; IF wday < 1 OR wday > 7 THEN RETURN; ELSIF LX = 0 THEN WEEKDAY_TO_STRING := language.WEEKDAYS[ly, WDAY]; ELSIF Lx = 2 THEN WEEKDAY_TO_STRING := language.WEEKDAYS2[ly, WDAY]; END_IF; (* revisison history hm 21. sep. 2008 rev 1.0 original version hm 19. oct. 2008 rev 1.1 changed language setup constants *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK CALENDAR_CALC VAR_INPUT SPE : BOOL; H : REAL := -0.83333333333; END_VAR VAR_IN_OUT XCAL : CALENDAR; HOLIDAYS : ARRAY[0..29] OF HOLIDAY_DATA; END_VAR VAR last : DT; last_day : DINT; holy : HOLIDAY; sun : SUN_TIME; last_hour: INT; utod: TOD; pos : SUN_POS; plast: DT; END_VAR VAR_TEMP dtemp : DINT; tmp : INT; END_VAR (* version 1.6 6. apr. 2011 programmer hugo tested by oscat calendar_calc liest die weltzeit .UTC aus einer CALENDAR Struktur und berechnet die restlichen Werte der Struktur. calendar_calc stellt sicher das die Werte fortlaufend aktualisiert werden und dabei funktionen nur dann aufgerufen werden wenn dies nötig ist. calendar_calc will calculate sun position data when SPE = TRUE; *) (* @END_DECLARATION := '0' *) IF xcal.UTC <> last THEN (* run once per second *) (* update utc last calculated *) last := XCAL.UTC; utod := DT_TO_TOD(xcal.UTC); (* calculate ltc from utc *) XCAL.LDT := UTC_TO_LTIME(XCAL.UTC, XCAL.DST_EN, XCAL.OFFSET); XCAL.LDATE := DT_TO_DATE(XCAL.LDT); XCAL.LTOD := DT_TO_TOD(XCAL.LDT); dtemp := DAY_OF_DATE(XCAL.LDATE); xcal.night := XCAL.LTOD < XCAL.SUN_RISE OR XCAL.LTOD > XCAL.SUN_SET; (* run once per hour *) tmp := HOUR(xcal.LTOD); IF tmp <> last_hour THEN XCAL.DST_ON := DST(XCAL.UTC) AND xcal.DST_EN; last_hour := tmp; END_IF; (* run once per day *) IF dtemp <> last_day THEN last_day := dtemp; (* a new day has started, recalculate daily events *) XCAL.YEAR := YEAR_OF_DATE(XCAL.LDATE); XCAL.MONTH := MONTH_OF_DATE(XCAL.LDATE); XCAL.DAY := DAY_OF_MONTH(XCAL.LDATE); XCAL.WEEKDAY := DAY_OF_WEEK(XCAL.LDATE); HOLY(date_in := XCAL.LDATE, LANGU := xcal.LANGUAGE, HOLIDAYS := HOLIDAYS); XCAL.HOLIDAY := HOLY.Y; XCAL.HOLY_NAME := HOLY.NAME; sun(latitude := XCAL.LATITUDE, longitude := xcal.LONGITUDE, utc := DT_TO_DATE(xcal.UTC), H := H); XCAL.SUN_RISE := DINT_TO_TOD(TOD_TO_DINT(sun.sun_rise) + XCAL.OFFSET * 60000 + SEL(XCAL.DST_ON,DINT#0,3600000)); XCAL.SUN_SET := DINT_TO_TOD(TOD_TO_DINT(sun.sun_set) + XCAL.OFFSET * 60000 + SEL(XCAL.DST_ON,DINT#0,3600000)); XCAL.SUN_MIDDAY := DINT_TO_TOD(TOD_TO_DINT(sun.MIDDAY) + XCAL.OFFSET * 60000 + SEL(XCAL.DST_ON,DINT#0,3600000)); XCAL.SUN_HEIGTH := sun.sun_declination; XCAL.WORK_WEEK := WORK_WEEK(XCAL.LDATE); END_IF; (* calculate the suns position every 10 seconds when SPE = TRUE *) IF SPE AND xcal.UTC - plast >= t#25s THEN plast := last; pos(latitude := xcal.LATITUDE, longitude := xcal.LONGITUDE, utc := xcal.UTC); xcal.SUN_HOR := pos.B; xcal.SUN_VER := pos.HR; END_IF; END_IF; (* revision history hm 23. oct. 2008 rev 1.0 original version hm 8. feb. 2009 rev 1.1 night was calculated wrong added sun position data hm 10. mar. 2009 rev 1.2 added work_week, sun_midday, sun_heigth sun_position will only be calculated evey 25 seconds dst will only become true when dst_en = true hm 23. jan 2010 rev 1.3 sun_rise, sun_set and sun_midday are now calculated in local time hm 18. jan. 2011 rev 1.4 added input holidays to specify local holidays changed call for function sun_time hm 2. feb. 2011 rev 1.5 added input H to specify twilight hm 6. apr. 2011 rev 1.6 night was calculated wrong *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DATE_ADD : DATE VAR_INPUT IDATE : DATE; D : INT; W : INT; M : INT; Y : INT; END_VAR VAR mo: INT; yr : INT; dm: INT; END_VAR (* version 1.8 22. mar. 2011 programmer hugo tested by oscat date_add adds days, weeks, month or years to a date. negative inputs are allowed for subtraction. *) (* @END_DECLARATION := '0' *) DATE_ADD := UDINT_TO_DATE(DATE_TO_UDINT(IDATE) + INT_TO_UDINT(D + W * 7) * UDINT#86400); yr := Y + YEAR_OF_DATE(DATE_ADD); mo := M + MONTH_OF_DATE(DATE_ADD); dm := DAY_OF_MONTH(DATE_ADD); WHILE mo > 12 DO mo := mo - 12; yr := yr + 1; END_WHILE; WHILE mo < 1 DO mo := mo + 12; yr := yr - 1; END_WHILE; DATE_ADD := SET_DATE(yr, mo, dm); (* revision history hm 27.12.2006 rev 1.0 nrw module hm 12.4.2007 rev 1.1 corrected an error while date would be incorrect when year = 0 hm 1.11.2007 rev 1.2 added int_to_dword stetements to avoid possible overrun with möller ecp4 hm 22. mar. 2008 rev 1.3 fixed some bugs when month was negative hm 7. oct. 2008 rev 1.4 changed function year to year_of_date changed function month to month_of_date hm 29. mar. 2009 rev 1.5 improved performance hm 27. jan. 2011 rev 1.6 faster code hm 2. feb. 2011 rev 1.7 fixed an error, weeks not calculated hm 22. mar. 2011 rev 1.8 fixed an error in formula *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DAY_OF_DATE : DINT VAR_INPUT idate : DATE; END_VAR (* version 1.3 7. apr. 2008 programmer oscat tested BY oscat DAY_OF_DATE returns the days since 1.1.1970 *) (* @END_DECLARATION := '0' *) DAY_OF_DATE := DWORD_TO_DINT(DATE_TO_DWORD(idate) / 86400); (* revision history hm 16.9.2007 rev 1.0 original version hm 1. okt 2007 rev 1.1 added step7 compatibility hm 22. mar. 2008 rev 1.2 changed output from int to Dint because the total date range is 49710 days hm 7. apr. 2008 rev 1.3 deleted unused step7 code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DAY_OF_MONTH : INT VAR_INPUT IDATE : DATE; END_VAR VAR leap: INT; END_VAR (* version 2.1 10. mar. 2009 programmer hugo tested by tobias returns the day OF month for any DATE *) (* @END_DECLARATION := '0' *) (* calculate the day in the year *) DAY_OF_MONTH := DAY_OF_YEAR(idate); (* leap will be set to one for a leap year *) leap := BOOL_TO_INT(LEAP_OF_DATE(idate)); (* if leap year deduct one from the days of the year *) DAY_OF_MONTH := DAY_OF_MONTH - leap; (* search if we are in month december to march ? *) IF DAY_OF_MONTH > setup.MTH_OFS[9] THEN IF DAY_OF_MONTH > setup.MTH_OFS[11] THEN IF DAY_OF_MONTH > setup.mth_ofs[12] THEN DAY_OF_MONTH := DAY_OF_MONTH - setup.MTH_OFS[12]; ELSE DAY_OF_MONTH := DAY_OF_MONTH - setup.MTH_OFS[11]; END_IF; ELSE IF DAY_OF_MONTH > setup.mth_ofs[10] THEN DAY_OF_MONTH := DAY_OF_MONTH - setup.MTH_OFS[10]; ELSE DAY_OF_MONTH := DAY_OF_MONTH - setup.MTH_OFS[9]; END_IF; END_IF; ELSIF DAY_OF_MONTH > setup.MTH_OFS[5] THEN IF DAY_OF_MONTH > setup.MTH_OFS[7] THEN IF DAY_OF_MONTH > setup.mth_ofs[8] THEN DAY_OF_MONTH := DAY_OF_MONTH - setup.MTH_OFS[8]; ELSE DAY_OF_MONTH := DAY_OF_MONTH - setup.MTH_OFS[7]; END_IF; ELSE IF DAY_OF_MONTH > setup.mth_ofs[6] THEN DAY_OF_MONTH := DAY_OF_MONTH - setup.MTH_OFS[6]; ELSE DAY_OF_MONTH := DAY_OF_MONTH - setup.MTH_OFS[5]; END_IF; END_IF; ELSIF DAY_OF_MONTH > setup.MTH_OFS[3] THEN IF DAY_OF_MONTH > setup.MTH_OFS[4] THEN DAY_OF_MONTH := DAY_OF_MONTH - setup.MTH_OFS[4]; ELSE DAY_OF_MONTH := DAY_OF_MONTH - setup.MTH_OFS[3]; END_IF; ELSE (* since now we must be in february or january we need to add leap again *) DAY_OF_MONTH := DAY_OF_MONTH + leap; IF DAY_OF_MONTH > setup.MTH_OFS[2] THEN DAY_OF_MONTH := DAY_OF_MONTH - setup.mth_ofs[2]; END_IF; (* since nothing was true before, day_of_month must already be good *) END_IF; (* Revision history hm 22.1.2007 rev 1.1 deleted unused variable day_in_year and day_in_year_begin hm 1. okt 2007 rev 1.2 changed code to use day_of_year and leap_of_date added compatibility to STEP7 hm 8. oct 2007 rev 1.3 deleted unused variable yr hm 8. jan 2008 rev 1.4 improved performance hm 25. oct. 2008 rev 2.0 new code using setup constants hm 10. mar. 2009 rev 2.1 removed nested comments *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DAY_OF_WEEK : INT VAR_INPUT IDATE : DATE; END_VAR (* version 1.4 7. oct. 2008 programmer hugo tested by tobias calculates the weekday of a week according to ISO8601 monday = 1 ..... sunday = 7 *) (* @END_DECLARATION := '0' *) DAY_OF_WEEK := DWORD_TO_INT((DATE_TO_DWORD(idate) / 86400 + 3) MOD 7) + 1; (* revision history hm 21.8.06 rev 1.1 corrected a miscalculation hm 23.12.2007 rev 1.2 correction for step7 hm 7. apr. 2008 rev 1.3 deleted unused step7 code hm 7. oct. 2008 rev 1.4 changed name of function from weekday to day_of_week *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DAY_OF_YEAR : INT VAR_INPUT IDATE : DATE; END_VAR (* version 1.4 28. jan. 2011 programmer hugo tested by oscat calculates the day of the year *) (* @END_DECLARATION := '0' *) DAY_OF_YEAR := UDINT_TO_INT((DATE_TO_UDINT(idate) / UDINT#86400) MOD UDINT#1461); IF DAY_OF_YEAR > 729 THEN IF DAY_OF_YEAR > 1095 THEN DAY_OF_YEAR := DAY_OF_YEAR - 1095; ELSE DAY_OF_YEAR := DAY_OF_YEAR - 729; END_IF; ELSIF DAY_OF_YEAR > 364 THEN DAY_OF_YEAR := DAY_OF_YEAR - 364; ELSE DAY_OF_YEAR := DAY_OF_YEAR + 1; END_IF; (* DAY_OF_YEAR := DWORD_TO_INT((DATE_TO_DWORD(idate) - DATE_TO_DWORD(YEAR_BEGIN(YEAR_OF_DATE(idate)))) / 86400) + 1; *) (* revivision history hm 4. aug. 2007 rev 1.0 original version hm 1. oct. 2007 rev 1.1 added compatibility to STEP7 hm 4. jan. 2008 rev 1.2 changed code for better performance hm 7. oct. 2008 rev 1.3 changed name of function year to year_of_date hm 28. jan. 2011 rev 1.4 improved code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DAY_TO_TIME : TIME VAR_INPUT IN : REAL; END_VAR (* version 1.1 24. feb. 2009 programmer hugo tested by tobias converts an amount of days in real to time *) (* @END_DECLARATION := '0' *) DAY_TO_TIME := DWORD_TO_TIME(REAL_TO_DWORD(IN * 86400000.0)); (* revision history hm 4. aug. 2006 rev 1.0 original release hm 24. feb. 2009 rev 1.1 renamed input to IN *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DAYS_DELTA : DINT VAR_INPUT date_1 : DATE; date_2 : DATE; END_VAR (* version 1.3 25. jan. 2011 programmer hugo tested by tobias days_delta calculates the days between two dates. the days are calculated date_2 - date_1. *) (* @END_DECLARATION := '0' *) IF DATE_1 > DATE_2 THEN DAYS_DELTA := - DWORD_TO_DINT((DATE_TO_DWORD(date_1) - DATE_TO_DWORD(date_2)) / 86400); ELSE DAYS_DELTA := DWORD_TO_DINT((DATE_TO_DWORD(date_2) - DATE_TO_DWORD(date_1)) / 86400); END_IF; (* revision history hm 27. dec 2006 rev 1.0 original version hm 16.9.2007 rev 1.1 coorected an error in formula and changed algorithm to show positive and negative delta hm 22. mar. 2008 rev 1.2 changed output from int to dint because the total date range is 49710 days hm 25. jan. 2011 rev 1.3 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DAYS_IN_MONTH : INT VAR_INPUT IDATE : DATE; END_VAR (* version 1.0 27. mar. 2009 programmer hugo tested by oscat returs the total days of the current month. e.g. 31 for january. the function works for dates from 1970 - 2099 *) (* @END_DECLARATION := '0' *) DAYS_IN_MONTH := DAY_OF_YEAR(IDATE); IF LEAP_OF_DATE(IDATE) THEN CASE DAYS_IN_MONTH OF 32..60 : DAYS_IN_MONTH := 29; 92..121 : DAYS_IN_MONTH := 30; 153..182: DAYS_IN_MONTH := 30; 245..274: DAYS_IN_MONTH := 30; 306..335: DAYS_IN_MONTH := 30; ELSE DAYS_IN_MONTH := 31; END_CASE; ELSE CASE DAYS_IN_MONTH OF 32..59 : DAYS_IN_MONTH := 28; 91..120 : DAYS_IN_MONTH := 30; 152..181: DAYS_IN_MONTH := 30; 244..273: DAYS_IN_MONTH := 30; 305..334: DAYS_IN_MONTH := 30; ELSE DAYS_IN_MONTH := 31; END_CASE; END_IF; (* revision history hm 27. mar. 2009 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DAYS_IN_YEAR : INT VAR_INPUT IDATE : DATE; END_VAR (* version 1.0 27. mar. 2009 programmer hugo tested by oscat returs the total days of the year. the function retruns 366 for leap years and 365 otherwise. the function works for dates from 1970 - 2099 *) (* @END_DECLARATION := '0' *) IF LEAP_OF_DATE(IDATE) THEN DAYS_IN_YEAR := 366; ELSE DAYS_IN_YEAR := 365; END_IF; (* revision history hm 27. mar. 2009 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK DCF77 VAR_INPUT REC : BOOL; SET : BOOL; SDT : DT; DSI : BOOL; END_VAR VAR_INPUT CONSTANT SYNC_TIMEOUT : TIME := t#2m; TIME_OFFSET : INT := 1; DST_EN : BOOL := TRUE; END_VAR VAR_OUTPUT TP : BOOL; DS : BOOL; WDAY : INT; ERROR : BOOL := TRUE; RTC : DT; RTC1 : DT; MSEC : INT; SYNC : BOOL; END_VAR VAR mez : DT; utc : DT; state : INT; edge: BOOL; tx : TIME; ty: TIME; last : TIME; bits : ARRAY[0..58] OF BOOL; cnt : INT; i : INT; old_time : DT; minute : INT; hour : INT; day : INT; month : INT; year : INT; last_sync: TIME; t1: TIME; tz: TIME; init: BOOL; END_VAR (* version 1.10 7. oct. 2008 programmer hugo tested by oscat this is a decoder for a DCF77 signal. the decoder decodes the DCF77 signal and checks for a valid Date_Time. since the dcf77 signal is only secured with a simple parity the decoder waits for two consecutive valid transmissions before it sets the output signals. given a valid reception by the receiver the decoder can take up to 3 minutes to start its clocks the first time. after a valid signal is detected the error output goes low and dignals a valid dcf signal from the receiver. a tp is only valid for one program cycle to allow for external rtc setting. after a valid signal is detected two independent internal clocks are started (RTC and RTC1), the seconds of these clocks are generated by software since the dcf signal is only valid for minutes. rtc is always utc ( world time) and rtc1 can be set to any time zone by setting the time_zone_offset and enabling dst for automatic summertime. a sync output signals the the rtc and rtc1 are in sync with the dcf77 signal, if the dcf77 signal is lost for more then the sync time the sync output goes low but the rtc and rtc1 outputs keep running based on software timing until a valid dcf77 signal is received again and triggers the clock. in addition a millisecond output gives further resolutionof the clocks. a dst output shows if daylight saving time is enables and the weekday output shows which day of week currently is ( 1= monday ... 7= sunday). *) (* @END_DECLARATION := '0' *) (* if tp was set it should only be active for 1 cycle *) TP := FALSE; (* read system time *) t1 := DWORD_TO_TIME(T_PLC_MS()); tx := t1 - last; IF rec XOR edge THEN edge := rec; IF NOT rec AND tx > t#1700ms AND tx < t#2000ms THEN (* start condition reached *) state := 0; tp := NOT error; ELSIF NOT rec AND tx > t#700ms AND tx < t#1000ms THEN (* second switch detected *) IF state < 58 THEN state := state +1; ELSE state := 0; END_IF; ELSIF rec AND tx < t#120ms THEN (* bit 0 detected *) bits[state] := 0; ELSIF rec AND tx > t#120ms AND tx < t#250ms THEN (* bit 1 detected *) bits[state] := 1; ELSE (* error condition received signal is not valid *) error := TRUE; state := 0; END_IF; last := last + tx; IF rec AND state = 58 THEN error := FALSE; (* decode the bits and check for possible errors *) IF bits[0] OR NOT (bits[17] XOR bits[18]) OR NOT bits[20] THEN error := TRUE; END_IF; (* decode minute *) MINUTE := 0; MINUTE.0 := bits[21]; MINUTE.1 := bits[22]; MINUTE.2 := bits[23]; MINUTE.3 := bits[24]; IF bits[25] THEN MINUTE := MINUTE + 10; END_IF; IF bits[26] THEN MINUTE := MINUTE + 20; END_IF; IF bits[27] THEN MINUTE := MINUTE + 40; END_IF; IF MINUTE > 59 OR (bits[21] XOR bits[22] XOR bits[23] XOR bits[24] XOR bits[25] XOR bits[26] XOR bits[27] XOR bits[28]) THEN error := TRUE; END_IF; (* decode hour *) HOUR := 0; HOUR.0 := bits[29]; HOUR.1 := bits[30]; HOUR.2 := bits[31]; HOUR.3 := bits[32]; IF bits[33] THEN HOUR := HOUR + 10; END_IF; IF bits[34] THEN HOUR := HOUR +20; END_IF; IF HOUR > 23 OR (bits[29] XOR bits[30] XOR bits[31] XOR bits[32] XOR bits[33] XOR bits[34] XOR bits[35]) THEN error := TRUE; END_IF; (* decode day of month *) day := 0; day.0 := bits[36]; day.1 := bits[37]; day.2 := bits[38]; day.3 := bits[39]; IF bits[40] THEN day := day + 10; END_IF; IF bits[41] THEN day := day + 20; END_IF; IF day > 31 THEN error := TRUE; END_IF; (* decode day of week *) wday := 0; wday.0 := bits[42]; wday.1 := bits[43]; wday.2 := bits[44]; IF wday > 7 OR wday < 1 THEN error := TRUE; END_IF; (* decode month *) MONTH := 0; MONTH.0 := bits[45]; MONTH.1 := bits[46]; MONTH.2 := bits[47]; MONTH.3 := bits[48]; IF bits[49] THEN MONTH := MONTH +10; END_IF; IF MONTH > 12 THEN error := TRUE; END_IF; (* decode year *) YEAR := 0; YEAR.0 := bits[50]; YEAR.1 := bits[51]; YEAR.2 := bits[52]; YEAR.3 := bits[53]; IF bits[54] THEN YEAR := YEAR + 10; END_IF; IF bits[55] THEN YEAR := YEAR + 20; END_IF; IF bits[56] THEN YEAR := YEAR + 40; END_IF; IF bits[57] THEN YEAR := YEAR + 80; END_IF; (* check parity for bits 36 to 58 *) cnt := 0; FOR i := 36 TO 58 DO IF bits[i] THEN cnt := cnt + 1; END_IF; END_FOR; IF NOT EVEN(cnt) THEN error := TRUE; END_IF; (* time must be valid for two cycles to clear error flag *) IF NOT error THEN (* set outputs *) old_time := mez; IF YEAR >= 70 THEN YEAR := YEAR + 1900; ELSE YEAR := YEAR + 2000; END_IF; mez := SET_DT(YEAR,MONTH,day,HOUR,MINUTE,0); DS := bits[17]; IF DS THEN UTC := DWORD_TO_DT(DT_TO_DWORD(mez) - 7200); ELSE UTC := DWORD_TO_DT(DT_TO_DWORD(mez) - 3600); END_IF; (* set trigger signal only if the receiver has received 2 successive minutes *) IF mez <> old_time + t#1m THEN error := TRUE ; END_IF; END_IF; END_IF; END_IF; (* this portion implements a free running clock which is triggered by the dcf77 signal *) tz := DWORD_TO_TIME(INT_TO_DWORD(ABS(time_offset))* 3600000); (* input sdt is copied to utc at first power up *) IF NOT init OR SET THEN init := TRUE; utc := sdt; tp := TRUE; DS := DSI; END_IF; IF tp THEN rtc := utc; IF DS AND dst_en THEN IF time_offset < 0 THEN rtc1 := rtc - tz + t#1h; ELSE rtc1 := rtc + tz + t#1h; END_IF; ELSE IF time_offset < 0 THEN rtc1 := rtc - tz; ELSE rtc1 := rtc + tz; END_IF; END_IF; sync := TRUE; last_sync := last; ty := last; ELSIF rtc > DWORD_TO_DT(0) AND T1 - ty >= t#1s THEN rtc := rtc + t#1s; rtc1 := rtc1 + t#1s; ty := ty + t#1s; sync := ty - last_sync < sync_timeout AND last_sync > DWORD_TO_TIME(0); wday := DAY_OF_WEEK(DT_TO_DATE(rtc1)); DS := dst_en AND DST(utc); END_IF; msec := TIME_TO_INT(t1 - ty); (* decode information bits content 0 Bitwert immer 0 1 bis 14 reserviert für Betriebsinformationen (nicht für DCF77-Nutzer bestimmt) 15 Rufbit für Alarmierung der PTB-Mitarbeiter (bis Mai 2003: Bitwert = 0 falls normale Antenne in Verwendung; 1 = Backupantenne) 16 Bitwert = 1 falls ein Wechsel von MEZ nach MESZ oder umgekehrt bevorsteht; Dauer der Anzeige: 1 Stunde 17 + 18 gültige Zeit = MEZ, falls Bit 17=0 und Bit 18=1 gültige Zeit = MESZ, falls Bit 17=1 und Bit 18=0 19 Bitwert = 1 falls innerhalb den nächsten 59 Minuten eine Schaltsekunde angeordnet ist. Beim Einfügen einer Schaltsekunde wird anstelle der 59. die 60. Sekundenmarke weggelassen und in der 58. erfolgt ausnahmsweise ein Trägerabfall. 20 Startbit für Zeitinformation (immer 1) 21 - 27 1, 2, 4, 8, 10, 20, 40 Minuten (bitweise Addition) 28 Prüfbit (gerade Parität) für die Bits 21-27 29 - 34 1, 2, 4, 8, 10, 20 Stunden (bitweise Addition) 35 Prüfbit (gerade Parität) für die Bits 29-34 36 - 41 Tagesnummer im aktuellen Monat: 1, 2, 4, 8, 10, 20 (bitweise Addition) 42 - 44 Tagesnummer in der aktuellen Woche: 1, 2, 4 (bitweise Addition) 45 - 49 Monatsnummer: 1, 2, 4, 8, 10 (bitweise Addition) 50 - 57 Jahr (zweistellig): 1, 2, 4, 8, 10, 20, 40, 80 (bitweise Addition) 58 Prüfbit (gerade Parität) füR die Bits 36-57 *) (* revision history hm 2.feb 2007 rev 1.1 change wday and dst outputs when there is no dcf reception hm 26.feb 2007 rev 1.2 changed statements where t#1h would be substracted from DT. under certain conditions the compiler would crash translating this statement hm 17. sep 2007 rev 1.3 replaced time() with T_PLC_MS() for compatibility reasons hm 24. oct 2007 rev 1.4 changed dst calculation because function dst was upgraded no error in DCF77 only a change in DST hm 12. nov 2007 rev 1.5 changed time_offset from time to integer to allow for negative offset time zones hm 8. dec 2007 rev 1.6 corrected an error in time_zone calculation hm 23. jan 2008 rev 1.7 added sdt input which is used to initialize rtc and rtc1 during first cycle. hm 16. mar 2008 rev 1.8 changed output weekday to wday and dst to ds for compatibility reasons hm 19. apr. 2008 rev 1.9 added input dsi to allow to set daylight savings time when SDT is TRUE. added asynchronous SET input hm 7. oct. 2008 rev 1.10 changed function weekday to day_of_week *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DST : BOOL VAR_INPUT UTC : DT; END_VAR VAR yr : INT; yr4 : DWORD; ltc: DWORD; idate : DWORD; END_VAR (* version 1.5 24. jan. 2011 programmer hugo tested by oscat this functions returns TRUE IF dst TIME is active the FUNCTION calculates automatically for any year betweek 1970 AND 2099 wheather daylight savings is on OR off the summertime calculation is done according to european standards. dst will become TRUE AT 01:00 utc in the morning FOR the respective days AND it will become FALSE after daylight savings TIME is switched back end OF october at 01:00 utc *) (* @END_DECLARATION := '0' *) yr := YEAR_OF_DATE(DT_TO_DATE(UTC)); ltc := DT_TO_DWORD(UTC); idate := DT_TO_DWORD(SET_DT(yr, 3, 31, 1, 0, 0)); yr4 := SHR(5 * INT_TO_DWORD(yr), 2) + 1; DST := (idate - ((yr4 + 3) MOD 7) * 86400 <= ltc) AND (idate + (214 - (yr4) MOD 7) * 86400 > ltc); (* Equation used TO calculate the beginning OF European Summer TIME: Sunday (31 - (5*y/4 + 4) mod 7) March at 01.00 UTC (valid through 2099, courtesy of Robert H. van Gent, EC). European Summer Time ends (clocks go back) at 01.00 UTC on * 29 October 2006 * 28 October 2007 * 26 October 2008 Equation used to calculate the end of European Summer Time: Sunday (31 - (5*y/4 + 1) mod 7) October at 01.00 UTC (validity AND credits as above). *) (* revision history hm 4. aug 2006 rev 1.0 original version hm 24. okt 2007 rev 1.1 deleted time_zone_offset input because dst is generally at 01:00 utc and not mesz uk starts 01:00 utc and also greece hm 1. dec 2007 rev 1.2 changed code to improve performance hm 16. mar. 2008 rev 1.3 added type conversion to avoid warnings under codesys 3.0 code improvement for better performance hm 7. oct. 2008 rev 1.4 changed name of function year to year_of_date hm 24. jan. 2011 rev 1.5 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DT2_TO_SDT : SDT VAR_INPUT DI : DATE; TI : TOD; END_VAR (* version 1.0 18. oct 2008 programmer hugo tested by oscat converts date and time of day to Structured date time (SDT) *) (* @END_DECLARATION := '0' *) DT2_TO_SDT.YEAR := YEAR_OF_DATE(di); DT2_TO_SDT.MONTH := MONTH_OF_DATE(di); DT2_TO_SDT.DAY := DAY_OF_MONTH(di); DT2_TO_SDT.WEEKDAY := DAY_OF_WEEK(di); DT2_TO_SDT.MS := DWORD_TO_INT(TOD_TO_DWORD(ti) MOD 1000); DT2_TO_SDT.SECOND := DWORD_TO_INT((TOD_TO_DWORD(ti) / 1000) MOD 60); DT2_TO_SDT.MINUTE := DWORD_TO_INT((TOD_TO_DWORD(ti) / 60000) MOD 60); DT2_TO_SDT.HOUR := DWORD_TO_INT(TOD_TO_DWORD(ti) / 3600000); (* revision history hm 18. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION DT_TO_SDT : SDT VAR_INPUT DTI : DT; END_VAR VAR tmp : DATE; tdt : DWORD; END_VAR (* version 1.0 18. oct 2008 programmer hugo tested by oscat converts date_time to Structured date time (SDT) *) (* @END_DECLARATION := '0' *) tmp := DT_TO_DATE(dti); tdt := DT_TO_DWORD(dti) - DATE_TO_DWORD(tmp); DT_TO_SDT.YEAR := YEAR_OF_DATE(tmp); DT_TO_SDT.MONTH := MONTH_OF_DATE(tmp); DT_TO_SDT.DAY := DAY_OF_MONTH(tmp); DT_TO_SDT.WEEKDAY := DAY_OF_WEEK(tmp); DT_TO_SDT.SECOND := DWORD_TO_INT(tdt MOD 60); DT_TO_SDT.MINUTE := DWORD_TO_INT((tdt / 60) MOD 60); DT_TO_SDT.HOUR := DWORD_TO_INT(tdt / 3600); (* revision history hm 18. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION EASTER : DATE VAR_INPUT year : INT; END_VAR VAR b,c: INT; oday: INT; END_VAR (* version 1.3 7. apr. 2008 programmer hugo tested by tobias easter calculates the day of easter sunday for a given year. most other catholic holidays are calculated in reference to easter sunday. *) (* @END_DECLARATION := '0' *) b := (204 - 11 * (YEAR MOD 19)) MOD 30; IF b > 27 THEN b := b - 1; END_IF; c := (year + SHR(year,2) + b - 13) MOD 7; oday := 28 + b - c; IF oday > 33 THEN EASTER := SET_DATE(year, 4, oday - 31); ELSE EASTER := SET_DATE(year, 3, oday); END_IF; (* alternativer algorithmus ueber das pasah fest execution time roughly 200us Der sog. Passah-Vollmond wird berechnet, in dem das Jahr durch 19 ge- teilt wird und der Rest mit der folgenden Tabelle verglichen wird: 0: Apr 14 5: Apr 18 10: Mrz 25 15: Mrz 30 1: Apr 03 6: Apr 08 11: Apr 13 16: Apr 17 2: Mrz 23 7: Mrz 28 12: Apr 02 17: Apr 07 3: Apr 11 8: Apr 16 13: Mrz 22 18: Mrz 27 4: Mrz 31 9: Apr 05 14: Apr 10 Faellt dieses Datum auf einen Sonntag, ist Ostern der naechste Sonntag! Beispiel: 1992 MOD 19 = 16, daraus folgt 17.04., der naechste Sonntag ist dann der 19. April (Ostersonntag) *) (* this algorithm is 180 us a := year MOD 19; b := year / 100; c := year MOD 100; d := b / 4; e := b MOD 4; f := (b + 8) / 25; g := (b - f + 1) / 3; h := (19 * a + b - d -g + 15) MOD 30; i := C / 4; k := c MOD 4; l := (32 + 2*e + 2*i - h - k) MOD 7; m := (a + 11 * h + 22 * l) / 451; x := h + l - 7 * m + 114; n := X / 31; o := x MOD 31 + 1; easter := set_Date(year,n,o); *) (* Im Magazin "Nature" vom 20. April 1876 veröffentlichte ein anonymer Autor eine Tabelle mit Regeln zur Berechnung des (Gregorianischen) Ostersonntages des Jahres J. In Formeln ausgedrückt erhält man das Folgende: a = J mod 19 b = int(J / 100) c = J mod 100 d = int(b / 4) e = b mod 4 f = int((b + 8) / 25) g = int((b - f + 1) / 3 h = (19 · a + b - d - g + 15) mod 30 i = int(c / 4) k = c mod 4 l = (32 + 2 · e + 2 · i - h - k) mod 7 m = int((a + 11 · h + 22 · l) / 451) n = int((h + l - 7 · m + 114) / 31) o = (h + l - 7 · m + 114) mod 31 n ist hierbei die Nummer des Monats, o + 1 die Nummer des Tages auf welchen der Ostersonntag im Jahr J fällt. Dieser Algorithmus kommt ohne Hilfszahlen aus. *) (* revision history hm 27. dec 2006 rev 1.0 original version hm 15. dec 2007 rev 1.1 modified code for better performance hm 3. feb 2008 rev 1.2 modified code for better performance hm 7. apr. 2008 rev 1.3 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK EVENTS VAR_INPUT DATE_IN : DATE; ENA : BOOL; END_VAR VAR_OUTPUT Y : BOOL; NAME : STRING(30); END_VAR VAR i : INT; last_active : DATE; size :INT := 49; day_in: DINT; cyr: INT; lday: DINT; check : HOLIDAY_DATA; y_int : BOOL; name_int : STRING(30); END_VAR VAR_IN_OUT ELIST : ARRAY[0..49] OF HOLIDAY_DATA; END_VAR (* version 1.0 18. jan. 2010 programmer hugo tested by tobias event checks an array with a list of events and displays the event if today is one. *) (* @END_DECLARATION := '0' *) (* for performance reasons only activate once a day *) IF last_active <> date_in THEN last_active := DATE_IN; Y_int := FALSE; name_int := ''; day_in := DAY_OF_DATE(DATE_IN); cyr := YEAR_OF_DATE(DATE_IN); (* search list for events *) FOR i := 0 TO size DO check := elist[i]; lday := DAY_OF_DATE(SET_DATE(cyr,check.month, check.day)); IF day_in >= lday AND day_in <= lday + check.use - 1 THEN y_int := TRUE; name_int := check.name; EXIT; END_IF; END_FOR; END_IF; IF ENA THEN Y := y_int; NAME := name_int; ELSE Y := FALSE; NAME := ''; END_IF; (* revision history hm 18. jan. 2011 rev 1.0 new module *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK HOLIDAY VAR_INPUT DATE_IN : DATE; LANGU : INT; FRIDAY : BOOL; SATURDAY : BOOL; SUNDAY : BOOL; END_VAR VAR_IN_OUT HOLIDAYS : ARRAY[0..29] OF HOLIDAY_DATA; END_VAR VAR CONSTANT SIZE : INT := 29; END_VAR VAR_OUTPUT Y : BOOL; NAME : STRING(30); END_VAR VAR last_active : DATE; ostern: DATE; i: INT; jahr: INT; x_date: DATE; lx: INT; wdx: INT; END_VAR (* version 2.0 18. jans 2011 programmer hugo tested by tobias holiday calculates if a given day is a holiday and displays the name of the holiday as string as well as a boolean flag to indicate a holiday. the holidays are specified in the country setup under global constants. a holiday can be of fixed date for example new years day on january 1st. a holiday can have a fixed offset from easter sunday as for most church holidays. a holiday can be a specific weekday before a fixed date, for example buss und bettag is the last wednesday before nov 23rd. with a simple f_use flag any specific holiday can be turned on or off if needed. please check the manual for examples of holiday definitions *) (* @END_DECLARATION := '0' *) (* for performance reasons only activate once a day *) IF last_active = date_in THEN RETURN; END_IF; last_active := DATE_IN; (* determine language *) IF LANGU = 0 THEN lx := language.DEFAULT; ELSE lx := MIN(language.LMAX, LANGU); END_IF; (* berechnung von ostern für das aktuelle jahr *) jahr := YEAR_OF_DATE(date_in); ostern := EASTER(jahr); wdx := DAY_OF_WEEK(DATE_IN); Y := FALSE; (* check for holidays *) FOR i := 0 TO size DO x_date := SET_DATE(jahr, HOLIDAYS[i].MONTH , HOLIDAYS[i].DAY); IF HOLIDAYS[i].USE = 1 AND HOLIDAYS[i].MONTH > 0 THEN (* check for fixed date holiday *) IF x_date = date_in THEN Y := TRUE; NAME := HOLIDAYS[i].NAME; RETURN; END_IF; ELSIF HOLIDAYS[i].USE = 1 AND HOLIDAYS[i].MONTH = 0 THEN (* check for holiday in reference to easter *) IF DATE_ADD(ostern, HOLIDAYS[i].DAY ,0,0,0) = date_in THEN Y := TRUE; NAME := HOLIDAYS[i].NAME; RETURN; END_IF; ELSIF HOLIDAYS[i].USE < 0 THEN (* check for holiday on a weekday before date *) IF DAY_OF_WEEK(date_in) = ABS(HOLIDAYS[i].USE) AND date_in < x_date AND date_in >= DATE_ADD(x_date,-7,0,0,0) THEN Y := TRUE; NAME := HOLIDAYS[i].NAME; RETURN; END_IF; END_IF; END_FOR; (* check array if today is weekend *) IF NOT Y AND (wdx = 5 AND FRIDAY OR wdx = 6 AND SATURDAY OR wdx = 7 AND SUNDAY) THEN Y := TRUE; NAME := LANGUAGE.WEEKDAYS[LOCATION.LANGUAGE[lx],wdx]; ELSE NAME := ''; END_IF; (* Neujahrstag 1. Januar • • • • • • • • • • • • • • • • Heilige Drei Könige 6. Januar • • • Karfreitag Ostersonntag - 2d • • • • • • • • • • • • • • • • Ostersonntag siehe Osterdatum (•) Ostermontag Ostersonntag + 1d • • • • • • • • • • • • • • • • Tag der Arbeit 1. Mai • • • • • • • • • • • • • • • • Christi Himmelfahrt Ostersonntag + 39d • • • • • • • • • • • • • • • • Pfingstsonntag Ostersonntag + 49d (•) Pfingstmontag Ostersonntag + 50d • • • • • • • • • • • • • • • • Fronleichnam Ostersonntag + 60d • • • • • • 1) 2) Augsburger Friedensfest 8. August (3) Mariä Himmelfahrt 15. August (5) • Tag der Deutschen Einheit 3. Oktober 6) • • • • • • • • • • • • • • • • Reformationstag 31. Oktober • • • • • Allerheiligen 1. November • • • • • Buß- und Bettag 4) Mittwoch vor dem 23.11. 7 • 1. Weihnachtstag 25. Dezember • • • • • • • • • • • • • • • • 2. Weihnachtstag 26. Dezember • • • • • • • • • • • • • • • • *) (* revision history hm 27. feb. 2007 rev 1.1 deleted unused variable init hm 31. oct. 2007 rev 1.2 changed holiday definition from constant to input constant to allow easier changes by user without recompilation of the lib hm 24. nov. 2007 rev 1.3 changes F_use of Buß_und_Bettag to 0 because this is no official holiday hm 7. apr. 2008 rev 1.4 improved performance hm 7. oct. 2008 rev 1.5 changed code to use setup data from global constants changed length of output NAME from 20 to 30 holiday will now also be indicated on a weekend changed function year to year_of_date changed function weekday to day_of_week hm 21. oct. 2008 rev 1.6 using location constants hm 18. jan 2011 rev 2.0 using user specified array for holidays *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION HOUR : INT VAR_INPUT itod : TOD; END_VAR (* version 1.1 2 okt 2006 programmer hugo tested by tobias extracts the hour of a Time_of_day *) (* @END_DECLARATION := '0' *) HOUR := DWORD_TO_INT(TOD_TO_DWORD(itod) / 3600000); (* change history hm 4. aug 2006 rev 1.0 original version hm 2.10.2006 rev 1.1 changed name of input to itod *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION HOUR_OF_DT : INT VAR_INPUT XDT : DT; END_VAR (* version 1.0 6. jun. 2008 programmer oscat tested BY oscat HOUR_OF_DT returns the current hour (hour of the day) of a DT variable *) (* @END_DECLARATION := '0' *) HOUR_OF_DT := DWORD_TO_INT((DT_TO_DWORD(XDT) MOD 86400) / 3600); (* revision history hm 6.9.2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION HOUR_TO_TIME : TIME VAR_INPUT IN : REAL; END_VAR (* version 1.2 24. feb. 2009 programmer hugo tested by tobias converts an amount of hours in real to time *) (* @END_DECLARATION := '0' *) HOUR_TO_TIME := DWORD_TO_TIME(REAL_TO_DWORD(IN * 3600000)); (* revision history hm 4. aug 2006 rev 1.0 original version hm 14. mar. 2008 rev 1.1 rounded the input after the last digit hm 24. feb. 2009 rev 1.2 changed input to IN *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION HOUR_TO_TOD : TOD VAR_INPUT IN : REAL; END_VAR (* version 1.2 24. feb 2009 programmer hugo tested by tobias converts an amount of hours in real to time of day TOD. *) (* @END_DECLARATION := '0' *) HOUR_TO_TOD := DWORD_TO_TOD(REAL_TO_DWORD(IN * 3600000)); (* revision history hm 4. aug. 2006 rev 1.0 original version hm 14. mar. 2008 rev 1.1 rounded the input after the last digit hm 24. feb. 2009 rev 1.2 changed input to IN *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION JD2000 : REAL VAR_INPUT DTI : DT; END_VAR (* version 1.0 15. jul. 2008 programmer hugo tested by oscat JULIAN calculates the astronomic julian date from 1.1.2000-12:00. *) (* @END_DECLARATION := '0' *) JD2000 := DWORD_TO_REAL(DT_TO_DWORD(DTI) - 946728000) / 86400.0; (* revision histroy hm 15. jul. 2008 rev 1.0 original release *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LEAP_DAY : BOOL VAR_INPUT IDATE : DATE; END_VAR VAR END_VAR (* version 1.2 24. jan. 2011 programmer hugo tested by oscat leap_day is true if the tested day is a leap day (29. of february). *) (* @END_DECLARATION := '0' *) LEAP_DAY := DATE_TO_UDINT(IDATE) MOD 126230400 = 68169600; (* change history hm 15. jun. 2008 rev 1.0 original version hm 7. oct. 2008 rev 1.1 changed function month to month_of_date hm 24. jan. 2011 rev 1.2 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LEAP_OF_DATE : BOOL VAR_INPUT idate : DATE; END_VAR (* version 1.3 28. jan. 2011 programmer hugo tested by tobias leap_of_date is true if current year is a leap year *) (* @END_DECLARATION := '0' *) LEAP_OF_DATE := SHL(((DATE_TO_DWORD(idate) + 43200) / 31557600), 30) = 16#80000000; (* change history 2.10.2006 rev 1.1 the function now calls leap_year to accomodate further accuracy. the function now works for any year from 1970 to 2100 8. jan 2008 rev 1.2 improved code for better performance 28. jan. 2011 rev 1.3 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LEAP_YEAR : BOOL VAR_INPUT yr : INT; END_VAR (* version 1.1 2. oct. 2006 programmer hugo tested by tobias leap_year is true if the tested year is a leap year *) (* @END_DECLARATION := '0' *) LEAP_YEAR := SHL(yr,14) = 0; (* this code was used prior to rev 1.1 IF yr MOD 400 = 0 THEN leap_year := TRUE; ELSIF yr MOD 100 = 0 THEN leap_year := FALSE; ELSIF yr MOD 4 =0 THEN leap_year := TRUE; ELSE leap_year := FALSE; END_IF; *) (* change history hm 2.10.2006 rev 1.1 the function now works for any year from 1970 up to 2100 hm 1. oct 2007 rev 1.2 chaged code for higher performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION LTIME_TO_UTC : DT VAR_INPUT LTIME : DT; DST : BOOL; TIME_ZONE_OFFSET : INT; END_VAR (* version 1.7 13. nov. 2009 programmer hugo tested by oscat LTIME_TO_UTC calculates UTC (World Time) from a local time LTIME. utc is calculated by subtracting Time_Zone_Offset from ltime and if dst it true subtracting an additional hour from ltime. *) (* @END_DECLARATION := '0' *) LTIME_TO_UTC := UDINT_TO_DT(DT_TO_UDINT(LTIME) - INT_TO_UDINT(TIME_ZONE_OFFSET) * 60); IF DST THEN LTIME_TO_UTC := LTIME_TO_UTC - T#1h; END_IF; (* revision history hm 5.7.2007 rev 1.0 original version hm 5.11.2007 rev 1.1 replaced literal constant with variable because of error in möller ecp4 compiler hm 12.nov 2007 rev 1.2 changed Type of time_zone_offset from time to int to allow for time zones with negative offset hm 8. dec 2007 rev 1.3 corrected a problem with time_zone_offset hm 14. oct. 2008 rev 1.4 changed time_zone_offset from int to real to allow for half hour offset hm 20. oct. 2006 rev 1.5 changed time_zone_offset from Real to INT, now in Minutes hm 27. feb. 2009 rev 1.6 added type conversions to avoid warnings under codesys 3.0 ks 13. nov. 2009 rev 1.7 corrected error in formula *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MINUTE : INT VAR_INPUT itod : TOD; END_VAR (* version 1.1 2 oct 2006 programmer hugo tested by tobias extracts the minutes out of TOD truncating the seconds *) (* @END_DECLARATION := '0' *) MINUTE := DWORD_TO_INT(TOD_TO_DWORD(itod) / 60000 - TOD_TO_DWORD(itod) / 3600000 * 60); (* change history 2.10.2006 changes name of input to itod *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MINUTE_OF_DT : INT VAR_INPUT XDT : DT; END_VAR (* version 1.0 6. jun. 2008 programmer oscat tested BY oscat MINUTE_OF_DT returns the current minute (minute of the hour) of a DT variable *) (* @END_DECLARATION := '0' *) MINUTE_OF_DT := DWORD_TO_INT(DT_TO_DWORD(XDT) MOD 3600) / 60; (* revision history hm 6.9.2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MINUTE_TO_TIME : TIME VAR_INPUT IN : REAL; END_VAR (* version 1.2 24. feb. 2009 programmer hugo tested by oscat converts an amount of minutes in real to time *) (* @END_DECLARATION := '0' *) MINUTE_TO_TIME := DWORD_TO_TIME(REAL_TO_DWORD(IN * 60000.0)); (* revision history hm 4. aug 2006 rev 1.0 original version hm 14. mar 2008 rev 1.1 rounded the input after the last digit hm 24. feb. 2009 rev 1.2 changed input to IN *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MONTH_BEGIN : DATE VAR_INPUT idate : DATE; END_VAR (* version 1.0 15. jun. 2008 programmer hugo tested by oscat returns the date for the first day of the current month in the current year. *) (* @END_DECLARATION := '0' *) MONTH_BEGIN := DWORD_TO_DATE(DATE_TO_DWORD(idate) - INT_TO_DWORD(DAY_OF_MONTH(idate) - 1) * 86400); (* revision history hm 15. jun. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MONTH_END : DATE VAR_INPUT IDATE : DATE; END_VAR (* version 1.1 7. oct. 2008 programmer hugo tested by oscat returns the date for the last day of the current month in the current year. *) (* @END_DECLARATION := '0' *) MONTH_END := DWORD_TO_DATE(DATE_TO_DWORD(SET_DATE(YEAR_OF_DATE(idate),MONTH_OF_DATE(idate)+1,1)) - 86400); (* revision history hm 15. jun. 2008 rev 1.0 original version hm 7. oct. 2008 rev 1.1 changed function year to year_of_date changed function month to month_of_date *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MONTH_OF_DATE : INT VAR_INPUT IDATE : DATE; END_VAR (* version 1.3 27. mar. 2009 programmer hugo tested by tobias returns the current month of the year. *) (* @END_DECLARATION := '0' *) MONTH_OF_DATE := DAY_OF_YEAR(idate); IF MONTH_OF_DATE < 32 THEN MONTH_OF_DATE := 1; ELSIF LEAP_OF_DATE(IDATE) THEN MONTH_OF_DATE := (MONTH_OF_DATE * 53 + 1668) / 1623; ELSE MONTH_OF_DATE := (MONTH_OF_DATE * 53 + 1700) / 1620; END_IF; (* code for rev 1.2 MONTH_OF_DATE := DAY_OF_YEAR(IDATE); IF LEAP_OF_DATE(IDATE) THEN CASE MONTH_OF_DATE OF 1..31 : MONTH_OF_DATE := 1; 32..60 : MONTH_OF_DATE := 2; 61..91 : MONTH_OF_DATE := 3; 92..121 : MONTH_OF_DATE := 4; 122..152: MONTH_OF_DATE := 5; 153..182: MONTH_OF_DATE := 6; 183..213: MONTH_OF_DATE := 7; 214..244: MONTH_OF_DATE := 8; 245..274: MONTH_OF_DATE := 9; 275..305: MONTH_OF_DATE := 10; 306..335: MONTH_OF_DATE := 11; 336..366: MONTH_OF_DATE := 12; END_CASE; ELSE CASE MONTH_OF_DATE OF 1..31 : MONTH_OF_DATE := 1; 32..59 : MONTH_OF_DATE := 2; 60..90 : MONTH_OF_DATE := 3; 91..120 : MONTH_OF_DATE := 4; 121..151: MONTH_OF_DATE := 5; 152..181: MONTH_OF_DATE := 6; 182..212: MONTH_OF_DATE := 7; 213..243: MONTH_OF_DATE := 8; 244..273: MONTH_OF_DATE := 9; 274..304: MONTH_OF_DATE := 10; 305..334: MONTH_OF_DATE := 11; 335..365: MONTH_OF_DATE := 12; END_CASE; END_IF; *) (* revision history hm 1. aug 2006 rev 1.0 original version hm 1. okt 2007 rev 1.1 replaced old code (string conversion) with mathematics the execution time is now multiple times faster. hm 7. oct. 2008 rev 1.2 changed name of function from month to MONTH_OF_DATE hm 27. mar. 2009 rev 1.3 new improved code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION MULTIME : TIME VAR_INPUT t : TIME; M : REAL; END_VAR (* version 1.1 14 mar 2008 programmer hugo tested by tobias multiplies a time by a real number and returns a time *) (* @END_DECLARATION := '0' *) MULTIME := DWORD_TO_TIME(REAL_TO_DWORD(DWORD_TO_REAL(TIME_TO_DWORD(t))*M)); (* revision history hm 2. oct 2006 rev 1.0 original version hm 14. mar 2008 rev 1.1 rounded the result after the last digit *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION PERIOD : BOOL VAR_INPUT d1 : DATE; dx : DATE; d2 : DATE; END_VAR VAR day1, day2, dayx : INT; END_VAR (* version 1.3 22. mar. 2008 programmer hugo tested by tobias PERIOD checks if a given date is between two dates (d1 and d2) d1 is the starting date and d2 the last date for the period. the years of the dates are ignored, so the function period cheks for a time period within a year independet of the year. *) (* @END_DECLARATION := '0' *) day1 := DAY_OF_YEAR(d1); day2 := DAY_OF_YEAR(d2); dayx := DAY_OF_YEAR(dx); IF NOT LEAP_OF_DATE(dx) AND dayx > 58 THEN dayx := dayx + 1; END_IF; IF NOT LEAP_OF_DATE(d1) AND day1 > 58 THEN day1 := day1 + 1; END_IF; IF NOT LEAP_OF_DATE(d2) AND day2 > 58 THEN day2 := day2 + 1; END_IF; IF day2 < day1 THEN (* the period spans over the new year *) PERIOD := dayx <= day2 OR dayx >= day1; ELSE PERIOD := dayx >= day1 AND dayx <= day2; END_IF; (* code before rev 1.2 yx := year(dx); p1 := date_add(d1,0,0,0,yx - year(d1)); p2 := date_add(d2,0,0,0,yx - year(d2)); IF p2 >= p1 THEN period := dx <= p2 AND dx >= p1; ELSE period := dx <= p2 OR dx >= p1; END_IF; *) (* revision history hm 19. sep 2007 rev 1.0 original version hm 20. sep 2007 rev 1.1 corrected a problem with leap year hm 4. jan 2008 rev 1.2 changed code for better performance hm 22. mar. 2008 rev 1.3 function would deliver wrong results when d1, d2 or dx are a leap_year *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION PERIOD2 : BOOL VAR_INPUT DP : ARRAY[0..3,0..1] OF DATE; DX : DATE; END_VAR (* version 1.0 27. apr. 2008 programmer hugo tested by tobias PERIOD2 checks if DX is within one of 4 periods and sets the output true if so. *) (* @END_DECLARATION := '0' *) PERIOD2 := (DX >= DP[0,0] AND DX <= DP[0,1]) OR (DX >= DP[1,0] AND DX <= DP[1,1]) OR (DX >= DP[2,0] AND DX <= DP[2,1]) OR (DX >= DP[3,0] AND DX <= DP[3,1]); (* revision history hm 27. apr 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION REFRACTION : REAL VAR_INPUT ELEV : REAL; END_VAR (* version 1.1 7. mar. 2009 programmer hugo tested by oscat REFRACTION calculates the atmospheric refraction in degrees. the input angle goes from 0 at the hirizon to 90 at midday. *) (* @END_DECLARATION := '0' *) elev := LIMIT(-1.9, elev, 80.0); REFRACTION := 0.0174532925199433 / TAN(0.0174532925199433 * (ELEV + 10.3 / (ELEV + 5.11))); (* revision histroy hm 14. jul. 2008 rev 1.0 original release hm 7. mar. 2009 rev 1.1 using new formula *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK RTC_2 VAR_INPUT SET : BOOL; SDT : DT; SMS : INT; DEN : BOOL; OFS : INT; END_VAR VAR_OUTPUT UDT : DT; LDT : DT; DSO : BOOL; XMS : INT; END_VAR VAR RT : RTC_MS; END_VAR (* version 1.4 27. apr. 2011 programmer hugo tested by tobias RTC_2 is a real time clock module which runs utc and generates local time from utc. daylight savings time can be enabled with den and an additional local time is generated with a delay of ofs im minutes. *) (* @END_DECLARATION := '0' *) (* call rtc *) RT(SET := SET, SDT := SDT, SMS := SMS); UDT := rt.xdt; XMS := rt.XMS; (* check for daylight savings time and set dso output *) DSO := DST(udt) AND DEN; (* calculate time offset and set ldt output *) LDT := DWORD_TO_DT(DT_TO_DWORD(UDT) + INT_TO_DWORD(ofs + BOOL_TO_INT(DSO)*60) * 60); (* revision history hm 20. jan. 2008 rev 1.0 original version hm 20. feb. 2008 rev 1.1 added Millisecond Set input hm 12. jun. 2008 rev 1.2 improved performance hm 20. jan. 2011 rev 1.3 changed offset to be in minutes hm 27. apr. 2011 rev 1.4 fixed error with local time calculation *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK RTC_MS VAR_INPUT SET : BOOL; SDT : DT; SMS : INT; END_VAR VAR_OUTPUT XDT : DT; XMS : INT; END_VAR VAR init: BOOL; last: DWORD; Tx: DWORD; END_VAR (* version 1.1 20. feb. 2008 programmer hugo tested by tobias RTC_MS is a real time clock module which can be set to SDT when set is TRUE and the outputs XDT and XT present the DateTime and TOD with a resolution of milliseconds. *) (* @END_DECLARATION := '0' *) tx := T_PLC_MS(); IF set OR NOT init THEN (* clock needs to be set when set is true or after power up *) init := TRUE; xdt := SDT; XMS := SMS; ELSE XMS := XMS + DWORD_TO_INT(tx - last); (* check if one second has expired *) IF XMS > 999 THEN XDT := XDT + T#1s; XMS := XMS - 1000; END_IF; END_IF; last := tx; (* revision history hm 20. jan. 2008 rev 1.0 original version hm 20. feb. 2008 rev 1.1 added Millisecond Set input *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SDT_TO_DATE : DATE VAR_INPUT DTI : SDT; END_VAR (* version 1.0 18. oct 2008 programmer hugo tested by oscat converts Structured date time (SDT) to Date Time *) (* @END_DECLARATION := '0' *) SDT_TO_DATE := SET_DATE(DTI.YEAR, DTI.MONTH, DTI.DAY); (* revision history hm 18. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SDT_TO_DT : DT VAR_INPUT DTI : SDT; END_VAR (* version 1.0 18. oct 2008 programmer hugo tested by oscat converts Structured date time (SDT) to Date Time *) (* @END_DECLARATION := '0' *) SDT_TO_DT := SET_DT(DTI.YEAR, DTI.MONTH, DTI.DAY, DTI.HOUR, DTI.MINUTE, DTI.SECOND); (* revision history hm 18. oct. 2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SDT_TO_TOD : TOD VAR_INPUT DTI : SDT; END_VAR (* version 1.1 16. nov. 2008 programmer hugo tested by oscat converts Structured date time (SDT) to Date Time *) (* @END_DECLARATION := '0' *) SDT_TO_TOD := DWORD_TO_TOD(INT_TO_DWORD(DTI.HOUR) * 3600000 + INT_TO_DWORD(DTI.MINUTE) * 60000 + INT_TO_DWORD(DTI.SECOND) * 1000 + INT_TO_DWORD(DTI.MS)); (* revision history hm 18. oct. 2008 rev 1.0 original version hm 16. nov. 2008 rev 1.1 added typecasts to avoid warnings *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SECOND : REAL VAR_INPUT itod : TOD; END_VAR (* version 1.1 2 oct 2006 programmer hugo tested by oscat returns the seconds and milliseconds as real of TOD *) (* @END_DECLARATION := '0' *) SECOND := DWORD_TO_REAL(TOD_TO_DWORD(itod) - TOD_TO_DWORD(itod)/60000 * 60000) / 1000.0; (* change history hm 2. oct. 2006 rev 1.1 changed name of input to itod *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SECOND_OF_DT : INT VAR_INPUT XDT : DT; END_VAR (* version 1.0 6. jun. 2008 programmer oscat tested BY oscat SECOND_OF_DT returns the current second (second of minute) of a DT variable *) (* @END_DECLARATION := '0' *) SECOND_OF_DT := DWORD_TO_INT(DT_TO_DWORD(XDT) MOD 60); (* revision history hm 6.9.2008 rev 1.0 original version *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SECOND_TO_TIME : TIME VAR_INPUT IN : REAL; END_VAR (* version 1.2 24. feb. 2009 programmer hugo tested by tobias converts an amount of seconds in real to time execution TIME on wago 750-841 = 17us *) (* @END_DECLARATION := '0' *) SECOND_TO_TIME := DWORD_TO_TIME(REAL_TO_DWORD(IN * 1000.0)); (* revision history hm 4. aug. 2006 rev 1.0 original version hm 14. mar. 2008 rev 1.1 rounded the input after the last digit hm 24. feb. 2009 rev 1.2 changed input to IN *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SET_DATE : DATE VAR_INPUT YEAR : INT; MONTH : INT; DAY : INT; END_VAR VAR count : INT; END_VAR (* version 2.3 29. dec. 2011 programmer hugo tested by tobias creates a date output from year, month and day of month *) (* @END_DECLARATION := '0' *) IF month > 2 THEN count := (month - 1) * 30; IF month > 7 THEN count := count + SHR(month - 3,1); ELSE count := count + SHR(month - 4,1); END_IF; (* chech for leap year and add one day if true *) IF SHL(year,14) = 0 THEN count := count + 1; END_IF; ELSE count := (month - 1) * 31; END_IF; SET_DATE := DWORD_TO_DATE((INT_TO_DWORD(count + day - 1) + SHR(INT_TO_DWORD(year) * 1461 - 2878169, 2)) * 86400); (* revision history hm 4. aug. 2006 rev 1.0 original version hm 19 sep. 2007 rev 1.1 use function leap_year to calculate leap year, more exceptions are handled hm 1. okt 2007 rev 1.2 added compatibility to step7 hm 16.dec 2007 rev 1.3 changed code to improove performance hm 3. jan. 2008 rev 1.4 further improvements in performance hm 16. mar. 2008 rev 1.5 added type conversions to avoid warnings under codesys 3.0 hm 7. apr. 2008 rev 1.6 deleted unused step7 code hm 14. oct. 2008 rev 1.7 optimized code for better performance hm 25. oct. 2008 rev 2.0 new code using setup constants hm 16. nov. 2008 rev 2.1 added typecasts to avoid warnings hm 22. jan. 2011 rev 2.2 improved performance hm 29. dec. 2011 rev 2.3 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SET_DT : DT VAR_INPUT year : INT; month : INT; day : INT; hour : INT; minute : INT; second : INT; END_VAR (* version 1.5 16 mar 2008 programmer hugo tested by tobias creates a date output from year, month and day of month year must be in the form of 4 digits ie 2006 or 1977. *) (* @END_DECLARATION := '0' *) SET_DT := DWORD_TO_DT(DATE_TO_DWORD(SET_DATE(YEAR, MONTH, day)) + INT_TO_DWORD(SECOND) + INT_TO_DWORD(MINUTE) * 60 + INT_TO_DWORD(HOUR) * 3600); (* revision history hm 4. aug. 2006 rev 1.0 original version hm 19 sep. 2007 rev 1.1 use function leap_year to calculate leap year, more exceptions are handled hm 1. okt 2007 rev 1.2 added step7 compatibility call function set_date hm 8. oct 2007 rev 1.3 deleted unused variables count and leap hm 1. 11 2007 rev 1.4 converted hour type integer to dword in calculation to avoid overrun on möller ecp4 hm 16. mar 2008 rev 1.5 added type conversions to avoid warnings under codesys 3.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SET_TOD : TOD VAR_INPUT hour : INT; minute : INT; second : REAL; END_VAR (* version 1.5 16. mar 2008 programmer hugo tested by tobias creates tod from hour minute and second *) (* @END_DECLARATION := '0' *) SET_TOD := DWORD_TO_TOD(REAL_TO_DWORD(SECOND * 1000.0) + INT_TO_DWORD(MINUTE) * 60000 + INT_TO_DWORD(HOUR) * 3600000); (* revision history hm 4.aug.2006 rev 1.0 original version hm 11. sep 2007 rev 1.1 changed coding to avoid a compiler warning under twincat. hm 1. nov 2007 rev 1.2 changed coding to avoid possible overrun situation on möller ecp4 hm 2. Nov 2007 rev 1.3 changed dword to DINT in calcualtion to avoid warnings with some compilers hm 14. mar 2008 rev 1.4 changed code to avoid rounding problem at last digit of millisecond hm 16. mar. 2008 rev 1.5 added type conversions to avoid warning under codesys 3.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION SUN_MIDDAY : TOD VAR_INPUT LON : REAL; UTC : DATE; END_VAR VAR T : REAL; OFFSET : REAL; END_VAR (* version 1.0 26. jan. 2011 programmer hugo tested by oscat this FUNCTION calculates the time when the sun stand exactly south of a given location. *) (* @END_DECLARATION := '0' *) T := INT_TO_REAL(DAY_OF_YEAR(utc)); OFFSET := -0.1752 * SIN(0.033430 * T + 0.5474) - 0.1340 * SIN(0.018234 * T - 0.1939); SUN_MIDDAY := HOUR_TO_TOD(12.0 - OFFSET - lon * 0.0666666666666); (* revision history hm 26. jan. 2011 rev 1.0 initial release *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SUN_POS VAR_INPUT latitude : REAL; (* latitude of geographical position *) longitude : REAL; (* longitude of geographical position *) utc : DT; (* world time *) END_VAR VAR_OUTPUT B: REAL; H : REAL; HR: REAL; END_VAR VAR_TEMP g: REAL; a: REAL; d: REAL; t1: REAL; n: REAL; e: REAL; c: REAL; tau: REAL; sin_d: REAL; rlat: REAL; sin_lat: REAL; cos_lat: REAL; cos_tau: REAL; cos_d: REAL; END_VAR (* version 2.1 7. mar. 2009 programmer hugo tested by oscat this FUNCTION block calculates the sun position for a given date and time. the times are calculated in utc and have to be corrected for the given time zone. B is the angle from north and HR is the highth in degrees. *) (* @END_DECLARATION := '0' *) (* n is the julian date and the number of days since 1.1.2000-12:00 midday *) (* be careful for step7 date startes 1.1.1990 instead of 1.1.1970 *) (* for step7 this line must change *) n := DWORD_TO_REAL(DT_TO_DWORD(UTC) - 946728000) * 0.000011574074074074; g :=MODR(6.240040768 + 0.01720197 * n, math.PI2); d := MODR(4.89495042 + 0.017202792 * n, math.PI2) + 0.033423055 * SIN(g) + 0.000349066 * SIN(2.0*g); e := 0.409087723 - 0.000000006981317008 * n; cos_d := COS(d); sin_d := SIN(d); a := ATAN(COS(e) * sin_d / cos_d); IF cos_d < 0.0 THEN a := a + math.PI; END_IF; c := ASIN(SIN(e) * sin_d); (* also here we must be very careful utc is from 1.1.1970 for step7 the formula must change *) tau := RAD(MODR(6.697376 + (n - 0.25) * 0.0657098245037645 + DWORD_TO_REAL(TOD_TO_DWORD(DT_TO_TOD(utc))) * 0.0000002785383333, 24.0) * 15.0 + longitude) - a; rlat := RAD(latitude); sin_lat := SIN(rlat); cos_lat := COS(rlat); cos_tau := COS(tau); t1 := cos_tau * sin_lat - TAN(c) * cos_lat; B := ATAN(SIN(tau) / t1); IF t1< 0.0 THEN B := B + math.PI2; ELSE B := B + math.PI; END_IF; B := DEG(MODR(B, math.PI2)); h := DEG(ASIN(COS(C) * cos_tau * cos_lat +SIN(c) * sin_lat)); IF h > 180.0 THEN h := h - 360.0; END_IF; (* consider refraction *) HR := h + REFRACTION(h); (* revision history hm 1. feb 2007 rev 1.0 original version hm 6. jan 2008 rev 1.1 performance improvements hm 18. jan 2008 rev 1.2 further performance improvements only calculate once every 10 seconds hm 16. mar. 2008 rev 1.3 added type conversion to avoid warnings under codesys 3.0 hm 30. jun. 2008 rev 1.4 added type conversions to avoid warnings under codesys 3.0 hm 18. oct. 2008 rev 1.5 using math constants hm 17. dec. 2008 rev 1.6 angles below horizon are displayed in negative degrees hm 27. feb. 2009 rev 2.0 new code with better accuracy hm 7. mar. 2009 rev 2.1 refraction is added after angle normalization deleted 10 second lockout added output for astronomical heigth h *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK SUN_TIME VAR_INPUT LATITUDE : REAL; (* latitude of geographical position *) LONGITUDE : REAL; (* longitude of geographical position *) UTC : DATE; (* world time *) H : REAL := -0.83333333333; (* heighth above horizon for sunrise *) END_VAR VAR_OUTPUT MIDDAY : TOD; (* astrological midday in hours when sun stands at south direction *) SUN_RISE : TOD; (* sun rise for current day in local time *) SUN_SET : TOD; (* sun set for current day in local time *) SUN_DECLINATION : REAL; (* sun declination above horizon at midday in degrees *) END_VAR VAR dk: REAL; (* sun declination at midday *) delta: TIME; (* delta from midday for sunrise and sunset *) b: REAL; END_VAR (* version 1.7 25. jan. 2011 programmer hugo tested by tobias this FUNCTION block calculates the sun rise, sun set, sun offset at midday sun declination for a given date for performance reasons the algorithm has been simplified and is accurate within a few minutes only the times are calculated in utc and have to be corrected for the given time zone this correction is not done within sun_time because it would be a problem on days where dst is enabled or disabled *) (* @END_DECLARATION := '0' *) B := latitude * 0.0174532925199433; MIDDAY := SUN_MIDDAY(longitude, utc); DK := 0.40954 * SIN(0.0172 * (INT_TO_REAL(DAY_OF_YEAR(utc)) - 79.35)); sun_declination := DEG(DK); IF sun_declination > 180.0 THEN sun_declination := sun_declination - 360.0; END_IF; sun_declination := 90.0 - LATITUDE + sun_declination; delta := HOUR_TO_TIME(ACOS((SIN(RAD(H)) - SIN(B) * SIN(DK)) / (COS(B) * COS(DK))) * 3.819718632); sun_rise := MIDDAY - delta; sun_set := MIDDAY + delta; (* revision history rev 1.1 hm 20.1.2007 deleted unused variables sun_riseR and sun_setR rev 1.2 hm 17.4.2007 corrected error while sun:midday would not be corrected for longitude. rev 1.3 hm 6. jan 2008 performance improvements rev 1.4 hm 17. jan 2008 calculation is now only performed once a day hm 10. mar. 2009 rev 1.5 improved performance calculation will be performed on every call to allow movong installations hm 26. jul 2009 rev 1.6 fixed a problem with wrong midday calculation hm 25. jan. 2011 rev 1.7 using function sun_midday corrected angle of sun_declination added input H *) END_FUNCTION_BLOCK (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION TIMECHECK : BOOL VAR_INPUT TD : TOD; START : TOD; STOP : TOD; END_VAR (* version 1.0 19. jul. 2009 programmer oscat tested by oscat this function retruns true if the daytime TD is between start and stop and returns true if so. if you want to generate an event to span over midnight, start timemust be later than the stop time. *) (* @END_DECLARATION := '0' *) IF stop < start THEN TIMECHECK := start <= TD OR TD < stop; ELSE TIMECHECK := start <= TD AND TD < stop; END_IF; (* revision history hm 19. jul. 2009 rev 1.0 original release *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION UTC_TO_LTIME : DT VAR_INPUT UTC : DT; DST_ENABLE : BOOL; TIME_ZONE_OFFSET : INT; END_VAR VAR tmp: INT; END_VAR (* version 1.9 27. feb. 2009 programmer oscat tested by oscat ltime is a real time clock that uses the system rtc as utc time and calculates and given time zone. the utc time is supplied on the input UTC. according to the input variable time_zone_offset when the input dst_enable is true, the dst on and off times are calculated by a formula for any given year and the time is advanced and reset by one hour at 02:00 and 03:00 for the last sunday of march and last sunday of october. the code is high performance and the rtc counts every second. if more then one time zone is needed by the systen the clock can be started many times by placing more then one function block. *) (* @END_DECLARATION := '0' *) tmp := TIME_ZONE_OFFSET * 60 + BOOL_TO_INT(DST_ENABLE AND DST(UTC)) * 3600; IF tmp < 0 THEN tmp := ABS(tmp); UTC_TO_LTIME := DWORD_TO_DT(DT_TO_DWORD(UTC) - INT_TO_DWORD(tmp)); ELSE UTC_TO_LTIME := DWORD_TO_DT(DT_TO_DWORD(UTC) + INT_TO_DWORD(tmp)); END_IF; (* revision history hm 2.10.2006 rev 1.1 corrected an error where dst would be delayed by 0.1second hm 17.1.2007 rev 1.2 added utc input instead of internal sysrtcgettime because this would only work on wago. dst_enable would not be checked before dst would be enabled. hm 18.3.2007 rev 1.3 changed code, dst would not work during first cycle. hm 24.10.2007 rev 1.4 changed code because the execution every 100ms can cause major problems if the supplied time was not correct at start. use of new dst function hm 12. nov 2007 rev 1.5 changed Type of time_zone_offset from time to int to allow for time zones with negative offset hm 8 dec 2007 rev 1.6 corrected a problem with time_zone_offset hm 14. oct. 2008 rev 1.7 renamed module from LTIME to UTC_TO_LTIME changed function weekday to day_of_week optimized code for better performance hm 20. oct. 2008 rev 1.8 changes type of input TIME_ZONE_OFFSET from real to int, now is in +/-minutes deleted outputs DST_ON and WDAY converted to function hm 27. feb. 2009 rev 1.9 added type conversions to avoid warnings under codesys 3.0 *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION WORK_WEEK : INT VAR_INPUT idate : DATE; END_VAR VAR d1 : DATE; w1 : INT; ds: DWORD; yr: INT; w31: INT; w01: INT; wm: INT; END_VAR (* version 1.5 25. oct. 2008 programmer hugo tested by oscat calculates the work week for a given date according to iso8601 *) (* @END_DECLARATION := '0' *) (* berechne den 1.1 des jahres von idate. *) yr := YEAR_OF_DATE(idate); d1 := YEAR_BEGIN(yr); (* wochentag von d1 *) w1 := DAY_OF_WEEK(d1); (* offset des montags der eletzten KW des vorjahres *) (* wenn der erste tag des jahres größer als donnerstag ist dann beginnt die letzte kw am montag davor *) (* wenn der erste tag des jahres ein donnerstag oder kleiner ist beginnt die erste kw 2 montage davor *) IF w1 < 5 THEN ds := DATE_TO_DWORD(d1) - INT_TO_DWORD(w1+6) * 86400; ELSE ds := DATE_TO_DWORD(d1) - INT_TO_DWORD(w1-1) * 86400; END_IF; (* kalenderwoche des eingangsdatums *) WORK_WEEK := DWORD_TO_INT((DATE_TO_DWORD(idate) - ds) / 604800); (* korrektur wenn work_week = 0 *) IF work_week = 0 THEN (* work_week needs to be 53 when 1.jan of the year before is thursday or dec 31. is thursday. *) (* first and last weekday of a year is equal and one more day for a leap_year. *) IF w1 > 1 THEN w31 := w1 - 1; ELSE W31 := 7; END_IF; IF LEAP_YEAR(yr - 1) AND w31 > 1 THEN w01 := W31 - 1; ELSE w1 := 7; END_IF; (* if first or last day of a year is a thursday, the year has 53 weeks *) WORK_WEEK := 52 + BOOL_TO_INT(w31 = 4 OR w01 = 4); ELSE (* end of year calculation *) (* calculated the first and last weekday *) IF leap_year(yr) THEN IF w1 < 7 THEN w31 := w1 + 1; ELSE w31 := 1; END_IF; ELSE w31 := w1; END_IF; (* if first or last day is thursday then the year has 53 weeks otherwise only 52 *) wm := 52 + BOOL_TO_INT(w31 = 4 OR w1 = 4); IF WORK_WEEK > wm THEN WORK_WEEK := 1; END_IF; END_IF; (* revision history hm 17.1.2007 rev 1.1 deleted unused variable yday hm 19. dec 2007 rev 1.2 changed code for better performance changed code to comply with ISO8601 hm 16. mar 2008 rev 1.3 added type conversions to avoid warnings under codesys 3.0 hm 7. oct. 2008 rev 1.4 changed function year to year_of_date changed function weekday to day_of_week hm 25. oct. 2008 rev 1.5 optimized code for performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION YEAR_BEGIN : DATE VAR_INPUT y : INT; END_VAR (* version 1.2 7. Apr. 2008 programmer hugo tested by tobias returs the date of january 1st for the given year the function works for dates from 1970 - 2099 *) (* @END_DECLARATION := '0' *) YEAR_BEGIN := DWORD_TO_DATE(SHR(INT_TO_DWORD(y) * 1461 - 2878169,2) * 86400); (* revision history hm 19. dec 2007 rev 1.0 original version hm 4. jan 2008 rev 1.1 formula for step7 was incorrect during leap years hm 7. apr. 2008 rev 1.2 deleted unused step7 code *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION YEAR_END : DATE VAR_INPUT y : INT; END_VAR (* version 1.1 24. jan. 2011 programmer hugo tested by oscat returs the date of december 31st for the given year the function works for dates from 1970 - 2099 *) (* @END_DECLARATION := '0' *) YEAR_END := DWORD_TO_DATE(SHR(INT_TO_DWORD(y) * 1461 - 2876712, 2) * 86400); (* revision history hm 15. jun. 2008 rev 1.0 original version hm 24. jan 2011 rev 1.1 improved performance *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '\/Time&Date' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION YEAR_OF_DATE : INT VAR_INPUT IDATE : DATE; END_VAR (* version 1.4 7. oct. 2008 programmer hugo tested by oscat returs the year of a date the function works for dates from 1970 - 2099 *) (* @END_DECLARATION := '0' *) YEAR_OF_DATE := DWORD_TO_INT((DATE_TO_DWORD(idate) + 43200) / 31557600 + 1970); (* revision history hm 4. aug 2006 rev 1.0 original version hm 1. okt 2007 rev 1.1 corrected error in algorithm adjustment for S7 compatibility hm 23.12.2007 rev 1.2 changed code for better performance hm 7. apr. 2008 rev 1.3 deleted unused step7 code hm 7. oct. 2008 rev 1.4 renamed function (year) to year_of_date *) END_FUNCTION (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE CALENDAR : STRUCT UTC : DT; (* world time UTC *) LDT : DT; (* local time *) LDATE : DATE; (* local date *) LTOD : TOD; (* local time of day *) YEAR : INT; (* year of LDATE *) MONTH : INT; (* month of LDATE *) DAY : INT; (* day of LDATE *) WEEKDAY : INT; (* weekday of LDATE *) OFFSET : INT; (* Time Zone Offset for Local time in minutes *) DST_EN : BOOL; (* daylight savings time enable *) DST_ON : BOOL; (* true when daylight savings time os on *) NAME : STRING(5); (* name of time zone *) LANGUAGE : INT; (* location number pls see location setup *) LONGITUDE : REAL; (* longitude of current location *) LATITUDE : REAL; (* latitude of current location *) SUN_RISE : TOD; (* sun_rise for current location *) SUN_SET : TOD; (* sun_set for current location *) SUN_MIDDAY : TOD; (* worldtime when sun stands at south position *) SUN_HEIGTH : REAL ; (* suns heigth at midday, south position *) SUN_HOR : REAL; (* sun angle horizontal 0 = north in degrees *) SUN_VER : REAL; (* sun angle vertical above horizon in degrees *) NIGHT : BOOL; (* true between sun_set and sun_rise *) HOLIDAY : BOOL; (* true when holiday *) HOLY_NAME : STRING(30); (* name of holiday *) WORK_WEEK : INT; (* current work week *) END_STRUCT END_TYPE (* revision history hm 21. nov. 2008 rev 1.0 original version hm 8. feb. 2009 rev 1.1 added sun position data hm 10. mar. 2009 rev 1.2 added work_week, sun_midday, sun_heigth hm 23. jan 2010 rev 1.3 sun_rise, sun_set and sun_midday are now calculated in local time *) (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE COMPLEX : STRUCT re : REAL; im : REAL; END_STRUCT END_TYPE (* revision history hm 18. oct. 2008 original version *) (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE CONSTANTS_LANGUAGE : STRUCT (* Language Setup *) DEFAULT : INT := 1; (* 1=english, 2=german 3=french *) LMAX : INT := 3; WEEKDAYS : ARRAY[1..3, 1..7] OF STRING(10) := 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'; WEEKDAYS2 : ARRAY[1..3, 1..7] OF STRING(2) := 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So', 'Lu', 'Ma', 'Me', 'Je', 'Ve', 'Sa', 'Di'; MONTHS : ARRAY[1..3, 1..12] OF STRING(10) := 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember', 'Janvier', 'Février', 'mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Decembre'; MONTHS3 : ARRAY[1..3, 1..12] OF STRING(3) := 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez', 'Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Jun', 'Jul', 'Aou', 'Sep', 'Oct', 'Nov', 'Dec'; DIRS : ARRAY[1..3,0..15] OF STRING(3) := 'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'; END_STRUCT END_TYPE (* revision history hm 19. oct. 2008 rev 1.0 original version hm 22. oct. 2008 rev 1.1 added directions DIRS hm 23. dec. 2008 rev 1.2 added french language *) (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE CONSTANTS_LOCATION : STRUCT (* location setup *) DEFAULT : INT := 1; (* 1=germany, 2=austria 3=france 4=belgium-german 5= italien-Südtirol *) LMAX : INT := 5; (* language spoken in the location *) LANGUAGE : ARRAY[1..5] OF INT := 2, 2, 3, 2, 2; END_STRUCT END_TYPE (* revision history hm 20. oct. 2008 rev 1.0 original version hm 23. dec. 2008 rev 1.1 added french holidays hm 18. jan 2011 rev 1.2 added spoken language deleted holidays from structure for more flexibility *) (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE CONSTANTS_MATH : STRUCT PI : REAL := 3.14159265358979323846264338327950288; (* Kreiszahl PI *) PI2 : REAL := 6.28318530717958647692528676655900576; (* PI * 2 *) PI4 : REAL := 12.56637061435917295385057353311801152; (* PI * 4 *) PI05 : REAL := 1.5707963267949; (* PI / 2 *) PI025 : REAL := 0.785398163397448; (* PI / 4 *) PI_INV : REAL := 0.318309886183791; (* 1 / PI *) E : REAL := 2.71828182845904523536028747135266249; (* Euler constant e *) E_INV : REAL := 0.367879441171442; (* 1 / e *) SQ2 : REAL := 1.4142135623731; (* Wurzel von 2 *) FACTS : ARRAY[0..12] OF DINT := 1,1,2,6,24,120,720,5040,40320,362880,3628800,39916800,479001600; END_STRUCT END_TYPE (* revision history hm 26.10.2008 rev 1.0 original version hm 26. mar. 2011 rev 1.1 added array facts *) (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE CONSTANTS_PHYS : STRUCT C : REAL := 299792458.0; (* Lichtgeschwindigkeit in m/s *) E : REAL := 1.60217653E-19; (* elementarladung in Colomb = A * s *) G : REAL := 9.80665; (* Erdbeschleunigung in m / s² *) T0 : REAL := -273.15; (* absoluter Nullpunkt in °C *) RU : REAL := 8.314472; (* Universelle Gaskonstante in J / (mol · K) *) PN : REAL := 101325.0; (* NormalDruck in Pa *) END_STRUCT END_TYPE (* revision history hm 18.10.2008 rev 1.0 original version hm 13. mar. 2009 rev 1.1 real constants updated to new systax using dot *) (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE CONSTANTS_SETUP : STRUCT (* setup Parameters *) EXTENDED_ASCII : BOOL := TRUE; CHARNAMES : ARRAY[1..4] OF STRING(253) := ';""&&<<>>€€  ¡¡¢¢££¤¤¥¥¦¦§§¨¨©©ªª««¬¬­­®®¯¯°°±±²²³³´´µµ¶¶··¸¸¹¹ºº»»¼¼ÛÛ', ';¾¾¿¿ÀÀÁÁÂÂÃÃÄÄÅÅÆÆÇÇÈÈÉÉÊÊËËÌÌÍÍÎÎÏÏÐÐÑÑÒÒÓÓÔÔÕÕÖÖ××ØØÙÙÚÚ½½', ';ÜÜÝÝÞÞßßààááââããääååææççèèééêêëëììííîîïïððññòòóóôôõõöö÷÷øøùù', ';úúûûüüýýþþÿÿ'; MTH_OFS : ARRAY[1..12] OF INT := 0,31,59,90,120,151,181,212,243,273,304,334; DECADES : ARRAY[0..8] OF REAL := 1.0,10.0,100.0,1000.0,10000.0,10000.0,100000.0,1000000.0,10000000.0; END_STRUCT END_TYPE (* revision history hm 24.10.2008 rev 1.0 original version *) (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE ESR_DATA : STRUCT TYP : BYTE; ADRESS : STRING(10); DS : DT; TS : TIME; DATA : ARRAY[0..7] OF BYTE; END_STRUCT END_TYPE (* revision history hm 18. oct. 2008 original version *) (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE FRACTION : STRUCT NUMERATOR : INT; DENOMINATOR : INT; END_STRUCT END_TYPE (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE HOLIDAY_DATA : STRUCT NAME : STRING(30); DAY : SINT; MONTH : SINT; USE : SINT; END_STRUCT END_TYPE (* revision history hm 18. oct. 2008 original version *) (* if month = 0 then F_day is the offset in days from easter used :0 = NOT used, 1 = used, -1..-7 menas the weekday before the specified DATE example -3 means wednesday before 23.11 *) (* samples for Holiday data HOLIDAY_DE : ARRAY[0..29] OF HOLIDAY_DATA := (name := 'Neujahr', day := 1, month := 1, use := 1), (name := 'Heilig Drei Könige', day := 6, month := 1, use := 1), (name := 'Karfreitag', day := -2, month := 0, use := 1), (name := 'Ostersonntag', day := 0, month := 0, use := 1), (name := 'Ostermontag', day := 1, month := 0, use := 1), (name := 'Tag der Arbeit', day := 1, month := 5, use := 1), (name := 'Christi Himmelfahrt', day := 39, month := 0, use := 1), (name := 'Pfingstsonntag', day := 49, month := 0, use := 1), (name := 'Pfingstmontag', day := 50, month := 0, use := 1), (name := 'Fronleichnam', day := 60, month := 0, use := 1), (name := 'Augsburger Friedensfest', day := 8, month := 8, use := 0), (name := 'Maria Himmelfahrt', day := 15, month := 8, use := 1), (name := 'Tag der Deutschen Einheit', day := 3, month := 10, use := 1), (name := 'Reformationstag', day := 31, month := 10, use := 0), (name := 'Allerheiligen', day := 1, month := 11, use := 1), (name := 'Buss und Bettag', day := 23, month := 11, use := 0), (name := '1. Weihnachtstag', day := 25, month := 12, use := 1), (name := '2. Weihnachtstag', day := 26, month := 12, use := 1); HOLIDAY_AT : ARRAY[0..29] OF HOLIDAY_DATA := (name := 'Neujahr', day := 1, month := 1, use := 1), (name := 'Heilig Drei Könige', day := 6, month := 1, use := 1), (name := 'Karfreitag', day := -2, month := 0, use := 1), (name := 'Ostersonntag', day := 0, month := 0, use := 1), (name := 'Ostermontag', day := 1, month := 0, use := 1), (name := '', day := 1, month := 5, use := 0), (name := 'Christi Himmelfahrt', day := 39, month := 0, use := 1), (name := 'Pfingstsonntag', day := 49, month := 0, use := 1), (name := 'Pfingstmontag', day := 50, month := 0, use := 1), (name := 'Fronleichnam', day := 60, month := 0, use := 1), (name := '', day := 8, month := 8, use := 0), (name := 'Maria Himmelfahrt', day := 15, month := 8, use := 1), (name := '', day := 3, month := 10, use := 0), (name := '', day := 31, month := 10, use := 0), (name := 'Allerheiligen', day := 1, month := 11, use := 1), (name := 'Maria Empfängnis', day := 8, month := 12, use := 1), (name := '1. Weihnachtstag', day := 25, month := 12, use := 1), (name := '2. Weihnachtstag', day := 26, month := 12, use := 1); HOLIDAY_FR : ARRAY[0..29] OF HOLIDAY_DATA := (name := 'Nouvel an', day := 1, month := 1, use := 1), (name := 'St Valentin', day := 14, month := 2, use := 0), (name := 'Vendredi Saint (alsace)', day := -2, month := 0, use := 0), (name := 'Dimanche de pâques', day := 0, month := 0, use := 1), (name := 'Lundi de pâques', day := 1, month := 0, use := 1), (name := 'Jeudi de Ascension', day := 39, month := 0, use := 1), (name := 'dimanche de Pentecôte ', day := 49, month := 0, use := 1), (name := 'jeudi de la Trinité', day := 60, month := 0, use := 0), (name := 'Fête du travail', day := 1, month := 5, use := 1), (name := 'Victoire 1945 ', day := 8, month := 5, use := 1), (name := 'Prise de La bastille', day := 14, month := 7, use := 1), (name := '15 Août 1944', day := 15, month := 8, use := 1), (name := 'Halloween', day := 31, month := 10, use := 0), (name := 'Armistice 1918', day := 11, month := 11, use := 1), (name := 'Noël', day := 25, month := 12, use := 1), (name := 'Saint Étienne (alsace)', day := 26, month := 12, use := 0), (name := 'Fête de la musique', day := 21, month := 6, use := 0); HOLIDAY_BED : ARRAY[0..29] OF HOLIDAY_DATA := (name := 'Neujahr', day := 1, month := 1, use := 1), (name := 'Ostersonntag', day := 0, month := 0, use := 1), (name := 'Ostermontag', day := 1, month := 0, use := 1), (name := 'Tag der Arbeit', day := 1, month := 5, use := 1), (name := 'Christi Himmelfahrt', day := 39, month := 0, use := 1), (name := 'Pfingsten', day := 49, month := 0, use := 1), (name := 'Pfingstmontag', day := 50, month := 0, use := 1), (name := 'Nationalfeiertag', day := 21, month := 7, use := 1), (name := 'Mariä Himmelfahrt', day := 15, month := 8, use := 1), (name := 'Allerheiligen', day := 1, month := 11, use := 1), (name := 'Feiertag DG', day := 15, month := 11, use := 1), (name := 'Heiligabend', day := 24, month := 12, use := 1), (name := '1. Weihnachtstag', day := 25, month := 12, use := 1), (name := '2. Weihnachtstag', day := 26, month := 12, use := 1), (name := 'Silvester', day := 31, month := 12, use := 1); HOLIDAY_ITD : ARRAY[0..29] OF HOLIDAY_DATA := (name := 'Neujahr', day := 1, month := 1, use := 1), (name := 'Heilig Drei Könige', day := 6, month := 1, use := 1), (name := 'Ostersonntag', day := 0, month := 0, use := 1), (name := 'Ostermontag', day := 1, month := 0, use := 1), (name := 'Tag der Befeiung Italiens', day := 25, month := 4, use := 1), (name := 'Tag der Arbeit', day := 1, month := 5, use := 1), (name := 'Pfingsten', day := 49, month := 0, use := 1), (name := 'Pfingstmontag', day := 50, month := 0, use := 1), (name := 'Tag der Republik Italien', day := 2, month := 6, use := 1), (name := 'Mariä Himmelfahrt', day := 15, month := 8, use := 1), (name := 'Allerheiligen', day := 1, month := 11, use := 1), (name := 'Mariä Empfängnis', day := 8, month := 12, use := 1), (name := 'Heiligabend', day := 24, month := 12, use := 0), (name := '1. Weihnachtstag', day := 25, month := 12, use := 1), (name := '2. Stephanstag', day := 26, month := 12, use := 1); *) (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE REAL2 : STRUCT R1 : REAL; (* small value *) RX : REAL; (* big value *) END_STRUCT END_TYPE (* revision history hm 18. oct. 2008 original version *) (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE SDT : STRUCT YEAR : INT; MONTH : INT; DAY : INT; WEEKDAY : INT; HOUR : INT; MINUTE : INT; SECOND : INT; MS : INT; END_STRUCT END_TYPE (* revision history hm 18. oct. 2008 original version *) (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE TIMER_EVENT : STRUCT TYP : BYTE; CHANNEL : BYTE; DAY : BYTE; START : TOD; DURATION : TIME; LAND : BYTE; LOR : BYTE; LAST : DT; END_STRUCT END_TYPE (* revision history hm 18. oct. 2008 rev 1.0 original version hm 26. jan. 2011 rev 1.1 changed type of last to be DT *) (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) TYPE VECTOR_3 : STRUCT X : REAL; Y : REAL; Z : REAL; END_STRUCT END_TYPE (* revision history hm 18. oct. 2008 original version *) (* @END_DECLARATION := '0' *) (* @NESTEDCOMMENTS := 'Yes' *) (* @GLOBAL_VARIABLE_LIST := 'Constants' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '4096' *) VAR_GLOBAL CONSTANT STRING_LENGTH : INT := 250; LIST_LENGTH : INT := 250; END_VAR (* revisionhistory hm 3. mar. 2007 extended accuracy of pi2 renamed T0 to Tk for better compatibility with other libraries renamed C t0 c0 for better compatibility hm 2. may. 2007 added RU universal gas constant hm 1. oct. 2007 added setup parameter STEP7 hm 31. oct. 2007 added PN hm 6. mar. 2008 added setup Parameter EXTENDED_ASCII hm 28. Mar. 2008 added setup parameter STRING_LENGTH hm 7. apr. 2008 Deleted unused setup parameter Step7 hm 19. apr. 2008 added setup parameter NETWORK_BUFFER_SIZE hm 27. apr. 2008 added PI05 and PI025 hm 18. may. 2008 added charname lists renamed E to E1 hm 30. jun. 2008 reduced size of string constants to 253 to avoid problems with target aixia DCUF hm 21. sep. 2008 added section language setup added section location setup hm 20. oct. 2008 changed to structured constants hm 16. nov. 2008 moved structured constants to global vars for compatibility reasons hm 13. nov. 2009 added network_buffer_short_size renamed network_buffer_long_size changed string length to 250 hm 20. jan. 2011 removed network buffers added list_length *) (* @OBJECT_END := 'Constants' *) (* @CONNECTIONS := Constants FILENAME : '' FILETIME : 0 EXPORT : 0 NUMOFCONNECTIONS : 0 *) (* @NESTEDCOMMENTS := 'Yes' *) (* @GLOBAL_VARIABLE_LIST := 'Globale_Variablen' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '4096' *) VAR_GLOBAL MATH : CONSTANTS_MATH; PHYS : CONSTANTS_PHYS; LANGUAGE : CONSTANTS_LANGUAGE; SETUP : CONSTANTS_SETUP; LOCATION : CONSTANTS_LOCATION; END_VAR (* @OBJECT_END := 'Globale_Variablen' *) (* @CONNECTIONS := Globale_Variablen FILENAME : '' FILETIME : 0 EXPORT : 0 NUMOFCONNECTIONS : 0 *) (* @NESTEDCOMMENTS := 'Yes' *) (* @GLOBAL_VARIABLE_LIST := 'Setup_Data' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '4096' *) VAR_GLOBAL RETAIN END_VAR (* @OBJECT_END := 'Setup_Data' *) (* @CONNECTIONS := Setup_Data FILENAME : '' FILETIME : 0 EXPORT : 0 NUMOFCONNECTIONS : 0 *) LIBRARY Standard.lib 4.10.05 11:14:46 (* @LIBRARYSYMFILEINFO := '0' *) NumOfPOUs: 21 CONCAT: 0 CTD: 0 CTU: 0 CTUD: 0 DELETE: 0 F_TRIG: 0 FIND: 0 INSERT: 0 LEFT: 0 LEN: 0 MID: 0 R_TRIG: 0 REPLACE: 0 RIGHT: 0 RS: 0 RTC: 0 SEMA: 0 SR: 0 TOF: 0 TON: 0 TP: 0 NumOfGVLs: 0 END_LIBRARY