While loop in action block

Hello,

I have created a module that I am trying to use to store recipe parameters in arrays, and then mapping elements of the arrays into the tuning parameters and the min/max value into a different Equipment module.

Below is my code for an action block

I have 2 while loops that are writing to tuning parameters for a PCSD EM TP_VALUE, TP_MIN, and TP_MAX from an array that I have set up. However, what I’m noticing is that it is executing the whole code in 1 scan and only updating the value for the final TP055. The while loop is incrementing the index, but are not writing the TP_TARGET, TP_TARGET_MIN, and TP_TARGET_MAX for every iteration of i, only the final iterarion. Any idea how to make this work?

i := 1;
WHILE i < 10 DO
'^/TP_VALUE_INDEX.CV' := "/TP00" + i + "_VALUE.CV";
'^/TP_MIN_INDEX.CV' := "/TP00" + i + "_MIN.CV";
'^/TP_MAX_INDEX.CV' := "/TP00" + i + "_MAX.CV";

'^/TP_TARGET.$REF' := '^/FERMENTER_INDEX.CV' + '^/TP_VALUE_INDEX.CV';
'^/TP_TARGET_MIN.$REF' := '^/FERMENTER_INDEX.CV' + '^/TP_MIN_INDEX.CV';
'^/TP_TARGET_MAX.$REF' := '^/FERMENTER_INDEX.CV' + '^/TP_MAX_INDEX.CV';

IF '^/TP_TARGET.CST' = 0 AND '^/TP_TARGET.$REF' = '^/FERMENTER_INDEX.CV' + '^/TP_VALUE_INDEX.CV' THEN
'^/TP_TARGET.CV' := '^/DNA_T_PARAM'[i][1];
'^/TP_TARGET_MIN.CV' := '^/DNA_T_LIM'[i][1];
'^/TP_TARGET_MAX.CV' := '^/DNA_T_LIM'[i][2];
i := i + 1;
ENDIF;
END_WHILE;

WHILE i >= 10 AND i <= 55 DO
'^/TP_VALUE_INDEX.CV' := "/TP0" + i + "_VALUE.CV";
'^/TP_MIN_INDEX.CV' := "/TP0" + i + "_MIN.CV";
'^/TP_MAX_INDEX.CV' := "/TP0" + i + "_MAX.CV";

'^/TP_TARGET.$REF' := '^/FERMENTER_INDEX.CV' + '^/TP_VALUE_INDEX.CV';
'^/TP_TARGET_MIN.$REF' := '^/FERMENTER_INDEX.CV' + '^/TP_MIN_INDEX.CV';
'^/TP_TARGET_MAX.$REF' := '^/FERMENTER_INDEX.CV' + '^/TP_MAX_INDEX.CV';
IF '^/TP_TARGET.CST' = 0 AND '^/TP_TARGET.$REF' = '^/FERMENTER_INDEX.CV' + '^/TP_VALUE_INDEX.CV' THEN
'^/TP_TARGET.CV' := '^/DNA_T_PARAM'[i][1];
'^/TP_TARGET_MIN.CV' := '^/DNA_T_LIM'[i][1];
'^/TP_TARGET_MAX.CV' := '^/DNA_T_LIM'[i][2];
i := i + 1;
ENDIF;
END_WHILE;

