Pointer and Memory Management

Variable:

When we declare a variable for our program, then the variable is stored in the PLC’s memory. The operating system stores the variable in the memory location. We can’t store the variable by ourselves but we can know where the variable has been stored. We declare a INT type variable and initialized it to 15. Lets assume, 15 is located at address 0x0004. The concept is explained in the following figure. Lets assume our PLC memory has memory address started from 0x0000 (just as an example). In real life the address can be for 64 bit PLC  8 bytes long.                            Download the sample.

 

nIntVar1 : INT := 15;  //OS saves nIntVar1 somewhere (in this case at 0x0004)
//nIntVar1 : INT; // it reserves the memory address but 15 has not stored yet

Figure 01: nIntVar1 variable is initialized to 15 and stored at address 0x0004

Pointer :

The pointer is itself a variable. So when we declare a pointer variable that is stored in the memory area. The following declaration declares a pointer variable whose type is INT

pInt : POINTER TO INT; //pInt just like a variable, it stores variable address	

This is shown in the following figure. The pointer has been declared but has not initialized that means it does not point anywhere yet. So for example, pInt variable is stored at address 0x0006. We know that the pointer variable is located at address 0x0006 but it does not point anywhere yet (Fig 2a).

 

Figure 02: pInt pointer variable stored at address 0x0006

The pointer can store the addresses of variables, function blocks, and programs, while an application program is running. A pointer points to one of the named objects or a variable with any data type.

We can find the address of the variable by the following statement and set the address to pInt variable

pInt := ADR(nIntVar1); //pInt is the address where it was saved

After the above assignment, pInt pointer variable points to 0x0004 not to the value 15. The concept is shown in the figure 02 2b.

 

Dereferencing a pointer means obtaining the value of the address to which the pointer points. You can dereference a pointer by appending the content operator to the pointer identifier, for example, see pInt^ in the example below.

pInt^ will be the value of a variable where it points to (in this case nIntVar1 with value 15).

We can change the value of nIntVar1 indirectly by the following way.

pInt^ := 345; //now nIntVar1 will contain 345

Let put the example as a summary. We can show pInt2 and valViaPointer in the memory address, but we have not shown. If you copy paste the code to Visual Studio then you can see the result.

VAR	
nIntVar1 : INT := 15; //Declare an integer type variable
pInt : POINTER TO INT; //Declare a pointer type variable of INT
pInt2 : POINTER TO INT; //Declare a pointer type variable of INT
valViaPointer : INT ;   //Declare an integer type variable of INT for holding data
END_VAR
//Body, lower part of the program
pInt := ADR(nIntVar1); //Now address of nIntVar1 is assigned to pInt
valViaPointer := pInt^; //Assign the content by dereferencing pInt (note the ^ operator)
pInt2 := pInt; //set the pointer variable pInt to pInt2
pInt2^:= 345; //Assign new value 345 to pInt2 and note that nIntVar1  is having value 345

Figure 03: running the code in Visual Studio and updating original variable indirectly via pointer

YouTube Video on Pointer

Pointer Arithmetic

In our previous example, we have shown the memory addresses are  0, 1, 2, etc. But in practice, those address depends on what kinds of data size we are storing. Strictly speaking, those are virtual addresses but we don’t need to care for them. We have declared an array of INT types with 3 elements. We declare 3 pointer variables which will hold the address of those 3 items.

plainInt : ARRAY[0..2] OF INT;	
plainIntAdd0 : POINTER TO INT;
plainIntAdd1 : POINTER TO INT;
plainIntAdd2 : POINTER TO INT;
//Init array with some values
plainInt[0]:= 2; // see next figure 04
plainInt[1]:= 5;
plainInt[2]:= 7;

 

Now let us assign the address to those pointers. If the value of plainIntAdd0 0x0002 then the values of plainIntAdd1 and plainIntAdd2 will be 0x0004 and 0x0006, and the offset is 2. This 2 is coming from the size of the data type which can be determined by sizeof operator.

