en
cz

Change Detection

Change Detection

This block was used for synchronizing the measurement of the quarter-hour. The input quarter-hour pulses are captured by the Wago 750-404 counter card, and the goal is to detect a change in the state of the counter (an increase of one pulse or potentially a counter overflow) and generate a pulse of one cycle length that would reset the counter for seconds in the quarter-hour. Directly feeding the synchronization pulse from the binary input into the counter input BD30 (as shown in the diagram below) would be risky— the pulse could be too short to safely pass through the input cards and the bus into the application program.

Here, the IF...ELSE...END_IF condition is already used, and using structured text makes sense. The source code is just a few lines—when the difference between the input and the stored value occurs, the internal variable pom is updated, and a logical 1 is written to the output for one cycle (until the next comparison).

 

The implementation of the change block in the program shows that even such a simple function significantly improves code readability:

The most significant challenge when working with elementary functions, variables, conditions (IF...ELSE, CASE), and loops (FOR, WHILE, and REPEAT) is probably getting the correct syntax.

ISO Week Number Calculation

One of the most unusual tasks in technical support was to algorithmically calculate the week number according to ISO 8601. The complete listing of the source code for the function block is as follows:

FUNCTION_BLOCK iso_week2
// As in https://webspace.science.uu.nl/~gent0113/calendar/isocalendar_text_5.htm
VAR
    today : dt; 
    wday, weekday, isoweeknr: dint; 
    m, dd, dow : sint;
    day, month, y : int;
    isoyear, year, d, d0, weekday0 : dint; 
END_VAR
VAR_OUTPUT
    iso_week2 : int;
END_VAR

today := getlocaltime();
DECOMPOSEDATE(T := today, YEAR => y, MONTH => m, DAY => dd, DAYOFWEEK => dow);
year:=to_dint(y);
month := m - 1; // 0=January, 1=February, etc.
day:=dd;
wday:=dow;

weekday := ((wday + 6) / 7) + 1; // weekdays will be numbered 1 to 7
isoyear:=year;
d0 := gregdaynumber(year,1,0);
weekday0 := to_dint(((d0+4) / 7)+1);
d := gregdaynumber(year, month + 1, day);
isoweeknr := trunc_sint((d - d0 + weekday0 + 6) / 7) - trunc_sint((weekday0 + 3) / 7);

// check whether the last few days of December belong to the next year's ISO week
if ((month = 11) and ((day - weekday) > 27)) then 
  isoweeknr:=1;
  isoyear:=isoyear+1;
end_if;
 
// check whether the first few days of January belong to the previous year's ISO week
if ((month = 0) and ((weekday - day) > 3)) then 
  d0 := gregdaynumber(year-1, 1, 0);
  weekday0 := ((d0+4) / 7) + 1;
  isoweeknr := trunc_int((d - d0 + weekday0 + 6) / 7) - trunc_int((weekday0 + 3) / 7);
  isoyear := isoyear -1;
END_IF;
iso_week2 := to_int(isoweeknr);
END_FUNCTION_BLOCK

FUNCTION gregdaynumber: dint
// computes the day number since 0 January 0 CE (Gregorian)
VAR_INPUT
    year : dint; 
    month, day : int;
END_VAR
VAR
    y: dint;
    m, d: int;
END_VAR 

y := year;
m := month;
IF month < 3 THEN 
    y := y - 1; 
    m := m + 12;
END_IF;
gregdaynumber := trunc_dint(365.25 * y) - trunc_dint(y/100) + trunc_dint(y/400) + trunc_dint(30.6*(m+1)) + day -62;
END_FUNCTION

This example is given for three reasons:

It is a function that is not often used, but it can be useful in cases involving time programs, exceptions, controlling devices in shift-based operations, etc.

It also shows that the code of a function block (iso_week2) can include other definitions – for example, the gregdaynumber function, which is then called three times in the block code (and thus it makes sense to separate this calculation as a function). The function definition comes after its call, which does not matter. It is enclosed by the words FUNCTION … END_FUNCTION.

And third – the function DECOMPOSEDATE is called in it in ST, which – like all functions, as we’ve already mentioned in the description of the differences between functions and function blocks – has only one implicit output but can provide data for multiple variables. We will show various ways in which functions exchange data with the environment. The call happens right on the second line of the code, after declaring the variables and loading the current time into the variable today.

The implicit output of the function “=>” according to the help returns a value of 1 if the conversion was successful. However, this information is not of interest to us in the program; we need the function to write the current values of the year, month, day, and day of the week into the corresponding block variables. This is done by assignment using the operator “=>”. In contrast, the input variable today is connected to the input of the function T via the assignment “:=”.

DECOMPOSEDATE(T := today, YEAR => y, MONTH => m, DAY => dd, DAYOFWEEK => dow);

These operators are essential for working with functions and function blocks called in ST. This "connection" of the function to variables is called parameter passing using a formal parameter list. The order of the parameters does not matter here; unmentioned inputs have implicit values.

Another way to assign inputs and outputs for the function is to directly write the variable names as arguments using an informal parameter list:

DECOMPOSEDATE(today, y, m, dd, dow);

In this case, however, all positions must be used in the order as listed in the help. For example, if we did not need the year value, the call

DECOMPOSEDATE(today, m, dd, dow); // ERROR

would result in a compilation error – here, type incompatibility: m is SINT, but INT is expected for YEAR. Even if the types matched, the call would have too few arguments; one input and four outputs are expected. The correct call with a limited number of arguments is done using the operator:

DECOMPOSEDATE(T := today, MONTH => m, DAY => dd, DAYOFWEEK => dow);

Mixed calls, such as:

DECOMPOSEDATE(T := today, y, m, DAY => dd, DAYOFWEEK => dow); // ERROR

are not permitted.

As another example, let's consider sending an alarm email:

Alarm Email

The following structure shows not a function block, but an entire program. Every program starts with the keyword PROGRAM and may again contain variable declarations at the beginning.

The variable SendLast monitors to ensure that the function messaging.sendmail is not called with every program execution (for example, every 20 ms), but only when the value of the Send variable changes from False to True. The result of the operation is stored in the Result variable, where a value of 0 indicates that the sending was successful. (The most common mistake here is getting result 16 = unknown email channel, which means that either the alarm channel is not defined in the PLC’s alarm definition, or a wrong name is used in the function call – note that the name is case-sensitive, so alrchannel and ALRCHANNEL are two different names.)

PROGRAM alarm_email
 VAR        
    Send : bool := False; //náběžná hrana odešle e-mail
    SendLast : bool;
    Channel : string := 'alrchannel';
    MailFrom  : string := 'sender@domain.com';
    MailTo  : string := 'recipient@domain.com';
    Subject  : string := 'Alarm!';
    Message : string := 'There is an alarm in the PLC.';
    Result: SINT;
 END_VAR     
 
 IF Send AND (NOT SendLast) THEN
   Result := messaging.sendmail(channel, MailFrom, MailTo, Subject, Message);
 END_IF;
 SendLast := Send; 
END_PROGRAM

That's all for calling functions with arguments.

 All parts of article