'IN_D.CV' := 0;

  • Unfortunately, your code as written is not going to work as you have designed. Dynamic references require a scan or two to resolve, so this is why you are only seeing the last one take effect. Loops execute in 1 scan. I think your best options are to unroll the loop and have lots of repeated code and/or consider restructuring the loop to do 1 value per scan. Next scan the dyn ref should be resolved and you can then write the values. This has the downside of taking 120 scans or so of the module to push all the values out.
  • In reply to Matt Forbis:

    Why do we even bother to have while loops then?
  • Hi,
    Can you update your code inside IF loop as below:
    Before incrementing variable 'i', check dynamic reference parameter '^/TP_TARGET.AWST' = 0 for making sure last write to target parameter is successful. I would suggest to check for all three dynamic reference parameters.
  • In reply to vmvmhatre:

    Unfortunately that still didn't work. I replaced the increment with

    IF '^/TP_TARGET.AWST' = 0 AND '^/TP_TARGET_MIN.AWST' = 0 AND '^/TP_TARGET_MAX.AWST' = 0 THEN
    i := i + 1;
    ENDIF;

    But it still went straight to the last point and did not write anything else
  • i think both min and max can't be can't be zero at a time.   ^/TP_TARGET_MIN.AWST' = 0 AND '^/TP_TARGET_MAX.AWST' = 0

  • In reply to gnvsrikanth1357:

    Why not? They are separate parameters
  • In reply to rhamlin:

    If I remember correctly, checks for .CST and .AWST are disabled within While loops in CALC and ACT blocks. Since no CALC or ACT block is allowed to consume more than 20,000 microseconds of CPU time the processor will interrupt the while loop prior to the dynamic reference resolving or the Asynchronous write completing.

    While loops are great for accessing local arrays or assigning the $REF of a dynamic reference. You can't do a write to a dynamic reference until it resolves and as stated previously, this can some time. I did an analysis several years ago on dynamic reference resolve time. For a parameter in another controller the resolve time could be up to 5 sec (after a fresh total download) but typically not less than 2 sec (previously used path). Within the same controller was not more than 2 sec (after a fresh total download) and as little as 0.5 sec for a previously used path. Controllers cache accessed dynamic reference paths so they are quicker on subsequent references.
  • In reply to rhamlin:

    One of the ways I frequently use While Loops is to handle array operations. For example, if you need to make an ordered queue, you could copy the values into an array, use the while loop to do a sort on the array, and then copy back to parameters at the end. While the act of putting stuff in and taking out of an array has to be done long hand, most operations within the array can be done very efficiently with loops. Other examples as Scott mentioned below are setting up lots of dynamic references.
  • Agree with Scott's reply. I also would have chosen SFC based loop approach instead of WHILE_LOOP approach for this functionality of parameter download when cross-controller communication is involved.
  • Sorry about my last post. Seems I pocket dialed an entry from my phone. I know, its another long one...

    Couple of thoughts on the While Loop and Calc blocks in general.

    While loops are executed in one scan of the block and so they need to work with internal data.
    Also note that the CALC block writes to local parameters are written at the end of the CALC block execution. This means that if you write to a local parameter and then read the value within the same scan, you will not read what you just wrote. That will appear on the next scan, or will be available for the next function block in the module.

    In addition to managing data in an Array, I've used the While loop to perform an iterative calculation for compressibility, which converges within 5 or 6 iterations. The math is done using internal parameters in the CALC expression so that values can be stored and read within the single scan of the expression.

    DeltaV limits the number of times the loop can run so as to prevent an infinite loop that would stop all module execution. I think it's a loop count limit, not a time, but either way, once the loop is deemed illegal, it flags and error and requires a download to clear. Same for a divide by 0 error. The math may be valid but at run time the data values result in something invalid, and the expression is flagged rather than crashing the controller.

    While Loops are quite useful, but we have to understand the environment in which they run. As mentioned already, an SFC provides an iterative loop with checks to manage an orderly progression. External references need to bind before they can be written to.

    Whether a parameter is local, externally referenced or dynamically referenced introduces additional concerns. From a CALC block expression, I guess there are four types of references to consider:
    - Internal values: These are Floating Point registers that can be declared and initialized or created adhoc in the Expression. These can be written to and read in the same scan. They do not persist through a download or a switchover. If data needs to persist, it must be stored in a parameters. The index value used in the Do While Loop is an internal value. CALC/OUT parameters are not internal values.

    -Local Parameters: Local Parameters are directly accessed by a module and a write to any local module parameter is immediate. The CALC block performs its parameter writes at the end of its code execution so they can't serve as temporary storage for use in the same scan (Internal values are for that), but the parameter value is updated and available to be called by the next Function block in the currently executing module. The result of one CALC expression can be used in a second CALC expression in the same scan of the module, assuming block execution is correct.

    -Remote Reference: This is a parameter in a module assigned to a different controller. Both local and remote references are accomplished with either an External Reference, or a Dynamic referenced. The $ref field holds the target parameter path. When the reference is remote, it is represented by a Proxy Client object in the communication layer. The proxy client represents the remote module and all the locally referenced parameters it needs to send to this controller. The CALC block is therefore able to perform a local write to the Proxy client and continue processing and executing. The value sits in the Proxy Client until the Asynnchronous Write task picks up all pending writes and sends them to the remote controllers hosting the target modules. The Proxy Client has an AWST (Asynchronous Write Status) that is set to Pending when the write is initiated, and is cleared when the write is actually completed to the destination which sends the success or error status. If the write fails because of improper mode, out of range, incorrect data type, the AWST will indicate an error. IF successful, the AWST = 0.

    A Dynamic reference is just like an External reference except it can be defined and redefined at runtime. Both External and Dynamic references have the have the AWS and the Connection Status that indicates the communication is established and good. (CST = 0 ) The external reference is defined in the database and verified on download. If the syntax is wrong or the reference is invalid, a warning is given. If you download anyway, the CST and ST will be BAD. The Dynamic reference is undefined on download. WHen $REF is finally defined, it too could have a syntax error or have an invalid path. Same result. It is always good practice of verify CST is 0 before using the remote reference. Note that if CST is BAD, then ST is also BAD, due to connection being bad. If CST is good, then ST reflects status of the referenced parameter. It may be BAD, but the substatus will indicate the reason for BAD. CST is your best check for communication integrity.

    How does a controller know where to locate a remote module? This is done with a runtime service that is managed and provided by a workstation. The workstations contain a complete module listing that includes their host controller node. Normally the Profession Plus handles module location requests, but if it is off line, an election is done to determine which available workstation will step in. When a controller is download, External references are established immediately and the Proplus is clearly present to respond. Dynamic references however, may only be defined hours or days later. When the $ref is finally defined, the controller looks locally, including the Proxy Clients, and if the module is not found, a request is sent as a broadcast (maybe a multicast but all workstations see it). The active directory service node responds with the host controller information. If the controller does not yet have a Device connection to this remote controller, it creates one. Then it creates the proxy Client for the module in question and adds the specific parameter. And then the remote module completes the sending of the data and the value is available and CST goes to 0. If the module was already referenced for a different parameter, then the Device connection and Module Proxy Client already exist. The new parameter is added to the existing Proxy, and once it is received, CST for this parameter goes to 0.

    Proxy clients are permanent, meaning that they do not "prune" or get destroyed if they are not used. The parameter will continue to be updated and CST will be 0 . However, if the controller is sent a Total Download, the Proxy clients will be destroyed and have to be rebuilt. In that case, the proxy client will not be recreated when the module is actually referenced again. SImilarly, if a switchover occurs, the Proxy clients will be created anew in the new Active processor. Enhancements have been made in v13 to handle switchover behavior and to avoid spurious conditions due to temporary bad connection status.

    Back to the CALC block, you should use this to execute calculations that take input data and produce output values that are used by subsequent function blocks. Any internal loops or incremental counters must use internal values if the value is to be stored and read in the same scan. Generally, write outputs to parameters once per expression execution. Note that local parameters also have a CST and AWST, which are always 0 as they are local and directly referenced. If you check for .CST of a local parameter the expression will work and the value will always be 0. so the same code that verifies a remote reference for CST or AWST will also work with a local parameter.

    and one more "rule". Only write values by exception. If you execute a write every scan, i.e. continuously, the write action must complete at the destination and this forces a load on the control thread to validate the write is permitted and is valid. For continuous exchange of data, set up a read connection. This makes use of the normal Unsolicited exception reporting. the source value is sent by exception and is present in the Proxy Client where it can be read by any module. Not only is the data sent more effectively, it is sent once for all modules in that controller. Also note that when you setup a dynamic reference, the referenced parameter establishes this connection in the reverse direction as unsolicited exception, making the written parameter available in the Proxy Client. If you write a value to 5 modules in the same controller, you have 5 writes to deal with and 5 Unsol sends to manage confirmation. If you set up 5 reads of the value in question, the value is sent once via unsolicited exception and used by the 5 modules. Use writes when it is appropriate to do so.

    Andre Dicaire

  • In reply to Andre Dicaire:

    Nice explanation, it would be cool to have a pictorial version of this explanation!
  • In reply to Andre Dicaire:

    While loops are limited to 2000 iterations, but it's a bit tricky, because IF statements within the loop are also counted as iterations. BOL describes this reasonably well, but you still have to test for worst-case scenarios.
    If the limit is exceeded, I believe there is some rollback performed by the controller. At least, what I experienced: I put a LOGEVENT in the WHILE loop to log an entry (not at every iteration, there is a limit for how many you can log). If the iteration limit was not reached, I saw the entries in the event journal. However, if it was, I saw absolutely no entries, as if the While loop would never have been executed.
  • You can use a local module parameter to save the index of where you are in looping. You can then EXIT the while loop after some iterations and then come back where you left off. In the example below, I have nested WHILE DO loops. I exit after 5 iterations of the OUTER loop.


    Rem '^/PARAM1 is wired to IN1. Param 1 is set true by external code and set false when both inner and outer loops complete
    Rem '^/SAVED_I stores the last completed index of the outer loop.
    Rem 'IN2 is the number of iterations for the outer while loop
    Rem 'IN3 is the number of iteration for the inner while loop

    IF IN1 = 1 THEN
    IF firstpass=0 THEN
    i := 1;
    j := 1;
    firstpass := 1;
    '^/SAVED_I':=0;
    ELSE
    i := '^/SAVED_I';
    End_IF;

    counter := 0;
    WHILE i <= IN2 DO
    j := 1 ;
    WHILE j <=IN3 DO
    REM - Do INNER LOOP STUFF
    j:=j+1;
    END_WHILE ;

    REM - Do OUTER LOOP STUFF

    REM - Overhead to allow nesting of WHILE loops
    counter := counter + 1;
    i := i+1 ;
    '^/SAVED_I.CV' :=i;
    IF i > IN2 THEN
    '^/PARAM1':=0; (* If i > IN2, we've completed the outer loop, clear the run request *)
    firstpass :=0;
    END_IF;

    IF counter > 5 then
    EXIT;
    END_IF;
    END_WHILE ;
    END_IF;