Converting ASCII Char to Integer in Siemens S7 STL: A Guide

David Krause14 min read
S7-300SiemensTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

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".

Scope: This article targets S7-300/S7-400 CPUs programmed with STEP 7 V5.x in STL. The same principles apply to S7-1200/S7-1500 in AWL/ST; syntax differences for indirect addressing and IEC timer/counter usage are noted where relevant.

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 100 data 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.

Watch the I-address: The previous discussion referenced 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.

Endian reminder: The arithmetic above assumes the device sends the most significant digit first ("1259"). If the device sends least significant digit first ("9521"), reverse the MB load order in each segment, or use a DB pointer loop with AR1.

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

  1. Open the VAT (Variable Table). Insert MB 100..MB 110, MW 200, DB50.NUM_STR in the "Monitor/Modify" view. Reference: STEP 7 V5.5 Programming and Operating Manual.
  2. Force test characters. Use the Modify function to write B#16#31 35 39 37 to MB 100..MB 103 and trigger M 0.0. Verify MW 200 reads 12597 decimal.
  3. Loop test. Force each digit pattern from 0000 to 9999 in increments of 137 (prime modulus avoids aliasing). Confirm the result tracks linearly with no overflow flag set in STW (status word bit OV).
  4. Boundary test. Force B#16#30 30 30 30 (zeros) and B#16#39 39 39 39 (max INT). Confirm overflow is handled.
  5. 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.
  6. 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" to 15 if the STRING type correctly captures the leading character. Confirm by setting NUM_STR.CUR_LEN = 3 and forcing 0x30 0x31 0x35 in the data area.
  • Negative numbers: Some scales prefix the value with a leading minus sign (0x2D). Detect 0x2D as the first byte, set a sign flag, and at the end apply NEGI to 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 by 10^n, or use FC 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 ITD after the load and switch to *D / +D operations.
  • 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

Receive ASCII bytes from field device Is the length fixed and small (≤ 6 digits)? YES Method 1: Arithmeticsubtract 48, multiply by 10 NO Is the value variable-length with terminator? YES Method 2: STRING libraryFC5 / FC30 conversion NO Is the format HEX (0-9, A-F)? YES Method 3: Hex reconstructionoffset 55, shift 4 bits

Related Standards and Documentation

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.

Back to blog