Building an S7 Array Move Questions

DesertDog

Member
Join Date
May 2003
Location
North of Los Angeles
Posts
350
First, this is not a time sensitive matter so don't spend time on it you don't have! Otherwise I would apreciate any help.

In using Step 7 I find many usefull built in commands I'm used to missing. One such is a simple GE style Array Move. I curently have enough time I can look into expanding my library and knowlege by building one. I am still fairly green with STL and fall short when it comes to pointers and address registers.

This is what I'm trying to duplicate:
In the example below:
%R00001 is the first register in the source array
%R00050 is the first register in the destination array
%R00100 is the source index or offset, this is how far from the first value you want to start. With GE the value to point to first value in an array is 1 not zero.
%R00101 is the destination index or offset. See source rules.
The N here is a constant of 5 but can also be a register value. This is how many values to move from the source to the destination.
The LEN is the length of the arrays and must be set to the length of the longest of the two arrays.


Code:
|         _____
|%I00001 |     |
|——| |———|ARRAY|—
|        |_MOVE|
|        |_WORD|
|%R00001—|SR DS|—%R00050
|        | LEN |
|        |00016|
|%R00100—|SNX  |
|        |     |
|        |     |
|%R00101—|DNX  |
|        |     |
|        |     |
| CONST —|N    |
|  00005 |_____|
|

So far I have come up with the following that I hope will find the source & dest datablocks and open them. Here is the STL source.

Code:
FUNCTION "Array_Move_Byte" : VOID
TITLE =Move N Bytes From Source ARRAY To Dest ARRAY
VERSION : 0.1

VAR_INPUT
  SR : POINTER ; // Source Array Head Address  
  SNX : INT ; // Source Array Index  
  DS : POINTER ; // Destination Array Head Address  
  DNX : INT ; // Destination Array Index 
  N : INT ; // Number Of Items To Move
END_VAR

VAR_OUTPUT
END_VAR

VAR_TEMP
  SDB_Number : INT ; // Source Database Number
  DDB_Number : INT ; // Destination Database Number
END_VAR

BEGIN
NETWORK
TITLE =

// Read In Pointer info
      L     P##SR; // Load Source Array Pointer 
      LAR1  ; // Into Address Register #1
      
      L     P##DS; // Load Destination Array Pointer 
      LAR2  ; // Into Address Register #2

