I need to serialize the data from many smaller Data Blocks and working memory via ANY
pointer to the single long DB for on-PLC filtering and transferring. Now I use a routine utilizing the special dedicated DB called DATASET_REGISTRAR
to organize the data into array-like structures, and then the second DB called DATASET_LIST
to grab and write them into the proper place over BLKMOV
system routine. Here is a commented sample of the code I use to do this (the code is real, taken from the testing station PLC).
FUNCTION "NET_TX_SERIALIZER" : WORD
TITLE =Serialize data to store them in the specified data block
//Dynamic spec: DATASET_REGISTRAR stores standardized structures defining so
//called "datasets" to collect the data from DBs or peripheral memory, then the
//data will be mapped over the area in the output DB. The dataset_list DB stores
//the sequence of datasets which will be mapped. The structure of the output DB
//should match tiled datasets as they are defined in DATASET_REGISTRAR by total
//length.
VERSION : 0.1
VAR_INPUT
dataset_list : BLOCK_DB ; // List of the datasets for serialization: homogeneous sequence of WORD elements containing dataset IDs
serialized_data : BLOCK_DB ; // Output DB: The ID is determined automatically, and it should be system-wide writable
END_VAR
VAR_TEMP
dataset_index : WORD ; // Index of the current dataset
general_offset : WORD ; // Offset in DATASET_REGISTRAR to read a spec for the current dataset
dataset_loop_iter : WORD ; // Loop counter
dataset_id_pointer : DWORD ; // Pointer to read actual dataset ID
dataset_record : STRUCT // Local copy of the spec stored in DATASET_REGISTRAR
dataset_id : WORD ;
datablock_id : WORD ;
type_id : WORD ;
start_offset : DWORD ;
dataset_length : DWORD ;
end_offset : DWORD ;
END_STRUCT ;
dataset_registrar_dbid : WORD ; // DATASET_REGISTRAR DB ID
ret_error_status : INT ; // Error code for BLKMOV
dataset_pointer : ANY ; // Pointer to the actual data stored within the dataset addressed by the spec
serialized_pointer : ANY ; // Pointer to the currently written tile in the output DB
serialized_offset : DWORD ; // Current position in the output DB
serialized_dbid : WORD ; // Output DB ID
prev_ar1 : DWORD ;
prev_ar2 : DWORD ;
actual_dataset_length : WORD ; // Actual length of the current dataset, in BYTES
dataset_spec_pointer : ANY ; // Pointer to read the spec for the current dataset
END_VAR
BEGIN
NETWORK
TITLE =Initialization
TAR1 #prev_ar1; // Preserving address registers
TAR2 #prev_ar2;
L 0; // Initializing index for loop over all specified datasets
T #dataset_index;
L 0; // Initializing index for serializer's tile counter with wariable dataset length
T #serialized_offset;
OPN "DATASET_REGISTRAR"; // Activating registrar to gather dataset specs
L DBNO;
T #dataset_registrar_dbid;
OPN #serialized_data; // Retrieving number of serializer output DB
L DBNO;
T #serialized_dbid;
NETWORK
TITLE =Loop over dataset list
OPN #dataset_list; // Open list DB
L DBLG; // Load length
L 2;
/I ;
iter: T #dataset_loop_iter; // Start loop
L #dataset_index; // Indexing incrementally
L 2; // Size factor is 2
*I ;
SLD 3; // Formulating pointer
L P#0.0;
+D ; // Commutative addition
T #dataset_id_pointer;
L DBW [#dataset_id_pointer]; // ACCU1 - current ID to extract dataset structure from DATASET_REGISTRAR
L 18; // Dataset description structure length is 18 bytes
*D ;
T #general_offset; // Offset in DATASET_REGISTRAR
LAR1 P##dataset_spec_pointer; // Constructing ANY pointer to static registrar DB for spec
L B#16#10; // 0x10 - SIEMENS ANY POINTER HEADER OPCODE
T LB [AR1,P#0.0];
L B#16#2; // BYTE
T LB [AR1,P#1.0];
L 18; // 18 bytes of spec structure
T LW [AR1,P#2.0];
L #dataset_registrar_dbid; // Load from dynamic DATASET_REGISTRAR by ID
T LW [AR1,P#4.0];
L P#DBX 0.0; // Formulating internal pointer
L #general_offset;
SLD 3;
+D ;
T LD [AR1,P#6.0]; // AR1 - current pointer to the registrar record containing the dataset spec structure
CALL "BLKMOV" ( // Copying spec to the internal record
SRCBLK := #dataset_spec_pointer,
RET_VAL := #ret_error_status,
DSTBLK := #dataset_record);
LAR1 P##dataset_pointer; // Constructing ANY pointer to the actual data
L B#16#10; // 0x10 - SIEMENS ANY POINTER HEADER OPCODE
T LB [AR1,P#0.0];
L B#16#2; // Copying byte-wise
T LB [AR1,P#1.0];
L #dataset_record.end_offset;
L #dataset_record.start_offset;
-D ; // ACCU1 - actual number of aligned bytes to copy
T #actual_dataset_length;
T LW [AR1,P#2.0];
L 0;
L #dataset_record.datablock_id; // 0 as DB ID leads to copying from the peripheral memory
==I ;
JC mcp;
T LW [AR1,P#4.0]; // Load from the specified DB by ID
L P#DBX 0.0;
JU ofst; // Switch unconditionally to offset calculation
mcp: L P#M 0.0; // Bit Memory area access
T LW [AR1,P#4.0];
ofst: L #dataset_record.start_offset; // Read from specified area by offset
SLD 3;
+D ;
T LD [AR1,P#6.0];
L P##serialized_pointer; // Constructing ANY pointer for the serialized DB memory area
LAR1 ;
L B#16#10; // 0x10 - SIEMENS ANY POINTER HEADER OPCODE
T LB [AR1,P#0.0];
L B#16#2; // Copying byte-wise
T LB [AR1,P#1.0];
L #actual_dataset_length; // Actual length, previously calculated
T LW [AR1,P#2.0];
L #serialized_dbid; // Serialized data storage enclosure ID, read from initial test in NW 1
T LW [AR1,P#4.0];
L P#DBX 0.0;
L #serialized_offset; // Tiling offset
SLD 3;
+D ;
T LD [AR1,P#6.0];
CALL "BLKMOV" ( // Copying actual data from the source area to the serialized DB
SRCBLK := #dataset_pointer,
RET_VAL := #ret_error_status,
DSTBLK := #serialized_pointer);
L #serialized_offset; // Offset increment for the output area
L #actual_dataset_length;
+D ;
T #serialized_offset;
L #dataset_index; // Looping over the next dataset (the same one in the example)
L 1;
+I ;
T #dataset_index;
L #dataset_loop_iter; // Closing loop
LOOP iter;
NETWORK
TITLE =Restoring address registers
LAR1 #prev_ar1;
LAR2 #prev_ar2;
END_FUNCTION
Could you please share your best practice tips and experience of doing exactly this on the PLCs? Is there an easier example to implement and debug?