Overview
Field devices such as barcode scanners, weigh scales, RFID readers, and legacy serial instruments commonly transmit numeric data as ASCII characters. A scanner that reads 12597 does not send the integer 12597 to the S7 CPU; it sends the byte sequence 0x31 0x32 0x35 0x39 0x37, which is the ASCII representation of the digits 1, 2, 5, 9, 7. A load cell transmitting a net weight of 15 kg transmits the bytes 0x31 0x35, not the binary word W#16#000F.
Inside a Siemens SIMATIC S7-300 or S7-400 CPU programmed in STL (Statement List), the engineer must translate the incoming byte stream from a CHAR/ASCII domain to a usable integer (INT or DINT) domain before the value can participate in arithmetic, comparison, scaling, or HMI display logic. This reference covers three field-proven techniques: arithmetic byte-to-digit conversion, IEC 61131-3 STRING library functions, and direct HEX value reconstruction when the upstream device is known to emit binary data labelled as "ASCII".
Prerequisites
- STEP 7 V5.5 SP4 or later (HF1 recommended) installed on a Windows engineering station. Reference: STEP 7 V5.5 SP4 release notes.
- S7-300 (CPU 31x) or S7-400 (CPU 41x) target with firmware supporting indirect memory access via AR1/AR2 (all standard CPUs from CPU 313 onward qualify).
- Configured hardware interrupt or cyclic OB1 logic to capture the incoming byte stream from the field device.
- Symbolic tag table entries for input bytes (e.g.,
IB 0..IB 7) and the working variables (MW 100..MW 200,DB 100data block). - Standard library blocks
FC 5 (STRING_TO_INT),FC 16 (I_STRNG),FC 38 (STRING_R), or the IEC 61131-3 STRING conversion routines from the "Standard Library → IEC Function Blocks" catalog.
S7 Memory Layout and CHAR Addressing
The SIMATIC memory model places process data in the Process Image Input (PII), output data in the Process Image Output (PIO), and working memory in the Bit Memory (M) and Data Block (DB) areas. The smallest addressable unit is the byte (MB); a word (MW) always contains two bytes with little-endian byte ordering on every S7 CPU.
| Address | Width | Range | Typical Use |
|---|---|---|---|
| MB n | 1 byte | 0..255 (INT 0..255) | Single ASCII character |
| MW n | 2 bytes (little-endian) | -32768..+32767 | Two concatenated ASCII chars or one 16-bit INT |
| MD n | 4 bytes (little-endian) | -2147483648..+2147483647 | Four ASCII chars or one 32-bit DINT |
| IB n / IW n | PII byte/word | Same as MB/MW | Read from physical input module |
| DBB / DBW / DBD | Data block bytes/words/double words | Same ranges | Persistent application data |
Important endian behavior: if the field device writes the digit '5' at IB 0 and the digit '1' at IB 1, reading the word IW 0 will yield 0x3135 on an S7 CPU. The high byte holds the lower input offset, and the low byte holds the higher input offset. This is the standard S7 "little-endian word" layout, documented in the S7-300 CPU 31x/31xC manual.
IW 123 as a "physical address." In STEP 7 V5.x, IW 123 is the Process Image Input word located at slot 123 of the PII; it is refreshed by OB1 at the end of each scan. To read directly from the backplane without PII update, use PIW 123 (peripheral input word). Mixing PI and I addresses is one of the most common sources of stale-data bugs.ASCII Code Reference Table
Every numeric ASCII character can be reduced to a digit (0..9) by subtracting the constant offset 0x30 (decimal 48). Letters A..F require subtracting 0x37 (decimal 55) for HEX conversion. The constant 48 is the same for every modern ASCII-compliant device; some Profibus gateways may use the older 0x30 rule, so verify with the device manual first.
| ASCII Char | Hex | Dec | Digit Value | Conversion |
|---|---|---|---|---|
| '0' | 0x30 | 48 | 0 | ch - 48 |
| '1' | 0x31 | 49 | 1 | ch - 48 |
| '2' | 0x32 | 50 | 2 | ch - 48 |
| '3' | 0x33 | 51 | 3 | ch - 48 |
| '4' | 0x34 | 52 | 4 | ch - 48 |
| '5' | 0x35 | 53 | 5 | ch - 48 |
| '6' | 0x36 | 54 | 6 | ch - 48 |
| '7' | 0x37 | 55 | 7 | ch - 48 |
| '8' | 0x38 | 56 | 8 | ch - 48 |
| '9' | 0x39 | 57 | 9 | ch - 48 |
| 'A' | 0x41 | 65 | 10 (hex) | ch - 55 |
| 'F' | 0x46 | 70 | 15 (hex) | ch - 55 |
Method 1 — Direct Arithmetic Conversion in STL
When the field device emits a fixed-length numeric string of one to four digits and the application does not require generic string manipulation, the fastest path is direct arithmetic. The example below converts a 4-character string "1259" arriving in MB 100..MB 103 to a usable integer in MW 200.
// FB 100 — Convert 4 ASCII digits at MB100..MB103 to INT in MW200
// Triggered by M 0.0 (one-shot from receive-complete flag)
NETWORK 1 Decode each digit and accumulate
L MB 100 // Load digit '1' (0x31 = 49)
L 48 // ASCII offset for '0'
-I // 49 - 48 = 1
T MW 200 // MW200 = 1 (working word)
L 10
*I // MW200 = 10
L MB 101 // Load digit '2' (0x32 = 50)
L 48
-I // 50 - 48 = 2
+I // 10 + 2 = 12
T MW 200 // MW200 = 12
L 10
*I // MW200 = 120
L MB 102 // Load digit '5' (0x35 = 53)
L 48
-I // 53 - 48 = 5
+I // 120 + 5 = 125
T MW 200 // MW200 = 125
L 10
*I // MW200 = 1250
L MB 103 // Load digit '9' (0x39 = 57)
L 48
-I // 57 - 48 = 9
+I // 1250 + 9 = 1259
T MW 200 // MW200 = 1259 (final INT value)
This is the most compact, deterministic implementation. The CPU accumulator holds the partial product between network segments; the same logic scales to five or six digits by extending the pattern. For a 4-digit input the worst-case value 9999 fits comfortably inside INT (-32768..32767). If the field device can emit values up to 99999, switch the accumulator to DINT and use ITD, *D, +D, and the final store target as MD 200.
Method 2 — STRING Library Functions (IEC 61131-3)
When the field device transmits variable-length strings terminated by 0x0A (LF) or 0x0D 0x0A (CR/LF) — common for weigh scales and laboratory instruments — the IEC 61131-3 STRING type and the STRING_TO_INT / STRING_TO_REAL functions provide a more maintainable solution. The conversion requires the following structure:
// DB 50 — Buffer and STRING workspace
DB_VAR
ASCII_BUF : ARRAY[0..31] OF BYTE; // Raw receive buffer
RAW_LEN : INT; // Valid byte count
NUM_STR : STRING[8]; // Convertible STRING
INT_VAL : INT; // Converted integer
OK_FLAG : BOOL; // Conversion success
END_VAR
// FB 110 — Build STRING and convert
NETWORK 1 Copy receive buffer into STRING data area
CALL FC 5 // STRING_TO_INT
S := DB50.NUM_STR
RET_VAL := DB50.INT_VAL // Result in INT
OK := DB50.OK_FLAG // TRUE if parseable
NETWORK 2 Test the OK flag before use
A DB50.OK_FLAG
JC OK1
L 0
T DB50.INT_VAL // Default to 0 on error
OK1: NOP 0
The STRING type in STEP 7 V5.x is defined as a 1-byte maximum-length header, a 1-byte current-length header, then the character data. For example, STRING[8] consumes 10 bytes total (2 header + 8 data). FC 5 returns a 16-bit INT; for floating point, use FC 30 (STRING_TO_REAL). Reference: S7-300/400 Standard and System Functions reference manual.
Method 3 — Hex Format Reconstruction
Some Profibus or Modbus gateways present binary values pre-formatted as ASCII HEX. The upstream device might send the byte 0x0F as the two-character string "0F" (bytes 0x30 0x46). The conversion differs from decimal conversion by using the 0x37 offset and a 4-bit shift:
// FB 120 — Convert 2 ASCII HEX digits to one byte in MB 200
NETWORK 1 High nibble
L MB 100 // '0' = 0x30 = 48
L 48
-I // 48 - 48 = 0
L 16
*I // 0 * 16 = 0
T MW 200 // Save partial
NETWORK 2 Low nibble
L MB 101 // 'F' = 0x46 = 70
L 55
-I // 70 - 55 = 15
L MW 200
+I // 0 + 15 = 15
T MB 200 // MB200 = 15 (0x0F)
For uppercase letters only, the offset is 55. If the device emits lowercase hex ("0f"), use offset 87 (since 'a' = 0x61 = 97, and 97 - 10 = 87). Always document the expected case in the device P&ID or data sheet.
Multi-Byte Stream Assembly with Loop and AR1
For strings up to 32 characters, a pointer-based loop scales better than copy-pasted networks. The example below copies a variable number of bytes from P#DB50.DBX0.0 BYTE 32 into the data area of DB50.NUM_STR using AR1:
// FB 130 — Generic ASCII buffer to STRING conversion
NETWORK 1 Set up the source pointer
L P#DB50.ASCII_BUF // Source: any-pointer format
LAR1 // AR1 -> first ASCII byte
L DB50.RAW_LEN // Loop counter
L 1
-I
T MW 250 // MW250 = RAW_LEN - 1 (zero-based)
NETWORK 2 Set up the destination
L P#DB50.NUM_STR // Destination pointer
LAR2 // AR2 -> STRING header
L DB50.RAW_LEN
T DB50.NUM_STR.CUR_LEN // Write current length
NETWORK 3 Copy loop
LOOP: NOP 0
L MB [AR1,P#0.0] // Load source byte (indirect)
T MB [AR2,P#2.0] // Store at STRING data area (skip 2 header bytes)
+AR1 P#1.0 // Advance source pointer
+AR2 P#1.0 // Advance destination pointer
L MW 250
LOOP LOOP // Decrement and jump if not zero
NOP 0
Indirect addressing via AR1 and AR2 is supported on all S7-300 CPUs from CPU 314 onward, and on every S7-400 CPU. For S7-1200/S7-1500, the equivalent is the PEEK_BLOB/PEEK function from the "Extended instructions" catalog, or symbolic slice access. Reference: S7-300 CPU 31x/31xC Manual, section 4.7 "Indirect addressing".
Compare Instructions for CHAR in STL
The CPU accumulator is 32 bits wide. A CHAR value loaded into ACCU1 occupies only the low byte; the upper three bytes are zero-filled. STL compare instructions (>I, <I, ==I, !=I, >=I, <=I) all operate on the full 16-bit INT, so the upper byte must be zero. Because L MB n only modifies the low byte, the comparison is safe:
NETWORK 1 Test if received character is a digit '0'..'9'
L MB 100 // Load ASCII byte
L B#16#30 // 0x30 = '0'
>=I // Is char >= '0' ?
JC CHECK_HIGH
L 0
T MB 250 // Not a digit
JU DONE
CHECK_HIGH:
L MB 100
L B#16#39 // 0x39 = '9'
<=I // Is char <= '9' ?
JC IS_DIGIT
L 0
T MB 250
JU DONE
IS_DIGIT:
L 1
T MB 250 // Mark as valid digit
DONE: NOP 0
Always verify the byte range with the symbol table or VAT online monitor before relying on the comparison; a corrupted string can yield 0xFF (treated as 255 signed) and produce unexpected branch behavior.
Verification and Commissioning
-
Open the VAT (Variable Table). Insert
MB 100..MB 110,MW 200,DB50.NUM_STRin the "Monitor/Modify" view. Reference: STEP 7 V5.5 Programming and Operating Manual. -
Force test characters. Use the Modify function to write
B#16#31 35 39 37toMB 100..MB 103and triggerM 0.0. VerifyMW 200reads12597decimal. -
Loop test. Force each digit pattern from
0000to9999in increments of 137 (prime modulus avoids aliasing). Confirm the result tracks linearly with no overflow flag set inSTW(status word bit OV). -
Boundary test. Force
B#16#30 30 30 30(zeros) andB#16#39 39 39 39(max INT). Confirm overflow is handled. -
Watch the OK flag. For STRING conversion, force a non-numeric character (e.g.,
0x41 = 'A') and verify the OK flag resets, not the value. - Cycle time check. In OB1, view the cycle time via online → module information. The arithmetic method should add < 1 ms for a 4-digit conversion; the library method is typically 2-4 ms.
Troubleshooting Matrix
| Symptom | Likely Root Cause | Diagnostic Step | Fix |
|---|---|---|---|
| Result is always 0 | ASCII offset 48 not applied (raw value 0x31 = 49, not 1) | Monitor MB 100 in VAT — confirm 0x31 not 0x01 | Insert L 48; -I step |
| Result is off by factor of 10 | Forgotten * 10 between digit loads |
Trace the accumulator value in VAT | Add multiplication step |
| Result is the digit reversed ("9521" instead of "1259") | Device sends LSB first, code assumes MSB first | Check device manual; inspect raw byte order in VAT | Reverse load order or use loop with index |
| STRING_TO_INT returns 0 with OK=0 | Length byte in STRING header is 0 or value contains non-numeric char | Monitor DB50.NUM_STR byte-by-byte |
Verify CUR_LEN is set; remove leading/trailing spaces |
| Compare instruction always TRUE | CPU accumulator has stray bits in upper bytes (rare on S7-300, possible on cross-CPU FB calls) | Mask the comparison: L MB n; L 0; ==I check is byte-safe |
Use L B#16#00; OW; L B#16#39; <=I pattern |
| PIW 123 reads 0 in OB1 but raw module value correct | Hardware fault on input module or wrong slot address | Online → Module Information → Diagnostics | Recompile HW Config; verify slot |
| STL code goes to SF (system fault) | Indirect address out of memory area (AR1 outside [DB/FB] range) | Check STL for +AR1 overflow |
Clamp pointer with area-crossing check (MCR dependencies, AR1 limits) |
| Value flickers in HMI | OB1 read of partial update (driver gives 3 of 4 bytes) | Check handshake bit in receive FB | Latch read at end-of-frame flag, not in OB1 |
Edge Cases and Field-Proven Caveats
-
Leading zeros: The scanner may send
"015"code> with leading zero. The arithmetic method handles leading zeros naturally; the STRING method will convert"015"to15if the STRING type correctly captures the leading character. Confirm by settingNUM_STR.CUR_LEN = 3and forcing0x30 0x31 0x35in the data area. -
Negative numbers: Some scales prefix the value with a leading minus sign (
0x2D). Detect0x2Das the first byte, set a sign flag, and at the end applyNEGIto the result. Always check the device's data format specification first. -
Decimal points: Scales frequently transmit
"12.59". Either parse the dot position with a loop, then place the decimal by dividing by10^n, or useFC 30 (STRING_TO_REAL)and store the result in a REAL tag. -
Trailing CR/LF: Many instruments append
0x0D 0x0A. Set the STRING length byte to exclude these or strip them in the receive FB before invoking the conversion. -
CPU accumulator size on S7-400: S7-400 CPUs use two 32-bit accumulators. The arithmetic above still works because STL integer math is 16-bit. To force 32-bit results, use
ITDafter the load and switch to*D/+Doperations. -
Reentrancy: If the FB is multi-instanced, do not use bit memory addresses (
MB,MW) for the working values. Use the FB's static local variables (STAT) declared in the instance DB instead.
Diagnostic Flowchart
Related Standards and Documentation
- S7-300 CPU 31x/31xC Manual — Memory model, addressing, accumulator behavior.
- S7-300/400 Standard and System Functions Reference — FC5, FC30, FC38 specification.
- STEP 7 V5.5 Programming and Operating Manual — STL syntax, FB/FC, VAT, indirect addressing.
- IEC 61131-3:2013 — Standard PLC programming languages (STRING type definition, conversion semantics).
- ASCII table — ANSI X3.4-1986 (R1997) and ISO/IEC 646:1991.
Why does my converter return 49 instead of 1 when reading the character '1'?
The character '1' is encoded as ASCII 0x31 (decimal 49). To obtain the integer digit value 1, you must subtract 48 (0x30) from the byte. Without this offset, your STL accumulator holds the raw ASCII code, not the numeric digit.
What is the difference between IW 123 and PIW 123 in STEP 7 V5.x?
IW 123 reads the Process Image Input word, which is refreshed by the CPU at the start of OB1. PIW 123 reads directly from the peripheral backplane, bypassing the PII. PIW is required for time-critical hardware interrupts and can return inconsistent data mid-scan; IW is the safer default for cyclic OB1 logic.
Can I store more than 32767 after converting a 5-digit ASCII string?
No — INT is 16-bit and limited to -32768..+32767. For values up to 99999, convert the bytes to a DINT (32-bit) by using ITD to widen the accumulator, *D and +D for arithmetic, and store the result in MD n. The DINT range is -2147483648..+2147483647.
How do I handle a negative sign character 0x2D in the ASCII stream?
Inspect the first byte before the conversion. If MB n = 0x2D, set a sign flag, skip the sign byte, convert the remaining digits, then apply NEGI (16-bit) or load 0, -D (32-bit) to invert the sign. Always document the sign convention in the device data sheet.
Why does STRING_TO_INT return 0 even when my string is correct?
Three common causes: (1) the STRING current-length header byte is 0 — verify DB50.NUM_STR.CUR_LEN is set to the actual character count; (2) leading or trailing whitespace is included in the string — strip 0x20 characters first; (3) the string contains characters outside 0–9 and an optional leading minus sign — the IEC function rejects everything else.