plainIntAdd0:= ADR(plainInt[0]); //plainIntAdd0 holds the address of 1st element
plainIntAdd1:= ADR(plainInt[1]);
plainIntAdd2:= ADR(plainInt[2]); //plainIntAdd2 holds the address of 3rd element

Value of plainIntAdd2 will be  plainIntAdd0 + 2 * sizeof (INT);

plainIntAdd2:= ADR(plainInt[0])+2*SIZEOF (INT);//equivalent plainIntAdd2:=ADR(plainInt[2]);
testvar := plainIntAdd2^; //testvar will contains 3rd value of array
plainIntPointer:= plainIntAdd0 + 0 * SIZEOF (INT); //First one
//equivalent plainIntPointer:= ADR(plainInt) + 0 * SIZEOF (INT); //First one
plainIntPointer:= plainIntAdd0 + 1 * SIZEOF (INT); //Second one
plainIntPointer:= plainIntAdd0 + 2 * SIZEOF (INT); //Third one and so on

plainInt[0] is located at 0x0002 and plainInt[1] is located at 0x0004. The difference is 2 and that is SIZEOF  INT . We can know the next address by adding the size of data, in our case INT which has a size of 2 bytes. If we have a data size LREAL then it the offset will be 8 (SIZEOF (LREAL) == 8)

Figure 04: INT type data size has 2 bytes, next address = current address + size of data type

Multi Dimensional Array

In our previous example, we have shown a one-dimensional array of INT data. Now we shall show 2 dimensional data and its type is REAL

page : ARRAY[0..MaxRow]OF ARRAY[0..MaxColumn]OF REAL; 
dataReal : REAL; // can holds any element of the array VAR CONSTANT MaxRow : INT := 3; MaxColumn : INT := 5; END_VAR

This is called an array of arrays. The first index (MaxRow) is the number of rows and the second one number of columns. The size of REAL is 4 and LREAL is 8. If we declare INT type i and j, we can initialize all elements to 0.0 in the following way. Since the initialization is not done in each cycle, we can guard it by a BOOL variable.

IF NOT initDone THEN
	FOR i := 0 TO MaxRow DO
		FOR j := 0 TO MaxColumn DO
			page[i][j]:= 0.0;
		END_FOR	
	END_FOR
initDone:= TRUE; // will not execute in each cycle but the first one END_IF

If the values of pages are initialized with individual values then we can assign a unique value to each cell.

page[rowIndex][columnIndex] := some real data;

page[0][0]:= 0; //assign 0 to first row, first column
page[3][0]:= 30;
//page[row_value][column_value] row first then column
page[0][1]:= 1;
page[3][1]:= 31;//assign 31 to 4th row, second column

We can assign each row to a pointer variable and manipulate those as needed. In the following snippet, we can manipulate each row one by one with a single pointer or different pointer variables.

pageFirstRow		    : POINTER TO ARRAY[0..MaxColumn] OF REAL;
pageSecondRow		    : POINTER TO ARRAY[0..MaxColumn] OF REAL;
pageThirdRow		    : POINTER TO ARRAY[0..MaxColumn] OF REAL;

Figure 05: Multidimensional array initialization with specific value for the table name page (in our excercise)

The following page shows the memory addresses of the above-mentioned variables. We can pass the variable for example pageFirstRow to other functions and make changes which will be reflected to the caller. It reduces the number of assignments and is a bit faster as we directly manipulate memory location.

Figure 06: Actual 8 bytes address (64 bit CPU)is shown from real PLC

YouTube Video on pointer arithmetic and array

Page Fault and Pointer

Page fault issues in PLC are related to pointer manipulation. If we misuse the pointer then OS kills the process and we see a note on the PLC desktop as shown in the following image. We shall show here an example and how to fix it. We can make similar problems while using a pointer. It can be hard to debug this type of issue sometimes.

Figure 06: Page fault reasons

If we see the above problems in our application, we follow the following steps

=> Close the dialog on the PLC Desktop

=> Change the TwinCAT icon to config mode

=> Fix the bug until we are able to run the PLC in run mode

Now we describe our application which leads to this situation:

We want to read motor speed by passing a variable to a function block. The speed will be measured in the function block and will be used in the caller program. Here are the sample function blocks.

 

