Program Purpose and Functional Scope
The STL snippet below is a classic quadrature encoder direction decoder written for a SIMATIC S7-300 CPU executing STEP 7 classic (S7-300 / S7-400 STL). It is built entirely on standard 24 V digital inputs — no FM350-1, no high-speed counter, no technology object. The program samples two channels of a quadrature encoder (typically labelled A and B, shifted 90° in phase) connected to a SM321 DI module and decides, in real time, whether the shaft is rotating forward or reverse. The Italian comments on the original code identify the surrounding machine context: a sezione lenta (“slow section”) head, a conteggio fasi (“phase counting”) task, and an inverter that drives the motor (START/STOP MOT.INVERTER). The same logic is reusable for any low-to-medium pulse-rate application: indexing tables, diverters, slow capstar wheels, label feeders, and similar packaging peripherals.
The routine does three things:
- Detects the rising and falling edges of channel B with a memory-bit (flag) pattern, producing one-cycle pulses
M1.1(positive edge) andM1.2(negative edge). - Combines each edge with the instantaneous state of channel A to set the “machine running in reverse” memory bits
M8.6andM8.7. - Supervises the encoder pulse train with a 40 ms retentive ON-delay timer (
T1). If edges stop arriving while the inverter is commanded on, the routine clears the “reverse check enable” bitM8.5, preventing the next line of the machine from interpreting a stalled head as a valid reverse event.
The end result is a pair of robust direction flags and a watchdog that downstream logic can trust, even though no dedicated counter hardware is involved.
Prerequisites and Hardware
To run, modify, or commission this logic you need:
- A SIMATIC S7-300 CPU 31x (e.g., CPU 314, 315-2 DP, 317-2) or any S7-400 CPU that supports the classic STL instruction set. The code also runs unchanged on an S7-1500 in compatibility mode, but a native SCL version is recommended on that platform.
- A SM321 digital-input module located in the same station as the encoder, with byte address 8 in the process image (I8.0–I8.7). The encoder's A and B channels land on
I8.6andI8.7respectively, with the 24 V common tied to the module's M terminal. Refer to the SIMATIC S7-300 SM 321 digital input module manual for input-delay selection and wiring details. - A 5 V or 24 V incremental encoder with two quadrature channels (A, B) and ≤ ~500 pulses per revolution. A 24 V push-pull or NPN output is preferred when wiring to a SM321; HTL (10–30 V) encoders connect directly.
- STEP 7 V5.x (or TIA Portal with an S7-300 in compatibility mode) with the symbol table populated. See the STEP 7 STL/FBD/LAD programming and reference manual for the exact semantics of
FP,FN,S,R,SF, and theS5T#time literal. - Ability to force inputs in the online watch table and to view cross-references.
Quadrature Encoder Signal Model
An incremental encoder with two channels A and B emits two square waves displaced by 90° electrical. At any given instant, one of four states (00, 01, 11, 10) is present on the (A, B) pair. A full quadrature cycle is therefore four state transitions per electrical period, and the “effective” counts per revolution are 4 × PPR (pulses per revolution). The Renishaw encoder primer explains this in a vendor-neutral way; the same rule is also documented in the Allen-Bradley 2198-UM003 encoder output module manual (publication 2198-UM003) under the “quadrature decoding” section.
The key property exploited by the S7 program is the quadrature phase relationship:
- If A leads B (A rises while B is still low), the shaft is rotating in one direction.
- If B leads A (B rises while A is still low), the shaft is rotating in the other direction.
The decoder does not measure the full period — it only inspects the A bit at the exact instant B changes state. That makes the logic cheap but imposes the “no missed pulses” rule that is discussed in the “Limits” section below.
STL Source Code with Bilingual Annotations
The original code carries Italian comments because the programmer wrote it for an Italian end user. Below the program is reprinted verbatim; the inline annotations on the right side are English translations. Use the symbol table in the next section as the reference for the symbolic names.
NETWORK 1 — Read process image of inputs byte 8
L PIB 8 // Load Process-Image Inputs Byte 8
T IB 8 // Transfer to inputs byte 8 (refresh)
NETWORK 2 — Manual rising edge on channel B (I8.7)
A I8.7 // A = true when Channel B is high
AN M1.3 // AND NOT previous-B memory
S M1.3 // Set B-memory bit
= M1.1 // M1.1 = positive-edge pulse of B
NETWORK 3 — Manual falling edge on channel B (I8.7)
AN I8.7 // A = true when Channel B is low
A M1.3 // AND previous-B memory still set
R M1.3 // Reset B-memory bit
= M1.2 // M1.2 = negative-edge pulse of B
NETWORK 4 — Start the 40 ms encoder watchdog
A "MFASLEN+" M1.1 // Positive edge of channel B
S "MBACKENABLELEN" M8.5 // Latch reverse-rotation enable
NETWORK 5 — Set 40 ms preset for the watchdog
A "MFASLEN+" M1.1 // Positive edge of channel B
L S5T#40MS // Load 40 ms time literal
SF "TIMER ENCODERLEN" T1 // Retentive ON-delay, 40 ms
NETWORK 6 — Clear reverse-rotation enable if no edges arrive
AN "TIMER ENCODERLEN" T1 // Timer elapsed (no edge in 40 ms)
R "MBACKENABLELEN" M8.5 // Drop reverse-rotation enable
NETWORK 7 — Detect reverse direction on B rising edge with A=0
A "MFASLEN+" M1.1 // B positive edge
AN "B25.1 /CK1" I8.6 // Channel A is still low
AN "MBACKENABLELEN" M8.5 // Reverse-rotation enable is on
= "MFASLENRETRO+" M8.6 // M8.6 = B rising, A=0 = reverse
NETWORK 8 — Detect reverse direction on B falling edge with A=1
A "MFASLEN-" M1.2 // B negative edge
A "B25.1 /CK1" I8.6 // Channel A is high
AN "MBACKENABLELEN" M8.5 // Reverse-rotation enable is on
= "MFASLENRETRO-" M8.7 // M8.7 = B falling, A=1 = reverse
Inline timing diagram (quadrature states vs. decoder outputs)
When B leads A (reverse direction) the rising edge of B always occurs while A is still logic ‘0’. The first network fires M1.1, and the seventh network combines that pulse with the read of I8.6 = 0 to set M8.6 — the “reverse on rising edge” memory. The complementary M8.7 pulse is generated at the falling edge of B while A is high, which is the other transition in the reverse-rotation state sequence (10 → 00 in the AB pair).
Edge-Detection Memory on Channel B
The first three networks implement a software edge detector for I8.7 using the flag bit M1.3 as a one-cycle delay memory. The technique is functionally equivalent to using the standard FP (rising-edge) and FN (falling-edge) instructions in STL, but the author wrote it out long-hand so that the resulting bits can be referenced symbolically (M1.1, M1.2) and so that both edges are available in the same scan without a second flag bit. The behaviour is summarised in the table below.
| Scan | I8.7 (B) | M1.3 (prev) | Action | M1.1 (B ↑) | M1.2 (B ↓) |
|---|---|---|---|---|---|
| n−1 | 0 | 0 | Idle | 0 | 0 |
| n | 1 | 0 | Set M1.3, output M1.1 | 1 | 0 |
| n+1 | 1 | 1 | Idle | 0 | 0 |
| n+2 | 0 | 1 | Reset M1.3, output M1.2 | 0 | 1 |
| n+3 | 0 | 0 | Idle | 0 | 0 |
This manual form has the same edge sensitivity as the built-in FP/FN instructions documented in the STEP 7 STL reference manual. The difference is that the user-defined M1.1 and M1.2 outputs are visible in the symbol table, making the rest of the program self-documenting in cross-reference view.
Direction Decode Matrix
Combining the two edge bits (M1.1, M1.2) with channel A (I8.6) yields the four possible transitions of a quadrature cycle:
| Transition (A,B) | B edge | A at that instant | Direction | Output bit set |
|---|---|---|---|---|
| 00 → 10 | Rising (M1.1) | 0 | Reverse (B leads A) | M8.6 = 1 |
| 10 → 11 | Falling (M1.2) | 1 | Reverse (B leads A) | M8.7 = 1 |
| 11 → 01 | Rising (M1.1) | 1 | Forward (A leads B) | M1.1 alone used |
| 01 → 00 | Falling (M1.2) | 0 | Forward (A leads B) | M1.2 alone used |
The original STL only stores the “reverse” combinations explicitly (M8.6, M8.7). The “forward” combinations are conveyed by the absence of M8.6/M8.7 combined with the inverter run-bit Q15.5; downstream logic typically inverts that condition to drive the “forward” branch of the state machine.
40 ms Watchdog Timer Behaviour
The SF instruction in network 5 is the retentive ON-delay timer from the S7 instruction set: once started, it accumulates the ‘1’ duration of its enable input across scans, and only resets when explicitly cleared with R. Combined with the S of network 4 and the AN T1 / R M8.5 of network 6, the routine forms a pulse-presence watchdog:
- Every rising edge of B (M1.1) re-arms M8.5 (reverse-rotation enable) and re-loads T1 with 40 ms.
- If a second rising edge arrives within 40 ms, T1 is re-loaded before it elapses, so
AN T1remains false and M8.5 stays set. - If no edge arrives for the full 40 ms, T1 finishes,
AN T1becomes true, and M8.5 is cleared. From that moment the reverse-decoder networks 7 and 8 are masked off by theAN M8.5term, so any stray noise on the input cannot create a phantom reverse event.
The 40 ms threshold is dimensioned for the slowest legitimate pulse period of the slow section. The pulse period at the slowest speed n_min (rpm) of an encoder with PPR = p is:
T_pulse = 60 / (4 × p × n_min) seconds
For example, with p = 100 PPR and n_min = 5 rpm:
T_pulse = 60 / (4 × 100 × 5) = 30 ms
That is shorter than the 40 ms watchdog, so the decoder will correctly time out when the head stops. Increase the timer to S5T#200MS or S5T#1S for slower lines. The S5 time format is documented in the STEP 7 reference manual as S5T#<base><value> where base may be MS, S, M, or H.
Symbol Table and I/O Allocation
Maintain a single source of truth for the symbolic names. The recommended symbol table is:
| Symbolic name | Address | Type | English meaning | Italian source |
|---|---|---|---|---|
| B25.1 /CK1 | I8.6 | BOOL | Encoder channel A (clock phase 1) | — |
| (unnamed) | I8.7 | BOOL | Encoder channel B | — |
| MFASLEN+ | M1.1 | BOOL | Rising edge of channel B | Memoria fronte positivo fasi lente |
| MFASLEN− | M1.2 | BOOL | Falling edge of channel B | Memoria fronte negativo fasi lente |
| (B memory) | M1.3 | BOOL | One-scan delay of channel B | — |
| MBACKENABLELEN | M8.5 | BOOL | Enable reverse-rotation decode | Memoria abilitazione controllo rotazione inversa |
| MFASLENRETRO+ | M8.6 | BOOL | Reverse direction (B ↑, A=0) | Memoria fronte positivo sez. lenta macchina indietro |
| MFASLENRETRO− | M8.7 | BOOL | Reverse direction (B ↓, A=1) | — |
| TIMER ENCODERLEN | T1 | TIMER | 40 ms encoder pulse watchdog | Timer controllo velocità encoder sezione lenta |
| (motor run) | Q15.5 | BOOL | START/STOP of motor inverter | Start/stop mot. inverter |
The flag-byte conventions used in the original program (M1.x for edge memory, M8.x for direction memory) are a good place to start, but the modern best practice is to put direction and edge flags in a dedicated FB with an instance DB so that the same logic can be reused for several heads without colliding on M-bits.
Wiring the Encoder to a Standard DI Module
- Connect the encoder cable shield to the cabinet ground bar at one end only, close to the PLC, to avoid ground loops. See the SM 321 installation manual for the EMC bonding recommendations.
- Wire the encoder 24 V supply to a fused output of the same 24 V rail that powers the SM321 inputs. This prevents input noise during motor start-up.
- Route the A and B pairs as a twisted shielded pair; do not run them alongside the inverter power cable. If the run is > 25 m, set the SM321 to the 0.5 ms input-filter position for that byte only (DIP switch on the side of the module).
- Bring channel A to terminal that maps to
I8.6and channel B to the terminal that maps toI8.7. The unused inputs of byte 8 should be tied to 24 V to keep the byte stable in the process image (theL PIB 8 / T IB 8refresh at the top of the routine makes this especially important). - Verify the polarity with a voltmeter before powering the encoder. Reversing A and B inverts the “forward” / “reverse” sense and is the single most common commissioning bug for this code.
Limits of Polled Digital-Input Decoding
The decoder above is intentionally minimal, and that has consequences:
-
Missed pulses at high speed. A pulse must be wider than one full OB1 scan, otherwise the input image can settle on the same logic level across two consecutive scans. The minimum pulse period is therefore
T_pulse_min ≥ 2 × T_scan. For a typical 10 ms OB1 cycle, the maximum countable pulse frequency is roughly 50 Hz, or 1 200 rpm with 100 PPR encoders. - Noisy edges produce extra direction events. With 24 V inputs on a long cable, a 1 µs bounce can be enough to create an extra M1.1 pulse. The reverse-decode networks then fire spuriously, and the watchdog has to clear the fault. The Renishaw encoder primer describes why differential outputs (RS-422 / 5 V TTL) reject this noise class far better than single-ended HTL.
- No absolute count. The routine only yields direction. Counting revolutions, indexing positions, or handling power-down retention requires additional memory and a different architecture.
- No synchronisation to OB1 boundaries. Two transitions between scans (e.g., a fast burst of A and B that happens in < 1 ms) collapse into a single state read and the decoder logs no edge at all.
For a quantitative sizing, the maximum usable mechanical speed is:
n_max_safe (rpm) = 60 / (4 × p × T_scan × 2)
Plug in p = 100 PPR and T_scan = 5 ms:
n_max_safe = 60 / (4 × 100 × 0.005 × 2) = 15 rpm
That is slow, which is why this technique is labelled a “slow section” decoder in the source code itself.
Migration Path to High-Speed Counters
When the slow-section decoder no longer keeps up, the natural upgrades on S7-300 are:
| Module / function | Order number | Max count freq. | Quadrature | Notes |
|---|---|---|---|---|
| FM 350-1 counter module | 6ES7350-1AH03-0AE0 | 500 kHz | 1x, 2x, 4x | Single-channel; one FM per encoder |
| FM 350-2 counter module | 6ES7350-2AH00-0AE0 | 20 kHz (8 ch.) | 1x, 2x, 4x | 8 channels, ideal for multi-head machines |
| SM 338 POS encoder input | 6ES7338-4BC01-0AB0 | 1 MHz | RS-422 | For SSI and incremental, three encoders per module |
| CPU 314C-2 technology counters | CPU 314C-2 PtP / DP | 60 kHz | 1x, 2x, 4x | Built into the CPU; no extra slot |
| ET 200S 1Count 24V | 6ES7138-4DA04-0AB0 | 100 kHz | 1x, 2x, 4x | Distributed I/O, ideal for cabinet-free layouts |
For a like-for-like replacement on Allen-Bradley ControlLogix, the equivalent hardware is the 1756-HSC high-speed counter module or the 1756-EN2TR / 1756-EN4TR encoder modules; see the Allen-Bradley 2198-UM003 encoder output module manual for the Kinetix-side wiring when the encoder is mounted on a servo axis.
When migrating, replace the four networks above with a single call to the FM's CNT_CTL or the SFB “count 24V” block. The semantics of the “direction” output of those blocks is the same: a BOOL that is true for one direction and false for the other. The 40 ms watchdog is no longer needed because the FM samples the encoder in hardware.
Commissioning and Verification Procedure
- Static check. With the inverter disabled and the encoder hand-cranked, watch I8.6 and I8.7 in the online monitor. They should alternate 0/1/0/1… in quadrature, never both stuck at the same value for more than one scan.
- Edge check. Toggle the encoder by hand slowly. In the VAT, force a cross-reference view on M1.1, M1.2, and M1.3. Each mechanical detent should produce exactly one M1.1 and one M1.2 pulse.
- Direction check. Crank the shaft in the “forward” mechanical sense for 10 turns. M8.6 and M8.7 should remain 0; M8.5 should remain 1 because T1 is being retriggered. Crank the shaft in the “reverse” sense: M8.6 should pulse at every rising edge of B and M8.7 at every falling edge of B.
- Watchdog check. Stop the shaft and start the inverter (or simulate Q15.5 = 1 in the VAT). After 40 ms ± one scan, M8.5 should drop to 0. Verify that no further M8.6/M8.7 pulses appear even if the input is forced.
- Speed check. Run the section at its normal speed and monitor the scan time with a watch-dog OB1. The OB1 must remain below 50% of T_pulse_min, otherwise the decoder will start losing edges.
- Fault insertion. Disconnect channel A and command reverse rotation. M8.6 and M8.7 should not appear, because the program correctly identifies the missing A signal as “not a valid quadrature state” and the watchdog should still time out.
Document the OB1 scan time measured at the maximum normal speed in the project documentation so that a future firmware update that increases the cycle time is caught at FAT, not at the customer site.
Why is the routine sampling PIB 8 at the top and then writing it back to IB 8?
The L PIB 8 / T IB 8 pair is a manual refresh of the process-image input byte. It forces OB1 to read the physical inputs on slot 8 once at the start of the routine, guaranteeing that I8.6 and I8.7 are coherent within the same scan. Without it, a transition that occurs between two reads of the same input by OB1 could be missed. On modern S7-300 firmware this is rarely needed because OB1 already updates the I area atomically, but the pattern is harmless and improves determinism on long scan times.
Can I replace the manual edge detection (M1.3 + AN/A) with FP and FN?
Yes. The same logic is written more compactly as A I8.7; FP M1.0; = M1.1; for the rising edge and AN I8.7; FN M1.0; = M1.2; for the falling edge. The M1.0 flag in those calls is the dedicated edge bit per STEP 7 convention. The long-hand version in the source code was kept because the rest of the program references M1.1 and M1.2 symbolically, and the M1.3 memory bit also serves as a one-cycle delay of the B channel for the watchdog networks.
What is the fastest pulse rate this decoder can resolve on a CPU 315-2 DP?
With a typical OB1 cycle of 4 ms on a CPU 315-2 DP, the minimum pulse period that two consecutive scans can both sample is roughly 8 ms, so the maximum countable pulse frequency is about 125 Hz. With a 100 PPR encoder in 4x mode that corresponds to about 19 rpm. For higher speeds, move to the integrated technology counters of the CPU 314C-2 (60 kHz) or to an FM 350-1 module (500 kHz). The 40 ms watchdog threshold of the source routine implies the original designer was running well below these limits.
Why does the program use SF and not SE for the 40 ms timer?
SF is the retentive ON-delay, meaning the timer accumulates time across multiple scans even if its input goes low in between. SE is the non-retentive ON-delay, which restarts on every 0→1 transition of its input. Because the encoder edge is a one-scan pulse, using SE would re-arm the timer on every cycle without ever letting it elapse; the watchdog would never trip. With SF, only the leading edge of M1.1 starts the timer and every subsequent edge re-loads the preset, so a 40 ms gap without edges is what actually trips it.
How do I scale the watchdog when the section runs at 1 rpm instead of 30 rpm?
Use the formula T_watchdog ≥ 60 / (4 × p × n_min) × 2 to set the timer preset. For p = 100 PPR and n_min = 1 rpm that gives T_watchdog ≥ 300 ms, so change the S5T#40MS literal to S5T#500MS or S5T#1S. Pick the next S5 time base up, not down, to avoid a false reverse event during acceleration.