In the old days (Step5) you couldn't use symbolic programming for datablocks / datawords etc.

Step 7 made a big improvement by allowing us to name the datablock variables like words and booleans.

To use the symbolic names the datablock must also has a symbolic name and you must supply the full name to be able to point out the right variable.

Example    DB20  :  Name=DataMotor1,     Variabele inside DB20: 0.0        SpeedSetpoint :  INT 

There are a few ways to read this variable "SpeedSetpoint"

 

        L   DB20.DBW0                                                 // Load the first word of DB20 (without symbolic)

        L    "DataMotor1".SpeedSetpoint                     // Load the first word of DB20 (symbolic)

 

       OPN   DB20                                                        // open the datablock  DB20

       L      DBW 0                                                        // read first word of open datablock

 

The last 2 lines are almost identical to the S5 method.

I assume everyone uses the symbolic method when possible . This is the best way to minimze errors by wrong addressing of the datablock variables.

   Behind the scenes: Your symbolic lines are actually translated into into 2 seperate instructions....   "OPN DB20"  and  " L   DBW 0".

So every symbolic STL-line is translated into 2 instructions. Since symbolic programming is a big advantage nobody worries about the extra instructions. 

Although in some case you can speed up your code by using the old S5 method.

Open the datablock once and access the datablock variables directly like "L DBW 0".

 

When you use this "old" addressing method you have to be carefull when calling FB's with DBW parameters....

    OPN  DB20                                  // Open datablock DB20 "DataMotor1"

    CALL  FB10, DB10                       // call FB10 (motorcontrol)  with instance datablock 10

       motor  := 1                              // motor id

        speed := DBW0                      // DataMotor1.SpeedSetpoint

        start   :=  DBX10.0                // start/stop motor

        rpm    := DBW20                    // speed feedback in rpm

 

This example can give faulty results. Since the open datablock changes during the execution of the FB-call.

When you call an FB or FC the datablock registers are saved and when the function is finished they are restored.

But still this can result in errors. In this example the actual motor speed is an output of the function FB10. This function result is copied to the open datablock in DBW20.... and here it can go wrong. This is done before the datablock registers are restored and thus the actual open DB might not be the one we expect to be open, namely DB20.

The open datablock number might be influenced by the code inside FB10 and is set to the previous value of the instance datablock number  at the call of FB10. Before the FB10 is actually executed the CPU swaps the DI and DB register and opens DI 10.

 

What actually happens when FB10 is called is something like this:

      save the values of the DB and DI registers

     exchange DB and DI register

     open the instance datablock

     copy the parameters to the instance datablock

     execute  FB10

     copy the results to the output parameters (in this case rpm  *)

     restore  the DB and DI register

 

In this example the code to copy the output value to the rpm-variable could be:

   L     DIW 8                 // read rpm from the instance datablock DI10

   T    DBW20                // copy to dataword 20 of the open datablock  --> could be faulty !!!

 

To prevent suchs errors you need to use the full address like   "DB20.DBW20"  instead of "DBW20" only !!!  or be absolutely sure that you know what's going on behind the scenes.

 

 

If you want to create a sequencer/stepper program that requires a timer for most of the steps you can consider re-using one timer for all steps. 

In this example we use the S5T-timer of type S_ODT. It can only be used in STL-programming.

 

A timer is start by a rising edge on it's input. So if we want to re-use (restart) a timer we have to make sure that the timer gets a low input before we can restart it again. In this simple example we use a timer in "Step01" and again in "Step02".

I hope the example / comment makes things clear.

 

 


       AN    Step01         //  Step 1 active ?
       JC    S2                 // no ? check step2

       A     T 1                // timer finished ?
       R     Step01          // reset step 1
       S     Step02          // activate step 2

      A     Step01         //  Step 1 still active ? if Step 1 has just been reset this line also resets the timer input
      A      xyz                // are the conditions to finish step 1  TRUE ?
      L      S5T#5s          // start/reload timer
      SD   T 1


S2: AN    Step02         //  Step 2 active ?
      JC     S3                // no ? check step3

      A       T 1
      R       Step02
      S        Step03

      A     Step02         //  Step 2 still active ? if Step 2 has just been reset this line also resets the timer input
      A      xyz                // are the conditions to finish step 2  TRUE ?
      L      S5T#3s          // start/reload timer
      SD   T 1

S3: AN Step03

      and so on . . . . .

 

 

The bold-lines seem useless at first sight since the jump earlier in the program already checks the state of the step. But this line is added to reset the timer input when the step is finished. So the timer can detect a rising edge on it's input at the next step.


With modern new CPU's it's often not necessary to use this construction since there is enough room to use the memory consuming IEC timers which are only limited by the amount of work-memory.

But there are situation that this method can be usefull. For example if you want to create a standard block that has some stepper functionality and uses multiple timers. If you want to limit resource use this might be a good method.

 

When using REAL-variables (Floating point variables) you must be aware of their limited resolution.

The Siemens CPU makes use of a 32-bit floating point also known as variable type "SINGLE".

This variable type makes use of a 8 bit exponent and a 24-bit mantissa.

 

Any REAL variable is stored as a 7-digit number plus an exponent value.

     value 1234567     is stored as     1.234567 e+07

 

Problems occur when you need more then the 7-digit precision . . .

example:          1234567 +   0.0025  =  1234567.0025

but since the PLC cannot store more then 7 digits it will give a inaccurate result -> 1234567

(the extra digits in this example  .0025 are rounded to zero).

Normally this won't be a problem but when you're totalizing lots of numbers (small and big) you might lose information.

 

 

How to solve this problem ?

You can improve the resolution by using 2 floating points to store the sub-results.

Example       A =  A + B 

