Intermediate
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:
| Function | Description | Example |
|---|---|---|
| LEN(s) | Length of string | LEN('Hello') → 5 |
| LEFT(s, n) | First n characters | LEFT('Hello', 3) → 'Hel' |
| RIGHT(s, n) | Last n characters | RIGHT('Hello', 3) → 'llo' |
| MID(s, n, p) | n chars from position p | MID('Hello', 2, 3) → 'll' |
| CONCAT(s1, s2) | Join two strings | CONCAT('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 p | INSERT('Helo', 'l', 3) → 'Hello' |
| DELETE(s, n, p) | Delete n chars from p | DELETE('Hello', 2, 2) → 'Hlo' |
| REPLACE(s1, s2, n, p) | Replace n chars at p with s2 | REPLACE('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
STRING[80], not just STRING, so you control memory usage.LEN(s) > 0 before processing.You can practice string parsing in the online ST editor, and the barcode parsing lesson walks through a real example.