// Determine & Open The Source Data Block
      L     W [AR1,P#0.0]; // Loads the DB Number 
      T     #SDB_Number; // Transfer Data Block Number To Temp Area
      OPN   DB [#SDB_Number]; // Opens the Data Block

// Determine & Open The Destination Data Block (If not the same as Source)
      L     W [AR2,P#0.0]; // Loads the DB Number 
      T     #DDB_Number; // Transfer Data Block Number To Temp Area
      == I // Compare Data Block Numbers
      JCN   Same // Skip Open DB If Source & Dest Use The Same DB 
      OPN   DB [#DDB_Number]; // Opens the Data Block
Same, NOP

END_FUNCTION

What I know about pointers I figured out doing another project people here helped with.
I know it works but I don't understand what is going on here (Code Below) and I need this operation to build a loop that moves N Bytes of data from (SR + SNX ) to (DS + DNX ). I think if I understand the pointers and addressing I can get the rest.

Code:
// Read Bit Data into Accumulator
      L     D [AR1,P#2.0]; // Loads a DWord of Data starting with BoolIn
      LAR1  ; // Into Address Register 1  
      L     D [AR1,P#0.0];

Thanks in advance to anyone up to this.
 
Looking into the SFC20 MOVE operation it looks like the actual move could be done as easily with that as a loop, but once again I'm at a loss when it comes to structuring the pointers. Is there any good tutorials, lessons, or documentation on Step 7 pointers and addressing?
 
This is something I use to get a block of data from a DB File.
The code is rough but it works.

// In Main Routine
CALL "My Function"
ByteAddr:=DB11.DBW0
InputDBNumber :=101
OutputPointer :=P#DB11.DBX 298.0 INT 40
//***********************************************



Function "My Function"
// Save Ar1
TAR1 #T_AR1
TAR2 #T_AR2

//Make up ANY Pointer for Source
LAR1 P##T_SRC_Any //Load start address of the source Any

L B#16#10 //Enter syntax ID in the Any pointer

T LB [AR1,P#0.0]
L B#16#4 //Otherwise load area type WORD

T LB [AR1,P#1.0] //and store in the Any pointer
L 15 // Words to transfer
T LW [AR1,P#2.0]
L #InputDBNumber //Enter source DB number

T LW [AR1,P#4.0]
L P#DBX 0.0 //Load area pointer in data block

L #ByteAdr //NUMBER OF BYTE To start at
ITD
SLD 3 //Convert to pointer format
+D //Add to area pointer
T LD [AR1,P#6.0] //Enter result in ANY pointer

//Make up Pointer for DESTINATION
L P##OutputPointer
LAR1

LAR2 P##T_DEST_Any
L 5
Back: T #Count
L W [AR1,P#0.0]
T LW [AR2,P#0.0]
+AR1 P#2.0
+AR2 P#2.0
L #Count
LOOP Back

//Call Function BlockMove
CALL SFC20
SRCBLK :=#T_SRC_Any
RET_VAL:=#SFC_Ret_Val
DSTBLK :=#T_DEST_Any

*** How do you get it to format nice?? Looked good in Notepad !!
 
Last edited:
Similar to the Ladder function if you use "Code" formated text "/Code" using brakets instead of quotes it will save the formating from monospaced editors such as notepad, the STL source editor, or my favorite EditPlus .
 
Last edited:
SFC20 is the best way to copy blocks of data from one area to another. The only thing you might want to be careful about though is that it has a relatively high fixed scantime overhead, so I wouldn't use it for copying just a few bytes if scantime could be an issue (I can't remember the actual values, but the execution time is Xms + (Yms*Zbytes), so you can see it's more efficient as the data area grows larger).

Anyway, the best way to learn how to use the pointers is to just get very acquainted with the Any Pointer description in Siemens Help (look up "Format of the Parameter Type ANY", and you will find a very concise description of the various parts of a pointer). Let's step through some basic pointer code:

First, create two local variables of type ANY, and call them SourceAddress and DestinationAddress, and a function return variable called Return. For our example, we'll assume that you want to copy 2000 bytes starting at DB10.DBB16 to DB14.DBB34.


L P##SourceAddress
LAR1
L W#16#1002 //10h for S7, 02h for data type BYTE
T LW [AR1,P#0.0] //Transfer to the first part of the ANY Parameter
L 2000 //Load the number of bytes to copy
T LW [AR1,P#2.0]
L 10 //Load the Data Block number
T LW [AR1,P#4.0]
L P#16.0 //Load the address of the first byte
T LD [AR1,P#6.0]
L B#16#84 //84h is the DB memory area type
T LB [AR1,P#6.0]

L P##DestinationAddress
LAR1
L W#16#1002
T LW [AR1,P#0.0]
L 2000
T LW [AR1,P#2.0]
L 14
T LW [AR1,P#4.0]
L P#34.0
T LD [AR1,P#6.0]
L B#16#84
T LB [AR1,P#6.0]



CALL SFC 20
SRCBLK :=#SourceAddress
RET_VAL:=#Return
DSTBLK :=#DestinationAddress

That is basically how a pointer is set up for SFC20, but the way we did it is still basically hardcoded, which limits it's usefulness. But, you can easily see how the pointer can be constructed dynamically. For instance, if you were calling this function within another function that used a different DB every time, instead of this:

L 10 //Load the Data Block number
T LW [AR1,P#4.0]

you could do this:

L DBNO //Load the Data Block number
T LW [AR1,P#4.0]


Or, if you wanted to copy an entire DB but didn't want to hardcode the length, you could do this for the repetition factor:

L DBLG //Load the number of bytes to copy
T LW [AR1,P#2.0]

Go ahead and experiment a little bit, and get back to us. I use ANY pointers liberally throughout my code, and they will become second nature to you in no time.
 
Don't constrain your thinking to SFC20 only. Have a look around in the STEP7 Standard Library. There is a program folder in there entitled "TI-S7 Conversion Functions". This contain a series of FBs and FCs based on the instruction set of the TI 505-series PLCs that have been there since Siemens bought out the TI business.

For example, you can't re-parameterise SFC20 BLKMOV dynamically at run-time, but there is another IBLKMOV FC which does allow for that. There are a whole series of FCs grouped under the general heading TABLE functions. Moving Words to a Table, moving a Table register out to a Word, Table-to-Table compares, searching within Tables for equals or not equals. I'm sure one of these is much closer to the GE Array Move instruction you're looking for, rather than re-writing custom code yourself.

Regards

Ken.
 
For example, you can't re-parameterise SFC20 BLKMOV dynamically at run-time

What do you mean by that? I create the SFC20 parameters dynamically throughout my program based on runtime conditons on a regular basis.
 
I've picked apart some other items but I'm still at a loss on how they work. I've seen a couple of things (including IBLKMOV) where the DB# is picked out using code such as this:
Code:
      L     P##S_DATA                   // ACC1 := Ptr to a ptr to start location of source data
      LAR1                              // AR1 := Ptr to a ptr to start location of source data
      L     0                           // ACC1 := 0
      L     W [AR1,P#0.0]               // ACC1 := Memory reference byte 0..1 ; ACC2 := 0
      ==I                               // if (memory reference byte 0..1 = DB#)
      JZ    IF01                        // {
      T     #BLOCK_NO                   //   ACC1 := DB#
      OPN   DB [#BLOCK_NO]              //   Open DB#

I'm confused because looking at pointers I see bytes 4 & 5 hold the DB# but this appears to looks in byte 0 and other code I've seen uses:
Code:
      L     D [AR1,P#2.0]               // ACC1 := Starting address of source data
      LAR1                              // AR1 := Starting address of source data
      L     W [AR1,P#0.0]               // ACC1 := Memory reference byte 0..1 ; ACC2 := 0
      T     #BLOCK_NO                   // BLOCK_NO := DB#

This looks like it's using Byte #2 I've read what I can find in the help files but I haven't found much useful when it comes to indirect addressing.


Also Ken M
I'm sure one of these is much closer to the GE Array Move instruction you're looking for, rather than re-writing custom code yourself.
Thanks for you're help and it has been useful but I haven't found indirect indexed block moves and a major part of this project for me is to learn pointers and addressing in STL.
 
Ok, I think I see where you are confused. Without seeing the rest of the code, I'm guessing that S_DATA is parameter type POINTER. The POINTER type consists of only six bytes, vs the 10 bytes of type ANY. Essentially, the POINTER type is simply the last six bytes of the ANY type, so that is why you can point to byte 0 in the first code example you gave. Take a look at the help and look for "Format of the Parameter Type POINTER", and you'll see what I mean.

As for the second code example, I would have to see the actual parameter types. Have you verified that this code works? It almost looks like someone is doing more of the same with a pointer, but establishing AR1 based on the pointer bit address. But for it to work properly that way, I think it has to be programmed like this:

Code:
      L     W [AR1,P#0.0]               // ACC1 := Memory reference byte 0..1 ; ACC2 := 0
      T     #BLOCK_NO                   // BLOCK_NO := DB#
      L     D [AR1,P#2.0]               // ACC1 := Starting address of source data
      SLD 3
      SRD 3
      LAR1                              // AR1 := Starting address of

On the other hand, I can't see the rest of the code so I don't really know what they were trying to do. If I get to my test station later, I'll try it out.
 
The block I took that from was using pointers as inputs. If I use AnyPointers, can I then simply use plain symbolic inputs and then look at the data using the any pointer layout as they were using the pointer layout ([AR1, P#4.0] instead of [AR1, P#0.0] to find block #). Part of my confusion is figuring out how the data gets transformed from a regular symbolic input to the pointer info inside the block.
 
S7Guy,

What do you mean by that? I create the SFC20 parameters dynamically throughout my program based on runtime conditons on a regular basis.

Hmmm, yes. What did I mean by that? Damned if I know. As you say, I've done it myself, assembling two separate pointers at runtime as source and destination and then applying these to SFC20. I think we have to write off that earlier statement of mine as one of these momentary human equivalents of a mains spike or something. Just goes to show - because it's on the web doesn't mean it's true!

Sorry about any confusion.

Regards

Ken.
 
S7Guy,

What do you mean by that? I create the SFC20 parameters dynamically throughout my program based on runtime conditons on a regular basis.

Hmmm, yes. What did I mean by that? Damned if I know. As you say, I've done it myself, assembling two separate pointers at runtime as source and destination and then applying these to SFC20. I think we have to write off that earlier statement of mine as one of these momentary human equivalents of a mains spike or something. Just goes to show - because it's on the web doesn't mean it's true!

Sorry about any confusion.

Regards

Ken.
 
It's Working

I have the basic indexed array move working, but I still have a couple of questions:
1. Why do I have to treat the destination DB as an instance Data Block even though it's not? I tried otherwise and it doesn't work.
2. Is there an easy way to allow this to work with bits as well.

I plan to next look into the pointer and find the data type to automatically adjust the move for the different data sizes. I don't think that will be very difficult.

I welcome any comments or better methods. Here is the source file if anyone wants it. I'll probably put it in the download section when finished.

Code:
FUNCTION "ArrayMove" : VOID
TITLE =Move N Words From Source ARRAY To Dest ARRAY
VERSION : 0.1


VAR_INPUT
  SR : ANY ;  // Source Array Head Address  
  SNX : INT ; // Source Array Index  
  DS : ANY ;  // Destination Array Head Address  
  DNX : INT ; // Destination Array Index 
  N : INT ; // Number Of Items To Move
END_VAR
VAR_TEMP
  SDB_Number : INT ;  // Source Database Number
  DDB_Number : INT ;  // Destination Database Number
  TEMP_AR1 : DWORD ;  // Address register 1 contents   
  TEMP_AR2 : DWORD ;  // Address register 2 contents  
  Loops : INT ; //Number Of Loops Remaining
  SrcOffset : INT ; //# Bytes From Source To Start
  DstOffset : INT ; //# Bytes From Dest To Start
END_VAR
BEGIN
NETWORK
TITLE =
// Save Current Address Register Values
      TAR1  #TEMP_AR1; // Save Address register 1 contents   
      TAR2  #TEMP_AR2; // Save Address register 2 contents   

// Adjust Byte Offsets For Words
      L     2; // 2 Bytes Per Word
      L     #SNX; // Source Index # Words
      *I    ; // Multiply Words * Bytes Per Word
      T     #SrcOffset; // Source Offset In Bytes

      L     2; // 2 Bytes Per Word
      L     #DNX; // Destination Index # Words
      *I    ; // Multiply Words * Bytes Per Word
      T     #DstOffset; // Destination Offset In Bytes


// Read In Source Pointer info
      L     P##SR; // Load Source Array Pointer 
      LAR1  ; // Into Address Register #1               

// Determine & Open The Source Data Block
      L     0; 
      L     W [AR1,P#4.0]; // Loads the DB Number 
      ==I   ; // Compare DB # to Zero
      JZ    SNDB; // Jump If Source Is Not DB
      T     #SDB_Number; // Transfer Data Block Number To Temp Area
      OPN   DB [#SDB_Number]; // Opens the Data Block
SNDB: NOP   0; 

      L     D [AR1,P#6.0]; // Load Memory Area Data Of Source Pointer
      SRD   3; // Shift Off Bit Address (Word Address Remains)
      L     #SrcOffset; // { Add Byte Offset
      +D    ; //   To Address }
      SLD   3; // Shift Back Bit Address (000)
      LAR1  ; // Into Address Register 1

// Read In Destination Pointer info
      L     P##DS; // Load Destination Array Pointer 
      LAR2  ; // Into Address Register #2            

// Determine & Open The Destination Data Block (If not the same as Source)
      L     0; 
      L     W [AR2,P#4.0]; // Loads the DB Number                      
      ==I   ; // Compare Data Block Numbers  
      JZ    DNDB; // Jump If Source Is Not DB
      T     #DDB_Number; // Transfer Data Block Number To Temp Area   
      OPN   DI [#DDB_Number]; // Opens the Data Block 
      L     DW#16#5000000; // { OR Hex #5000000 With Memory Area 
      L     D [AR2,P#6.0]; //   To Force DB Into DI (Instance
      OD    ; //   Data Block) And Load }
      JU    Dst; // Jump To Load Address Reg 2
DNDB: L     D [AR2,P#6.0]; // Load Memory Area Data Of Destination Pointer  
      SRD   3; // Shift Off Bit Address (Word Address Remains)
      L     #DstOffset; // { Add Byte Offset
      +D    ; //   To Address }
      SLD   3; // Shift Back Bit Address (000)
Dst:  LAR2  ; // Load Destination Location Into Address Register 2

// Transfer Data From Source Array TO Destination Array
      L     #N; // Number Of Values To Move
L1:   T     #Loops; // Number Of Loops Remaining  
      L     W [AR1,P#0.0]; // Reads a Word of Data From Source        
      T     W [AR2,P#0.0]; // Writes A Word Of Data To Destination
      +AR1  P#2.0; // Increment Source Position
      +AR2  P#2.0; // Increment Destination Position
      L     #Loops; // Load Loop Number
      LOOP  L1; // Decrement Loops Remaining And Loop

// Restore Address Registers When Done
      LAR1  #TEMP_AR1; 
      LAR2  #TEMP_AR2; 

END_FUNCTION
 
Hey Desert Dog, may I offer an honest critique? Here's what I noticed in looking at the code:

1) Why is the offset an input parameter? This can be calculated from your ANY pointer.
2) Looking at the code that converts the Offsets to bytes (you multiply by 2), you might as well multiply by 16 instead, and then you could avoid two SRD 3 instructions below. But like I say, let's try to get rid of the Offsets entirely.
3) You might have problems with the L D[AR1, P#6.0] instruction, because byte 6 contains the data type (i.e. 84Hex). You need to filter out the first byte.
4) Where does "N", the number of words to move, come from? Is it dynamic? It's possible that you can eliminate this parameter as well.

I just took a few minutes looking at it, so maybe I missed something, but thought I'd offer some ideas.
 
I welcome any critique. Especially simplification of more effeciant code. I have years of ladder in other PLC's,. 2 years S7 Ladder but STL is a new venture.

I don't know how you would get the offset from the any pointer. I put a symbolic reference on the input of the block so I can use the function as a command without formulating a pointer. The offset is the index from the head value in the associated array. Example DB1.DBW0.0 is the first value in a recipe array. If each recipe has 5 values. I can select recipes by changing the index by 5 for each recipe leaving the source as db1.dbw0.0 or the symbolic reference for it. I would have the destination point to a current selection table with an index of 0. To save recipes it the same but the source and dest are swapped and the dest is indexed instead.

N is also an input so the value can be adapted externally for each project.

I am hoping to keep this as a library item that I just plug into my ladder and feed the appropriate values. See the GE example in the first post.

I have to look into the multiply by 16 thing, that might allow easily using this for bool arrays as well.

I didn't have any problems when I tested it but I'll check the L D[AR1, P#6.0] instruction to be sure.

The last post is a complete source file that can be compiled.
 
Last edited:

Similar Topics

Hello everyone, I am a student and would like to take the next step and learn FactoryTalk (Batch preferably) and how to create HMIs etc. Would...
Replies
4
Views
456
Hi all Trend seemed to have carved a niche in building controls. The controllers seem expensive and inflexible, in the words of a Trend user. Do...
Replies
1
Views
467
I have a 24" plotter that we print out full scale backpanels and transfer all the holes to a backpanel for faster layout and build time. Does...
Replies
11
Views
1,589
Anyone building cabinets according to Korean standards? What to think about? Do you need to use components with Korean approvals?
Replies
0
Views
828
General question for everyone. I'm finishing up some industrial electrician training in the next couple years and then plan to open up my own shop...
Replies
2
Views
1,557
Back
Top Bottom