ControlLogix STRING Write: Structure, Not SINT Array

Mark Townsend8 min read
Allen-BradleyControlLogixTroubleshooting
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

The write comes back CD 00 05 00 and the tag never changes. Every other data type in the driver — DINT, REAL, BOOL, arrays — writes clean. Start with the read reply, not the write payload. The controller already told you what that tag is, and general status is it saying the path names something it does not have.

Decode the Read Reply Before You Touch the Write

Issue Read Tag (), element count 1, no member segment, against the bare element: Z_PRD_NM[3]. The first bytes of the reply data are the type header, and they decide which branch you are on.

Read reply header What the tag actually is Why your write failed Next check
A0 02 hh hh Structure; hh hh is the template handle You sent an atomic type code into a structure member, or the member size is wrong Structure write, section below
C4 00 + 4 bytes DINT — not a string .DATA does not exist under a DINT, so the path never resolves Fix the tag reference, not the packet
C2 00 + 1 byte SINT Same as above — no members Fix the tag reference
Status Path segment error Malformed IOI: missing pad byte, wrong segment code, wrong path word count Re-encode the IOI
Status ext Element count runs past the end of the tag You asked for more elements than the member holds Read the template for the real size
Status ext Type code in the request does not match the tag Wrong type header or wrong structure handle Echo the header from the read reply

The IOI in the failing write is well formed: 91 08 + eight name bytes, 28 03 for element 3, 91 04 44 41 54 41 for the member — 18 bytes, path size 09 words. That encoding is not the fault. Stop editing it.

If the Read Returns C4, Fix the Tag Reference

A bare read of the element answering C4 00 with an Int32 means the controller resolved the path and found a DINT. A DINT has no members. That is exactly what status — path destination unknown — reports when you append 91 04 DATA to it.

What wastes time on this branch: swapping type codes, trying C2 versus D0, shortening the payload, retrying with fragmented services. None of it changes a path resolution failure.

  1. Browse the Symbol Object, class , with Get Instance Attribute List (), requesting attribute 1 (name) and attribute 2 (symbol type).
  2. Test bit in the symbol type word. Set means structure, and the lower 12 bits carry the template instance ID. Clear means atomic, and the low byte carries the type code ( DINT, SINT, REAL).
  3. Bits 13-14 give the array dimension count. Use them to decide whether an element segment is legal at all.
  4. Confirm against the project which tag holds the product name string. The same base name commonly exists as a DINT array in one controller and a string array in another; a driver that caches types across controllers lands here every time.

Never infer a tag's type from its name or from what the customer says it is. Read the symbol table once at connect, cache it, and invalidate on download.

If the Read Returns A0 02, Write the Whole Structure

A Logix STRING is a structure: LEN as a DINT at offset 0, then DATA as a SINT array at offset 4. The built-in STRING carries 82 characters; string types created in the project carry whatever length the designer chose. Two consequences drive the fix.

  • Writing only .DATA leaves LEN stale. Logix instructions and HMI faceplates render DATA[0] through DATA[LEN-1]. Write five characters over a ten-character string without touching LEN and the display shows five good characters plus five leftovers.
  • One message can carry both. Address the structure itself — no member segment — send the structured type header, element count 1, and the full structure image with LEN in the first four bytes.

Get the sizes from the controller instead of hard-coding them. Class (Template Object), Get_Attribute_List: attribute 1 returns the structure handle, attribute 2 the member count, attribute 4 the definition size in 32-bit words, attribute 5 the structure size in bytes. Read Template ( against class ) returns member names, types and offsets, which is where you read the true DATA length rather than assuming 81 or 82.

The cheap shortcut for a single tag: copy the four type-header bytes straight out of the read reply into the write request, and size the payload to the number of data bytes that read returned.

Build the 0x4D Structure Write

