Do-more PLC: How Do I Display Packed Hex Modbus Time?

Brian Holt7 min read
AutomationDirectModbusTechnical Reference
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

A Modbus read from the generator controller lands in the PLC as 2061, the operator screen shows 2061, and the generator's own panel says 08:13. Nothing is broken. That register is two independent one-byte fields sharing a 16-bit word, and 2061 is exactly what a straight integer read of it looks like.

Confirm What the Register Actually Holds

Do the arithmetic before you touch the ladder. 2061 decimal is 0x080D. The high byte 0x08 is the hour (valid range 0x00–0x17, or 0–23) and the low byte 0x0D is the minute (0x00–0x3B, or 0–59). Hour × 256 + minute = 8 × 256 + 13 = 2061. That is the whole mechanism.

The upper limits also tell you the encoding is plain binary, not BCD. Get that wrong and every field over 9 reads high.

Quick fix people try first What you get at 08:13 Why it fails
Scale by 0.01 in the HMI 20.61 The word is two packed bytes, not a scaled decimal number
BCD-to-binary convert the word Garbage or an instruction fault 0x0D and 0x3B contain nibbles above 9 — illegal BCD
Divide/modulo ladder (V0/256, V0 MOD 256) 8 and 13 Correct, but costs two instructions and a scratch word per field

Check before you move on: put the source register in Data View displayed as decimal and as hex. Change the generator clock one minute at the panel. The low byte must step by exactly 1 and the high byte must not move. Set the clock to minute 13: the low byte must read 0x0D. If it reads , the controller is sending BCD and every byte needs a BCD conversion ahead of the format step.

Address the High and Low Bytes Directly

Do-more addresses the bytes of a word natively, so no masking, shifting, or scratch memory is needed. With the Modbus read (MRX) destination set to V0:

  • V0:B1 = high byte = hour
  • V0:B0 = low byte = minute

:B0 is always the least-significant byte of the word, independent of how the Modbus driver assembled it. Those elements are readable anywhere a numeric source is accepted — comparisons, MATH, HMI tags, and the print script below.

Check: add V0:B1 and V0:B0 to Data View beside V0. At 08:13 they must read 8 and 13. If they read 13 and 8, the driver is swapping bytes — correct it by swapping the two byte references in the format string, not by changing the driver, which would break every other register in the same block.

Print HH:MM with STRPRINT and FmtInt

One STRPRINT does the formatting. Point its destination at a string element allocated in System Configuration → Memory Configuration, and use this format script:

STRPRINT
  Destination : (string element)
  Format      : FmtInt(V0:B1,dec,2,zeropad) ":" FmtInt(V0:B0,dec,2,zeropad)

The FmtInt arguments are source, radix, field width, and pad style. Width 2 with zeropad forces two characters every time, so you get 08:13 rather than 8:13, and 00:00 at midnight instead of a collapsing one-character field. Fixed width matters as soon as the string feeds a log column, a fixed-position display line, or a message body.

Everything inside the quotes prints verbatim, spaces included. A separator typed as ": " produces 08: 13. Type the colon with no padding.

Check: force V0 to 5947 in Data View — the string must read 23:59Force 0 — it must read00:00. Release the forces before leaving the machine.

Trigger the Print on Change, Not Every Scan

Put a delta contact on the source word ahead of the STRPRINT. The delta contact is true for exactly one scan when the value it watches changes, so the string builder runs once a minute instead of every scan. On a permissive that is always on, STRPRINT re-parses the format script and rewrites the destination thousands of times per minute for no new data.

The cost is more than scan time if the string is doing anything downstream. Feed it to a port write, a data-logging record, or an email body on an always-true rung and you get a duplicate transmission or a duplicate log row on every scan.

Check: watch the delta contact status in the ladder view. It should flash once per minute, in step with the generator's minute rollover.

Reuse the Pattern for Date, Day-of-Week, and Epoch Registers

