S7-1200 HSC PEEK Read Returns Zero: DWORD Access Fix for 1214C

David Krause11 min read
S7-1200SiemensTroubleshooting
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

Problem Description

On a SIMATIC S7-1200 CPU 1214C, reading a high-speed counter (HSC) value through the PEEK instruction inside an SCL function block returns 0, even though the configured HSC is counting correctly. The same counter value is visible in the configured HSC tag (e.g., the default "HSC" tag added by the HSC configuration wizard, or any user-defined hardware tag) and updates at the rate set in the device configuration.

A typical fault pattern in an SCL FB looks like this:

// Faulty read: PEEK only fetches ONE byte from the I/O area
#Measured_Value := PEEK(area:=16#81, dbNumber:=0, byteOffset:=#Speed_Counter_Input_Byte);
// #Speed_Counter_Input_Byte = 1000  (i.e., %IB1000, the LSB of %ID1000)
// Symbolic read of the configured HSC tag - returns the live value correctly
#Measured_Value := "HSC";

The PEEK call always returns 0 because of how the instruction addresses the input area, not because of an HSC configuration error. The symbolic read returns a real, updating count. The mismatch is a data-width and access-path problem at the SCL call site, not a defect in the HSC itself.

Root Cause: PEEK Reads One Byte, ID1000 Is a Double Word

The PEEK instruction in TIA Portal SCL is documented as a byte-granular read of the process-image input area. Its signature in the standard library is:

PEEK(area: BYTE, dbNumber: UINT, byteOffset: DINT): BYTE

That is, PEEK always returns one byte, regardless of the destination variable's declared width. In the source code, the user is reading %IB1000 through PEEK and assigning the byte into a wider destination, so the upper three bytes of the counter are never fetched. Worse, when the HSC is configured as a 32-bit counter (the default for frequency and most counting modes on the S7-1200), the least-significant byte at %IB1000 is frequently 16#00 at the moment of the read, producing the constant zero the user observes.

Two technical points drive this:

  1. Bit-width mismatch. The HSC hardware on the S7-1200 occupies a 32-bit (DWORD) input location starting at the configured address (default %ID1000). PEEK returns one of those four bytes; it does not widen to match the LHS of the assignment.
  2. Process-image vs. peripheral access. PEEK reads the process image of the inputs (PII), not the direct peripheral area (PI). On the CPU 1214C the HSC value is mirrored to the PII at the configured address, but the PII refreshes only at OB1 cycle start (or on a configured interrupt OB). The HSC sample itself is fast, but the read you obtain is still a 32-bit DWORD, not a single byte.

The corrective paths are: (a) use the 32-bit variant PEEK_DWORD followed by an explicit type cast, or (b) drop the byte-level introspection entirely and access the counter symbolically with the :P (peripheral) qualifier. The :P access gives a clean, immediate, fully-typed 32-bit read and is the pattern recommended by Siemens documentation for HSC read access on the S7-1200.

Solution 1: PEEK_DWORD with Explicit Cast

For users who want to keep an indexed-style access (for example, when the HSC address is computed at runtime), the correct pair is PEEK_DWORD followed by a type cast. PEEK_DWORD returns a DWORD that you can then cast to the application's integer type:

// Correct: 32-bit read from the input image
#Measured_Value := DWORD_TO_INT(PEEK_DWORD(area:=16#81, dbNumber:=0, byteOffset:=#Speed_Counter_Input_Byte));
// Or, preferred when the counter is treated as a 32-bit value:
#Measured_DWORD := PEEK_DWORD(area:=16#81, dbNumber:=0, byteOffset:=#Speed_Counter_Input_Byte);