4D                                Write Tag Service
06                                request path size = 6 words (12 bytes)
91 08 5A 5F 50 52 44 5F 4E 4D     ANSI symbolic "Z_PRD_NM" (8 chars, even, no pad)
28 03                             element index 3
A0 02 hh hh                       structured type + template handle (echoed from read)
01 00                             element count = 1 structure
05 00 00 00                       LEN = 5
48 45 4C 4C 4F                    "HELLO"
00 ... 00                         zero fill to the structure size from template attr 5
  1. Read the tag once at first access. Cache the four-byte type header and the structure size in bytes.
  2. Set LEN to the character count you are writing — not the buffer size, not the array size.
  3. Copy the characters into the image starting at byte offset 4.
  4. Zero-fill to the full structure size. A short payload draws status (not enough data); an over-long one draws (too much data).
  5. Send it and check the reply header: CD 00 00 00 is service 0x4D with bit 7 set, reserved byte, general status 0, no additional status.
  6. For a block of strings, keep element count equal to the number of structures and payload equal to count x structure size. When that exceeds the negotiated connection payload, switch to Write Tag Fragmented () and carry the byte offset yourself.

Get the Count, the Pad Byte and the Path Size Right

  • Element count is elements, not bytes. C2 00 52 00 plus 82 payload bytes declares 82 SINTs. If the string type in that project carries fewer characters than that, the controller answers with extended status — the count runs past the end of the tag. An off-by-one against a hard-coded 81 or 82 fails the same way.
  • Odd-length symbolic names need a trailing pad. Z_PRD_NM is eight characters, even, no pad. A seven-character name encodes as 91 07 + name + 00. Miss that and you get status , not .
  • Element segment width follows the index. 28 nn up to 255, 29 00 + 16-bit value beyond that, 2A 00 + 32-bit for very large arrays.
  • Path size is words counted after the size byte. Twelve path bytes means 06. Get it wrong by one and the controller parses your type code as part of the path — which returns or and sends you hunting in the payload.
  • Template instance IDs move on download. Re-read the symbol and template objects after any program change; a cached handle that no longer matches produces .

Verify It, Then Break It on Purpose

  1. Confirm the write reply is CD 00 00 00. Any non-zero general status means the controller rejected it — read the extended status before you change anything.
  2. Read the structure back and check LEN equals 5 and the first five DATA bytes are 48 45 4C 4C 4F.
  3. Monitor the tag in the programming software. It should show HELLO with no trailing characters.
  4. Regression test the LEN path: write HELLO over an existing ten-character value. If the display shows HELLO, your LEN write is real. If it shows HELLO followed by the tail of the old value, you are still writing DATA only.
  5. Write an empty string — LEN 0, DATA zero-filled — and confirm the tag renders blank.
  6. Capture the exchange in Wireshark with the EtherNet/IP dissector and confirm path size, type header, element count and payload length on the wire match what your buffer builder intended.

Stop debugging your own encoder once the read reply, the template attributes and your write request agree byte for byte and the controller still returns or . At that point the disagreement is between your request and the controller's tag database, and it needs the project file. Open a case with Rockwell Automation technical support with the Wireshark capture of the failing exchange, the controller catalog number and firmware revision, and the tag's declared data type from the project.

FAQ

Status is path destination unknown — the controller resolved part of the path but not the member you appended. Either the element is not a structure at all, or the member name does not exist in that string type; verify with a bare read of the element and check the returned type header.

Why does reading a STRING tag return type C4 and a DINT value?

Because the tag is a DINT in that controller, not a string. A structured tag answers with A0 02 plus a two-byte template handle followed by the raw structure image; C4 00 plus four bytes is an atomic DINT, and no string write will ever succeed against it.

Why does the string still show old characters after a successful DATA write?

You wrote DATA but left LEN unchanged, and Logix renders characters from DATA[0] through DATA[LEN-1]. Write the whole structure in one message so LEN and DATA update together.

The element count in your request runs past the end of the tag or member. Read the template structure size (class , attribute 5) and the DATA member length instead of hard-coding 81 or 82 characters.

How do I get the structure handle for a STRING tag?

Read the tag once with Read Tag () and copy the four-byte header A0 02 hh hh from the reply, or query the Template Object (class ) attribute 1. Cache it per controller and re-read it after any program download.

Back to blog