The rest of the generator's clock block uses the same packing, so the same two-byte split covers it.

Register High byte Low byte Print script
Time Hour 0–23 Minute 0–59 FmtInt(V0:B1,dec,2,zeropad) ":" FmtInt(V0:B0,dec,2,zeropad)
Date Month Day of month FmtInt(V1:B1,dec,2,zeropad) "/" FmtInt(V1:B0,dec,2,zeropad)
Day / Year Day of week, 0 = Sunday Last two digits of year "20" FmtInt(V2:B0,dec,2,zeropad)

Day of week is an index, not a number to display. Either print the raw 0–6 value and let the HMI do the lookup, or build a seven-element string array and index it with V2:B1, remembering that Sunday is element 0. Zero-padding an index is meaningless — drop the pad on that field.

If a register pair carries a Unix timestamp instead of packed bytes, stop reusing this pattern. Epoch time is a 32-bit count of seconds since 1970-01-01 UTC, so it spans two Modbus registers and must land in a double-word (D) location with the word order verified before you trust it. Do-more Designer supplies a date/time conversion iBox for this — browse the date/time group in the Instruction Toolbox rather than writing divide-and-modulo ladder that has to survive leap years. The UTC offset and any DST rule are yours to add after the conversion.

Check: compare the decoded epoch against the PLC's own real-time clock at a known instant. The difference must be exactly your UTC offset.

Run the End-to-End Verification

  1. At the generator panel, set the clock to 10:20. The raw word must read 2580 () and the string 10:20. This is the test that catches a BCD misread — a BCD source would show , or 4128 decimal, at the same wall time.
  2. Set the clock to 23:59. Raw word 5947 (), string 23:59.
  3. Let it roll over. The word must go to 0, the string to 00:00, and the date register's low byte must increment by one day.
  4. Confirm the delta contact pulses once per minute and the STRPRINT rung is not solid on.
  5. Leave the string on the HMI for one hour and confirm the hour field steps 09 → 10 cleanly with both digits present.
  6. Cycle the Modbus link — pull the cable, restore it — and confirm the string resumes rather than freezing on a stale value. If it freezes, the delta contact is masking a dead read; add a communication-status bit to the print rung.

Stop and escalate if the raw word does not track the generator's own display after you have proven byte order and encoding with the tests above. At that point the fault is upstream of the PLC — a wrong register address, a controller option that changes the clock format, or a register map revision that does not match your documentation. Request the current Modbus register map from the generator controller OEM's technical support, and take questions about STRPRINT, FmtInt, or iBox behavior to AutomationDirect technical support with your project file attached.

FAQ

What happens if I send the raw Modbus word straight to the HMI without splitting the bytes?

You display hour × 256 + minute as one number — 2061 at 08:13, 5947 at 23:59. No HMI scaling factor can undo it because the two fields are packed side by side, not scaled.

What happens if the controller sends BCD instead of binary?

Every value above 9 reads high: minute 13 arrives as (19 decimal) instead of 0x0D. Test by setting the clock to a minute between 10 and 15 and reading the low byte in hex, then insert a BCD-to-binary conversion on each byte ahead of FmtInt.

What happens if I drive STRPRINT with an always-on contact?

The format script is parsed and the destination string rewritten every scan, for data that changes once a minute. If that string feeds a port write, log record, or message body, you also get a duplicate output every scan. Use a delta contact on the source register.

What happens if the Modbus master byte-swaps the register?

Hour and minute trade places — 08:13 prints as 13:08, and 2061 reads back as 3336 (). Fix it by swapping V0:B1 and V0:B0 in the format string; changing the driver's byte order affects every other register in the same read block.

What happens if the generator gives a Unix timestamp instead of packed bytes?

A 32-bit epoch value spans two Modbus registers, so it must be read into a double-word location and the word order confirmed before use. Use the date/time conversion iBox in Do-more Designer rather than divide-and-modulo ladder, then apply your UTC offset and DST rule separately.

Back to blog