Here you go. In this example, I created a data block that contains the tags for twenty conveyor systems. Each conveyor is represented by a UDT, and is made up of varying data types. It is not necessary to add “fillers” to provide for new data later on, because the length of the UDT is determined at the startup of the PLC anyway, and the pointer will be adjusted accordingly. This is sometimes a big headache with absolute programming, where you might have to comb through the code and adjust indirect addresses.
I’m not sure which PLC you will want to test this in, so I created a startup function (FC 100) that you will have to call in your startup OB. This startup function will determine the length of a conveyor UDT and record it in MD50. I used a simple method of determining the UDT length (check the length of the data block and divide by 20), but there are other ways to do it that are more powerful.
If you look in FB20, you will see that I assign the conveyor UDT as a STAT variable, and that there are no other variables assigned. When I call FB20, the UDT will just map to the data block, using a pointer offset based on the UDT length (it sounds complicated, but bear with me). I tossed some simple code in FB20 just so you can see that you will be programming in a true symbol format.
Now, look in OB1. You can see that I call FB20 one time for each conveyor, while adding MD50 to AR2 for each call. Also, you will see that since I am calling FB20 unconditionally, I don’t have to include parameters or an instance data block, as FB20 just uses the UDT listed on its STAT variable, and it uses the instance data block that happens to be open. Technically, I could have used an FC instead, but FCs work differently and the scan time would skyrocket.
Stepping through the program, this is what happens:
1. The PLC goes from STOP to RUN, and FC100 is called from my startup OB.
2. I look at the length of DB100 and divide by 20. This gives me the length of the conveyor UDT in bytes. I multiply this by 8 to get the length in bits, and transfer it to MD50. If you change the number of conveyors, remember to change the number “20”.
3. Now, OB1 is called. The first thing it does is open DB100 as an instance data block. It then sets AR2 to zero, so that we will start looking at the very first conveyor when FB20 is called.
4. FB20 is called, the code is executed, and it returns to OB1.
5. AR2 is increased by the length of one UDT by adding MD50 to it.
6. FB20 is called again, but this time it points to the second conveyor’s addresses.
7. Upon the return to OB1, AR2 is indexed again and the next conveyor is called, and so on.
I also added a second FB (FB21) where I index AR2 within the function to take a look at the conveyor in front of it (Conveyor 1 checks to see if Conveyor 2 is running, Conveyor 2 looks at Conveyor 3, and so forth, with the exception of Conveyor 20). So, with a little imagination you can see that you can jump all over the place without using “traditional” indirect addresses (i.e. L DBW[AR1,P#10.0]). Also, remember that you can still use absolute addresses within a function. For instance, if you always want to stop all conveyors if Conveyor 20 is faulted, then you could just look at the direct state of Conveyor 20 without using a pointer (I did this in FB20).
Unfortunately, it is not possible to use ladder 100% of the time, but this method reduces the manipulation of pointers so much that it doesn’t matter much anyway. Also, by using a shared data block as an instance data block, you will avoid the management of instance data blocks and the function calls become simpler.
Again, if you find out six months from now that you have to add some new IO or some additional run data for each conveyor, and the conveyor UDT length changes drastically, you won’t have to change your code at all. Just download the new data block and restart the PLC, and your pointer (MD50) will be adjusted automatically. One thing to keep in mind though with symbolic programming is that if you ever change the UDT, you will have to recompile the blocks. You do this from the Simatic Manager by clicking Blocks>Check Block Consistency>Compile All. And, to make your project use symbolic priority, from the Simatic Manager click Blocks>Address Priority>Symbolic.
If you decide to experiment with this (and trust me, once you get the hang of it you won’t believe how much it speeds up development), make sure you experiment with a test PLC first, so you don’t break the real machine. Indirect addressing is powerful, but it can be destructive (I still have a bad day once in a while
. )