You need to create a A_high and a A_low variable (type REAL)

           Variable A_high is used to store the rounded result (digits on the left side of the decimal point).

           Variable A_low is used to store the value of the digits on the right side of the decimal point.

 

Calculation is as follows: 

A_high = A_high + rounded (B)

A_low  = A_low + (B - rounded(B)) 

 

For the best resolution you need to move the rounded value of A_low (>=1) to  from A_low to A_high

A_high = A_high + rounded(A_low)         // A_high contains a value >= 1.0

A_low = A_low - rounded(A_low)              // A_low contains a value  < 1.0

 

The total can be calculated by

A = A_high + A_low 

 

If you want to know more about floating point variables in general you can have a look at

http://docs.sun.com/source/806-3568/ncg_goldberg.html

A good programmer should exactly know what happens behind the scenes..... that's is one of our mottos.

This time we examined what happens inside the CPU when we call a function block (FB).

Some things are explained in the manuals but we tried to search a little deeper.

Here is the example FB-call       

 

  CALL  FB     2 , DB5                           // function  SUM=IN_A + IN_B
       IN_A:=MW6                                 // INPUT type INT
       IN_B:=MW8                                 // INPUT type INT
       SUM :=MW20                              // OUTPUT type INT
       IO  :=M0.6                                   // IN/OUT type BOOL

 

How is this interpreted by the CPU ?

- save the RLO-status on local stack at L 29.0
- swap the DB and DI registers
- open the instance datablock (DI 5)
- save address register AR2 on local stack at LD25
- copy MW6 to var IN_A which is stored inside the instance DB at DIW0
- copy MW8 to var IN_B which is stored inside the instance DB at DIW2
- copy M0.6 to var IO which is stored inside the instance DB at DIX6.0
- set pointer AR2 points to first variable in instance DB (DIW0)   -->   P#0.0
- call to function block FB2
- restore address register AR2
- copy the result (SUM) to MW20 (OUTPUT)
- copy the new status of IO to M0.6 (IO-pin has no function in this example)
- swap back the DB / DI registers

 

What does the code inside the FB look like ?

  L #IN_A               // IN_A  +  IN_B  --->  SUM
  L #IN_B
  +I
  T #SUM

 

This is translated as: 

  L DIW [AR2,P#0.0]                // Read value of IN_A from in instance DB   
  L DIW [AR2,P#2.0]                // Read value of IN_B from in instance DB
  +I                                              // add values
  T DIW [AR2,P#4.0]               // store result as SUM (OUTPUT parameter)

 

Conclusion:

As written in "The use of AR2 inside a function block" the CPU uses address register AR2 to access the variables inside the FB. This is done to make multi-instance functionality possible.

The DI-register is saved by a DB/DI-register swap. This is perhaps a bit confusing. We know a FB uses an instance datablock as memory so we might assume that the DI-register is used and thus altered, but that is not what happens.

In the FB-call you assign a DB (in this case DB5) to the function block so the Siemens programmers must have thought it's obvious that the DB-register is altered. So they save the DI-register and the DB-register is set to DB5 in this example when the function is finished.

This might be important to know in case you build complex blocks and rely the fact that calling a block doesn't alter the DB/DI-registers.

After a FB-call the DB-register points to the datablock used by the FB. The DI-register is restored (unchanged).

A lot of overhead is generated by using a FB call. All parameter data is copied to and from the instance DB parameter by parameter and inside the FB indirect addressing is used to access the parameters. This explains why FBs are rather slow compared to direct coding. 

In this example processing time is no issue but if you have many parameters and you call the FB many times per OB1-cycle then your cycle time will increase rapidly.

We will give an example how to improve the FB-cycle time in another article. 

 

 

It's not advisable to use AR2 (address register 2) inside a FB (Function block) since the CPU uses it internally to address the instance data (and thus to read and write the IN/OUT-parameters).

 

How does it work behind the scenes . . . . ?

For example you call a FB with  2 input parameters (type = INT)

CALL FB  10, DB20 

   INPUTS:     VAR1 :=   100    

                     VAR2 :=   50         

 

What happens  inside the CPU . . . .

The correct instance datablock is opened (DI 20)

AR2 is set to P#0.0 and thereby pointing to the first byte in the instance DB.

The values of the inputs are copied to the instance datablock before execution of the FB-code like :

OPN DI 20

L    100

T    DIW 0

L    50

T    DIW 2

LAR2 P#0.0

UC  FB10     // then the FB code is executed

 

As soon as you want to read the value of VAR1 inside the FB   with  STL-code   " L   #VAR1"   the CPU translates this into

L     DIW [AR2,P#0.0]        // -> reading DIW[0.0  + 0.0]   -->  DIW 0

STL-code   " L   #VAR2"   is  translates this into

L     DIW [AR2,P#2.0]        // -> reading DIW[0.0  + 2.0]   -->  DIW 2

 

If you use AR2 yourself inside the FB  and thus change the AR2 value for example from P#0.0 to P#2.0 this will result in faulty behaviour !

When the CPU tries to read VAR1 it actually reads the wrong address

L     DIW [AR2,P#0.0]        // -> reading DIW[2.0  + 0.0]   -->  DIW 2        This means VAR2 is read  instead of VAR1 !

 

This can lead to non existing addresses causing an "Access error"
 

Why does it use AR2 internally ?

In this example the use of AR2 seems not really necessary since it remains P#0.0. It's use becomes clear when using Multi instance functionality. Then  AR2 is used to address the first variable inside an array of variables (instances).

 

Solution:

Don't use AR2 inside a FB (you can choose to SAVE AR2 before using it yourself and RESTORE AR2 directly after use).

Make use of AR1 or use a local DWORD variable as pointer instead.