Key parameter rules:

  • area := 16#81 selects the inputs (I) area. Use 16#82 for outputs (Q), 16#84 for a data block, or consult the TIA Portal help for the full code list.
  • dbNumber := 0 is required when area is an I/O area code (16#81 or 16#82); it is only meaningful for DB areas.
  • byteOffset is the byte index, not the bit index. For %ID1000 the byte offset is 1000.
  • PEEK_DWORD is supported in SCL on S7-1200 CPUs from firmware 4.0 onward; on earlier firmware, build the read from PEEK and byte-shift manually, or upgrade the CPU firmware.
Watch the cast direction. DWORD_TO_INT clamps to the 16-bit signed range and will wrap on large counts. If the HSC value can exceed 32767, store the raw DWORD first, then apply the engineering conversion on a wider type. Using DINT as the cast target is usually a better fit than INT for 32-bit counter values.

Solution 2: Symbolic Access with the Peripheral Qualifier :P

The recommended pattern in TIA Portal is to drop the byte-level PEEK entirely and read the HSC symbolically with the peripheral access slice. If you create a tag named myHSC1 at address %ID1000 in the PLC tag table (DWord), the SCL access is:

// Direct peripheral read - no process image, no width mismatch
#Measured_DWORD := "myHSC1":P;

The :P qualifier forces a read from the peripheral input area (PI) rather than the process image input (PII). The advantages are concrete:

  • Immediate. No wait for an OB1 cycle update; the value is read straight from the peripheral register the moment the instruction is evaluated.
  • Typed. The bit width of the variable is preserved by the compiler; you cannot accidentally read only the LSB.
  • Symbolic. The tag is resolved by name; renaming the tag later does not break the access.
  • No EN/ENO surprises. Peripheral access does not need EN/ENO handling in LAD/FBD and cannot silently return the wrong width.

Siemens' HSC configuration wizard already follows the same idea: the default "HSC" tag (or whatever name is given in the HSC properties) is itself a peripheral-style tag that updates at the configured sample rate. If you do not need dynamic addressing, the wizard-generated tag is the simplest possible read.

Memory Layout: Why a Byte Read of a 32-Bit Counter Returns Zero

To make the failure mode explicit, the byte layout of %ID1000 in the input image looks like the following (little-endian, byte 0 is the LSB):

Address %IB1000 (LSB) %IB1001 %IB1002 %IB1003 (MSB)
Bit width 8 8 8 8
Read by PEEK Yes No No No
Read by PEEK_DWORD Yes Yes Yes Yes
Read by "myHSC1":P Yes Yes Yes Yes

If the running count is, for example, 16#0000_07D0 (2000 decimal), %IB1000 is 16#D0 and not zero, so PEEK would return 208, not 0. The symptom of "always zero" is therefore the statistical effect of an LSB that is frequently 16#00 on a counter that increments through the value range. The fix is the same regardless of the exact pattern: read all four bytes.

CPU 1214C HSC Capabilities and Address Space

Per Siemens' published HSC reference for the S7-1200 family, the CPU 1214C supports up to six high-speed counters, with the exact number and supported counting modes constrained by the signal board, encoder, and onboard digital I/O configuration. The relevant limits for the user case are:

Item CPU 1214C value
Maximum HSC instances 6 (HSC_1 to HSC_6)
Default input address range %ID1000 to %ID1008 (32-bit slots, one per HSC)
Counter bit width 32 bits, signed (DINT) by default
Counting modes Single-phase, two-phase, A/B quadrature (1x, 2x, 4x), with optional reset and gate inputs
Frequency / period measurement Supported; output is scaled in Hz by the wizard
Update rate for HSC value Configurable per HSC: process-image update in OB1, or hardware-interrupt-driven update via a dedicated OB

Source: SIMATIC S7-1200 High-Speed Counters (HSC) - Siemens Support PDF and the TIA Portal S7-1200 manual collection - Configuring a high-speed counter.

Default addresses are configurable. The address %ID1000 is the default first HSC slot, but the wizard can assign any free input word to any HSC. Always verify the configured address in the device configuration of the HSC, not in the PLC tag table, when debugging a read that returns 0.

Process Image vs. Peripheral: When Each Applies

The S7-1200 input area is logically two distinct regions:

  • PII (Process Image Inputs) - a snapshot of the physical inputs refreshed at the start of OB1, or on a configured hardware interrupt. Reads from PII are consistent within one cycle, which is what most application logic wants.
  • PI (Peripheral Inputs) - the live physical input registers. Reads from PI bypass the snapshot and return the value at the moment of the instruction.

PEEK and PEEK_DWORD read from PII. The trailing :P qualifier reads from PI. For an HSC, both can be used, but PI gives the freshest value. If the HSC is configured to update the PII only on a hardware interrupt, an OB1-cycle PEEK may even return a stale value, which is a second reason to prefer :P in fast measurement applications.

When PEEK on an HSC Could Still Be Useful

There are narrow use cases where PEEK_DWORD is justified, mostly when the address is computed at runtime from a recipe parameter or pointer:

  • The program monitors one of several HSCs and the active HSC index is selected by the recipe.
  • Generic library code that wraps a third-party address scheme in a portable way.
  • Legacy migration from S7-300/S7-400 code that used absolute DB or I/O access in a similar style.

Outside of these cases, the symbolic :P access is faster to write, easier to review, and impossible to get the width wrong.

Verification Steps

  1. Confirm the configured HSC address. Open the CPU 1214C in the project tree, expand "High-speed counter (HSC)", and select the active HSC. On the "I/O addresses" tab, read the input address. The default is ID1000, but a project that has been edited may have moved it.
  2. Watch the wizard HSC tag online. With TIA Portal online, force a count at the terminal or use a signal generator. Confirm that the wizard-generated tag (e.g., "HSC_1") increments at the expected rate. This proves the hardware and configuration are correct.
  3. Switch the SCL to PEEK_DWORD. Replace the PEEK call with PEEK_DWORD(area:=16#81, dbNumber:=0, byteOffset:=1000). Monitor the result online; it should now match the wizard HSC tag exactly, modulo the OB1 update rate.
  4. Switch to symbolic :P access. Create a tag myHSC1 at address %ID1000 (DWord) in the PLC tag table, then read "myHSC1":P in the SCL block. Monitor online; the value should update immediately and be free of the OB1-cycle jitter of the PEEK variant.
  5. Cross-check in a watch table. Add three watch rows for %ID1000, "HSC_1", and "myHSC1":P. All three should agree (apart from sub-cycle updates of the PI read).
  6. Sanity-check the cast target. If the application stores the result in a 16-bit INT, deliberately overflow the HSC value to confirm the cast is sized for the real count range.

Best Practices for HSC Value Access on S7-1200

  • Use the wizard-generated HSC tag where possible. The HSC configuration wizard binds a typed tag to the configured address and honors the configured update rate (process-image vs. fast interrupt) automatically.
  • Prefer symbolic access over absolute address access. PEEK and POKE defeat the compiler's type checking and hide the access in the binary. Symbol-named tags, including with :P, are refactor-safe and appear in the cross-reference list.
  • For frequency and period modes, let the system do the math. The HSC on S7-1200 can be configured to output an engineering value (Hz, RPM) directly. A user-side division is usually wrong by a factor of the update interval.
  • Avoid mixing PI and PII reads of the same HSC in the same cycle. If you read "myHSC1":P and also "myHSC1" (PII) in the same OB, the values can differ by one update; the peripheral read is the fresh one.
  • Document the area code in a constant. 16#81 (inputs) and 16#82 (outputs) are easy to misread. Define them as named constants in the project standard library.
  • Match the destination type to the source width. Use DWORD or DINT for raw counter values, and apply engineering conversion in a separate, well-named step.

Troubleshooting Matrix

Symptom Likely cause Fix
PEEK returns 0 on a counting HSC Byte width read; HSC is 32 bits Use PEEK_DWORD or :P access
PEEK_DWORD returns 0 Wrong area code, wrong byte offset, or HSC not enabled in HW config Verify area, byteOffset, and that the HSC is enabled in the device configuration
Symbolic HSC tag returns 0 HSC not enabled, wrong address, or no input pulse Force a count at the terminal; check wiring and HSC mode
Value stuck at a constant Reading the PII of an HSC configured to update only on a hardware interrupt Use :P for the live value, or wire the configured interrupt OB
Value wraps unexpectedly Cast to INT (16 bits) on a 32-bit counter Use DINT/DWORD for the cast target
Value drifts by one update between two reads One read uses PII, the other uses PI Pick one access path (PII for consistency, PI for freshness) and use it everywhere

Reference Material

FAQ

Why does PEEK return 0 for an S7-1200 high-speed counter at %ID1000?

PEEK is a byte-granular read of the process image input. Reading %IB1000 returns the least-significant byte of the 32-bit counter, which is frequently 0x00 on a running counter. Use PEEK_DWORD to fetch all four bytes, or read the counter symbolically with the :P peripheral qualifier for an immediate, full-width value.

How do I read a 32-bit HSC value with PEEK on a CPU 1214C?

Use PEEK_DWORD(area := 16#81, dbNumber := 0, byteOffset := 1000) and store the result in a DWORD or DINT variable. Cast to a 16-bit INT only after the engineering conversion, not before, or the value will wrap.

What is the difference between ID1000 and ID1000:P on S7-1200?

ID1000 reads the process image input (PII), which is refreshed at the start of OB1. ID1000:P reads the peripheral input (PI) directly, giving the live register value with no cycle delay. For high-speed counters, ID1000:P is typically the correct read.

How many high-speed counters does the CPU 1214C support?

The CPU 1214C supports up to 6 high-speed counters, allocated at default addresses %ID1000 to %ID1008. The exact count available depends on the encoder and digital I/O configuration in the device properties.

Can I move an HSC's address away from %ID1000?

Yes. In the device configuration of the HSC, the input address is editable. After changing the address, update any SCL code that references it and recompile. Symbolic access (e.g., "myHSC1":P) refactors automatically; PEEK_DWORD access with a hard-coded byteOffset does not.

Back to blog