//Header of the function block
FUNCTION_BLOCK MOTORBLOCK
VAR_INPUT
inputPointer :POINTER TO REAL;
END_VAR
VAR_OUTPUT
END_VAR
VAR
initDone : BOOL:= FALSE; 
realMotorSpeed : REAL;
END_VAR
//Body of the function block
IF NOT initDone THEN
inputPointer := ADR(realMotorSpeed);
realMotorSpeed := 123.04; // default value
initDone:= TRUE;
END_IF
inputPointer^:=realMotorSpeed; //new value is set after reading from the HW (not shown)

In the caller program, we have the following code. We see from the function block header file, it will take a pointer to type REAL and it will set the measured speed to the pointer. Now we try to access the speed in the following way. When we start the program we get a page fault, what causes this? InputPointer is not initialized! We must initialize the pointer before using it and we need to check that the pointer is not NULL.

 

VAR	
motorSpeed :  POINTER TO REAL;
fnMOTORBLOCK : MOTORBLOCK;
END_VAR
//Call the function block by passing the pointer, compile and links fine
fnMOTORBLOCK(inputPointer := motorSpeed);

Solution:

//We declare a variable and its address is passed to the input of the function block

Option 1:

motorSpeed :  REAL;
//motorSpeed :  POINTER TO REAL; // uncomment this and comment previous line to produce page fault

//We pass the address of motorSpeed not the NULL pointer

fnMOTORBLOCK(inputPointer := ADR(motorSpeed));
//fnMOTORBLOCK(inputPointer := motorSpeed); // uncomment this and comment previous line to produce page fault

//We check if the pointer is NULL before using it.

IF inputPointer <> NULL THEN
inputPointer^:=realMotorSpeed; //new value is set after reading from the HW (not shown)
END_IF

 

Option 2:

Declare an output pointer in the function block, set the pointer with local variable and return it to caller

VAR_OUTPUT
outputPointer :POINTER TO REAL;
END_VAR

IF NOT initDone THEN
outputPointer := ADR(realMotorSpeed);
realMotorSpeed := 123.04; // default value
initDone:= TRUE;
END_IF

motorSpeedOut:= fnMOTORBLOCK.outputPointer; // dereference motorSpeedOut to get the motor speed

We can download the sample from the above links and try to reproduce it. 

YouTube Video on page fault and how to avoid it

We have another concept that is REFERENCES. A reference points implicitly to another object. The reference is implicitly deferenced during the access and therefore requires no special content operator ^ like a pointer.

//declare variables

refInt  : REFERENCE TO INT;
 nA      : INT := 200;

 

//Set the references, after that, if you change one variable then the other will have the same value

refInt REF= nA;         // refInt points now to nA

Thank you for reading this far. If it is useful why not share it?

Memcpy

Library contains a number of functions which provide direct access to memory areas in the PLC runtime system of the PLC Controller. Default editor includes the library by default.

 

The fact that these functions allow direct access to the physical memory of the PLC. Misuse of such functions can result in a system crash, or in access to forbidden memory areas. So we need to be careful when we are using these function.

 

MEMCMP Compares the values of variables in two memory areas

 

MEMCPY Copies the values of variables from one memory area to another

 

MEMSET Sets the variables in a memory area to a particular value

Memcpy example

//Declaration

PROGRAM MAIN

VAR

org : DINT :=12;

dest : DINT :=13;

another : DINT := 14;

myTimer : TON;

startTimer : BOOL := FALSE;

END_VAR

//Body

 

myTimer(IN:= startTimer, PT:=T#1S);

 

MEMCPY(ADR(dest), ADR(org),4);

MEMCPY(ADR(another), ADR(dest),4);

 

IF myTimer.Q = TRUE THEN

myTimer(IN:= FALSE);

org:= org +1;

END_IF

 

Download the sample from the link given above.

Next, let’s try to understand persistent data https://www.hemelix.com/plc/structured-text-function-block/

Ask questions related to Hemelix sample code and design at Google group https://groups.google.com/g/hemelix