Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Direct and Hardware Access

Two things use the % sign: reading a part of a value, and binding a variable to a hardware address.

Direct access on a value

%<size><position> after a value reads a part of it. The standard allows this for bit string types, and this compiler allows it for every integer type as well.

LetterReadsExample
X1 bit%X1
B8 bit%B1
W16 bit%W1
D32 bit%D1

The position counts parts of that size, from 0, and it must fit the value. An LWORD has the bits 0 to 63 and the words 0 to 3; a position above that is rejected. For a bit, the %X can be left out.

FUNCTION main: DINT
    VAR
        variable: LWORD;
        bitTarget: BOOL;
        byteTarget: BYTE;
        wordTarget: WORD;
        dwordTarget: DWORD;
    END_VAR

    variable    := 16#AB_CD_EF_12_34_56_78_90;
    bitTarget   := variable.%X63;   (* the last bit *)
    byteTarget  := variable.%B7;    (* the last byte, 16#AB *)
    wordTarget  := variable.%W3;    (* the last word, 16#ABCD *)
    dwordTarget := variable.%D1;    (* the last double word, 16#ABCDEF12 *)

    bitTarget   := variable.%D1.%W1.%B1.%X1;   (* accesses can be chained *)
END_FUNCTION

The chained access reads 16#ABCDEF12, then 16#ABCD, then 16#AB, then bit 1 of it, which is TRUE.

The position can also be a variable. The standard allows a literal only; this is an extension.

access_var := 63;
bitTarget  := variable.%Xaccess_var;

A variable position needs the %X, because the short form without it is not allowed there. The variable must be a plain name, not a qualified one.

Hardware addresses

The second use of % is an address. AT %<area><size><position> binds a variable to a fixed address.

AreaMeaning
IInput
QOutput
MMemory
GGlobal

The size letter is the same as above, with L for 64 bit in addition. The position has one or more parts, separated by .:

VAR_GLOBAL
    inBit   AT %IX1.0: BOOL;
    outWord AT %QW2.5: WORD;
    memory  AT %MD3: DWORD;
END_VAR

AT also binds a variable to another variable, which makes it an alias:

VAR
    alias AT shared: STRING;
END_VAR

alias gets no storage of its own. It points at shared, so a write to alias writes to shared. It behaves like a reference that is bound at its declaration.

Addresses for instances

A function block that is instantiated more than once cannot name a fixed address in its declaration, because every instance needs its own. The declaration writes a template with * instead, and a VAR_CONFIG block gives the address of each instance:

FUNCTION_BLOCK Sensor
    VAR
        raw AT %I*: INT;
    END_VAR
END_FUNCTION_BLOCK

PROGRAM Cycle
    VAR
        s1: Sensor;
        s2: Sensor;
    END_VAR
END_PROGRAM

VAR_CONFIG
    Cycle.s1.raw AT %IW1.2: INT;
    Cycle.s2.raw AT %IW1.3: INT;
END_VAR

The path in VAR_CONFIG names the member from the outside: where the instance is, then the instance, then the member. Here the instance is in the program Cycle; a global instance is written without a program in front. The type must be the type of the member. Each template variable needs one entry, and the compiler rejects a template variable that has no entry as well as one that has two.

A build can write the list of all bound variables as a file, with --hwmap-file=<file>. Each entry gives the name in the source, the address, and the name of the symbol that holds the storage. A tool that reads the symbols of the binary, for example a live monitor in an IDE, needs that last name to find the value. The command line reference describes the option.

What’s next

The last chapter here is about code that is not text at all: graphical programs.