PLC String Handling in Structured Text: Parsing, Formatting & Communication

Learn to manipulate strings in Structured Text — from basic concatenation to barcode parsing, HMI message formatting, and serial protocol handling.

What a PLC does with strings

A modern PLC does more than control motors and valves. It parses barcode data, formats HMI display messages, talks to serial devices, builds log entries and exchanges data with MES/SCADA systems. Every one of those jobs is string handling, and it has to cope with fields that are missing, too long or badly formed.

String basics in Structured Text

Declare every string with a maximum length in brackets. LEN returns the number of characters actually stored, not the declared size:

VAR
    // Fixed-length strings
    ProductName : STRING[40] := 'Widget-A';
    Message : STRING[80] := '';
    
    // String length
    NameLen : INT;
END_VAR

NameLen := LEN(ProductName);  // Returns 8

The IEC 61131-3 string functions

Positions count from 1, and FIND returns 0 when the substring is not there:

FunctionDescriptionExample
LEN(s)Length of stringLEN('Hello') → 5
LEFT(s, n)First n charactersLEFT('Hello', 3) → 'Hel'
RIGHT(s, n)Last n charactersRIGHT('Hello', 3) → 'llo'
MID(s, n, p)n chars from position pMID('Hello', 2, 3) → 'll'
CONCAT(s1, s2)Join two stringsCONCAT('Hi', ' World') → 'Hi World'
FIND(s1, s2)Find s2 in s1 (0=not found)FIND('Hello', 'llo') → 3
INSERT(s1, s2, p)Insert s2 into s1 at pINSERT('Helo', 'l', 3) → 'Hello'
DELETE(s, n, p)Delete n chars from pDELETE('Hello', 2, 2) → 'Hlo'
REPLACE(s1, s2, n, p)Replace n chars at p with s2REPLACE('Hello', 'a', 1, 5) → 'Hella'

Building an HMI status line

Convert each number to a string first, then join the pieces with CONCAT. The target string has to be long enough for the longest message you expect:

PROGRAM HMIMessages
VAR
    Temperature : REAL := 72.5;
    BatchCount : INT := 1547;
    MachineName : STRING[20] := 'Press-01';
    StatusLine : STRING[80] := '';
    TempStr : STRING[10];
    CountStr : STRING[10];
END_VAR

// Convert numbers to strings
TempStr := REAL_TO_STRING(Temperature);
CountStr := INT_TO_STRING(BatchCount);

// Build display message
StatusLine := CONCAT(MachineName, ' | Temp: ');
StatusLine := CONCAT(StatusLine, TempStr);
StatusLine := CONCAT(StatusLine, '°C | Count: ');
StatusLine := CONCAT(StatusLine, CountStr);
// Result: "Press-01 | Temp: 72.5°C | Count: 1547"

Parsing a delimited barcode

Barcodes often carry several fields separated by delimiters. The parser below finds each field's tag with FIND, copies everything after the tag with MID, then cuts the copy at the next ;. The last field has no trailing ;, so the quantity branch falls back to the whole remainder:

PROGRAM BarcodeParser
VAR
    RawBarcode : STRING[80] := 'PN:12345;LOT:A2024-001;QTY:50;EXP:2026-12';
    
    PartNumber : STRING[20] := '';
    LotCode : STRING[20] := '';
    Quantity : INT := 0;
    ExpiryDate : STRING[20] := '';
    
    SearchPos : INT;
    FieldStart : INT;
    FieldEnd : INT;
    TempStr : STRING[80];
    QtyStr : STRING[10];
END_VAR

// Extract Part Number (after "PN:")
SearchPos := FIND(RawBarcode, 'PN:');
IF SearchPos > 0 THEN
    TempStr := MID(RawBarcode, 80, SearchPos + 3);
    FieldEnd := FIND(TempStr, ';');
    IF FieldEnd > 0 THEN
        PartNumber := LEFT(TempStr, FieldEnd - 1);
    END_IF;
END_IF;

// Extract Lot Code (after "LOT:")
SearchPos := FIND(RawBarcode, 'LOT:');
IF SearchPos > 0 THEN
    TempStr := MID(RawBarcode, 80, SearchPos + 4);
    FieldEnd := FIND(TempStr, ';');
    IF FieldEnd > 0 THEN
        LotCode := LEFT(TempStr, FieldEnd - 1);
    END_IF;
END_IF;

// Extract Quantity (after "QTY:")
SearchPos := FIND(RawBarcode, 'QTY:');
IF SearchPos > 0 THEN
    TempStr := MID(RawBarcode, 80, SearchPos + 4);
    FieldEnd := FIND(TempStr, ';');
    IF FieldEnd > 0 THEN
        QtyStr := LEFT(TempStr, FieldEnd - 1);
    ELSE
        QtyStr := TempStr;
    END_IF;
    Quantity := STRING_TO_INT(QtyStr);
END_IF;

Building an ASCII serial message

Many industrial devices use ASCII serial protocols framed by the STX and ETX control characters, which you write in ST as $02 and $03. This example builds the frame from the address, command and register, then appends a simple XOR checksum:

PROGRAM SerialProtocol
VAR
    DeviceAddr : INT := 1;
    Command : STRING[10] := 'RD';
    Register : INT := 4000;
    
    TxMessage : STRING[80] := '';
    Checksum : INT := 0;
    i : INT;
    CharCode : INT;
    AddrStr : STRING[5];
    RegStr : STRING[10];
END_VAR

// Build message: STX + Address + Command + Register + ETX + Checksum
AddrStr := INT_TO_STRING(DeviceAddr);
RegStr := INT_TO_STRING(Register);

TxMessage := '$02';  // STX character
TxMessage := CONCAT(TxMessage, AddrStr);
TxMessage := CONCAT(TxMessage, Command);
TxMessage := CONCAT(TxMessage, RegStr);
TxMessage := CONCAT(TxMessage, '$03');  // ETX character

// Calculate simple checksum (XOR of all bytes)
Checksum := 0;
FOR i := 1 TO LEN(TxMessage) DO
    // XOR each character's ASCII value
    Checksum := Checksum XOR ORD(MID(TxMessage, 1, i));
END_FOR;

TxMessage := CONCAT(TxMessage, INT_TO_STRING(Checksum));

Building a CSV log line

A log line is the same pattern again: append each field with a comma between, in a fixed column order that whatever reads the file expects:

PROGRAM CSVLogger
VAR
    Timestamp : STRING[20] := '2026-03-04 14:30:00';
    EventCode : INT := 101;
    EventDesc : STRING[40] := 'Batch Complete';
    Value1 : REAL := 98.7;
    Value2 : REAL := 45.2;
    
    CSVLine : STRING[200] := '';
END_VAR

// Build CSV: Timestamp,Code,Description,Value1,Value2
CSVLine := Timestamp;
CSVLine := CONCAT(CSVLine, ',');
CSVLine := CONCAT(CSVLine, INT_TO_STRING(EventCode));
CSVLine := CONCAT(CSVLine, ',');
CSVLine := CONCAT(CSVLine, EventDesc);
CSVLine := CONCAT(CSVLine, ',');
CSVLine := CONCAT(CSVLine, REAL_TO_STRING(Value1));
CSVLine := CONCAT(CSVLine, ',');
CSVLine := CONCAT(CSVLine, REAL_TO_STRING(Value2));
// Result: "2026-03-04 14:30:00,101,Batch Complete,98.7,45.2"

Validating operator input

Before you use text typed in at the HMI, check its length, reject forbidden characters (here ; and the apostrophe, written $27) and confirm the expected prefix:

PROGRAM InputValidation
VAR
    UserInput : STRING[40] := '';
    IsValid : BOOL := FALSE;
    ErrorMsg : STRING[80] := '';
    InputLen : INT;
END_VAR

InputLen := LEN(UserInput);

// Check minimum length
IF InputLen < 3 THEN
    IsValid := FALSE;
    ErrorMsg := 'Input too short (min 3 characters)';

// Check for forbidden characters
ELSIF FIND(UserInput, ';') > 0 OR FIND(UserInput, '$27') > 0 THEN
    IsValid := FALSE;
    ErrorMsg := 'Invalid characters detected';

// Check prefix
ELSIF LEFT(UserInput, 2) <> 'PN' THEN
    IsValid := FALSE;
    ErrorMsg := 'Must start with PN prefix';

ELSE
    IsValid := TRUE;
    ErrorMsg := '';
END_IF;

Rules for PLC string handling

  • Always declare the string length. Write STRING[80], not just STRING, so you control memory usage.
  • Check lengths before you operate on strings, so a concatenation cannot overflow the buffer.
  • Use FIND before MID, to confirm the delimiter exists before you extract a substring.
  • Handle empty strings by checking LEN(s) > 0 before processing.
  • Keep strings out of fast tasks. String operations are slow, so put them in slower cyclic tasks.
  • Test the edge cases: empty input, maximum length and missing delimiters.
  • You can practice string parsing in the online ST editor, and the barcode parsing lesson walks through a real example.