Programming AutomationDirect PLCs Without ST, UDTs or FBs

Brian Holt8 min read
AutomationDirectHMI ProgrammingTechnical 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

AutomationDirect stated in 2017 that it had no plans to implement IEC 61131-3 in its PLCs, so a project that assumes structured text, function block diagram, user-defined functions or user-defined types has to be checked against the current programming software before any code is written. That statement is a roadmap position from a legacy forum post, not a feature list. Read the current software documentation to see what the editor supports today. The sections below build the workaround in order, and each ends with a check that must pass before the next.

Confirm what the current software supports before writing any logic

Do not chase a firmware update, a hidden ST editor or a file-import filter hoping ST appears. IEC 61131-3 defines five languages (ladder diagram, function block diagram, structured text, sequential function chart, instruction list) plus user-defined function blocks, functions and data types. A controller that does not implement the standard does not offer them as a group, and code from an IEC-based runtime does not paste across.

Open the current programming software help and release notes for your controller. Fill in this table with yes/no answers and the software version you read them from.

Capability Why the project needs it Where to read the answer
Structured text editor Array loops, string handling, math-heavy routines Language or editor list in the software help
Function block diagram Existing FBD code, block-style reuse Language or editor list
Sequential function chart / Grafcet Sequences written as steps and transitions Language or editor list
User-defined block with private memory Per-instance timers, edge memory, latches Instruction set and user-block documentation
User-defined types: nesting, arrays inside, arrays of types Equipment records such as pump or valve structures Data-type and tag documentation

Check: every row has a yes/no with a date and software version. Rows marked no drive the next section.

Count the ST, UDT and block dependencies before you commit

AutomationDirect's 2016 survey, run by itself and with Control Engineering, drove the no-plans position. 85% of Control Engineering respondents and 75% of AutomationDirect customers were not familiar or only somewhat familiar with IEC 61131-3. Respondents rated ladder diagram most important (85%), then function block diagram (49%), custom function blocks (48%), structured text (29%), sequential function charts (27%), instruction list (23%), C (10%) and Basic (10%).

Those numbers describe a ladder-first user base. Integrators who work across brands report a different mix: some ST on most non-AutomationDirect projects, at roughly 60/40 ladder to ST. They also rank nested user-defined types, arrays inside types, arrays of types, and user-defined blocks with private static memory (as in the Siemens S7 series) as very important. If your standard project looks like that second profile, the workaround below is a stopgap.

Dependency in your design Fit on a ladder-only controller Action
Timers, counters, contact logic, simple math Native Write as ladder
Loop-heavy, array or string routines High effort, timing changes Convert (next section) or change platform
SFC / Grafcet sequences No editor Rebuild as a step-number state machine
Repeated equipment blocks with private state Copy per instance by hand Dedicated memory per copy
Nested types, arrays in types, arrays of types No equivalent data structure Address-stride map; change platform if the design depends on true UDTs

Check: the table is filled for your actual project. Any design that depends on nested UDTs or static-variable blocks at scale is a platform-change decision. Stop here and make it before building anything.

Convert ST routines to ladder instead of porting code

The quick fix is to transcribe ST line by line into long ladder rungs. It fails in two places: exclusive branches and loops.

  1. IF / ELSIF / ELSE: build mutually exclusive rungs with compare contacts, so only one branch energizes per scan.
  2. CASE: hold the selector in one step register and use one compare-equal contact per case.
  3. Boolean assignments: convert to series/parallel contacts.
  4. Math assignments: use the math or expression instruction in your instruction set. Confirm from the instruction reference that it accepts the operators and data types you need.
  5. FOR loops: a loop finishes inside one scan in ST. In ladder you either unroll it, which adds scan time, or process one index per scan, which changes response time. Pick per routine and document it.

Rung order matters. Ladder executes top to bottom, so a rung that reads a value written further down reads the previous scan's value, where ST would have used the value just assigned. Place producers above consumers.

Check: bench-test each converted routine with an input vector that includes boundary values and compare outputs to the original ST or to hand-calculated results. Test the per-scan-index case across a full sweep of the index.

Replace user-defined types with an address-stride map

Without nested types, arrays inside types or arrays of types, an equipment record becomes a flat block of addresses with a fixed stride per instance. Define the map once, on paper, before any rung refers to it.

addr(field) = BASE + (n - 1) * STRIDE + field_offset

Example layout (illustrative values, not product addresses):
  STRIDE = 4
  field_offset: Run = 0, Fault = 1, Setpoint = 2, Actual = 3

Range rule for an array of N records:
  last_used = BASE + (N - 1) * STRIDE + max(field_offset)
  last_used must be less than the BASE of the next block

The wrong fix is a naming convention with no address discipline, such as P01_Run, P02_Run assigned wherever free memory happens to be. It works until someone adds instance 9 and no indexed loop can reach it. Use consistent stride even if some fields sit unused.

Check: for every block, calculate last_used with the formula above and confirm it stays below the next block's base. Cross-reference the software's address usage view for overlaps.

Give every repeated block its own memory

A function block instance carries static variables: previous-scan state for edge detection, timer accumulators, latched states. Without user blocks, that memory does not exist per call. A subroutine called for two machines shares one set of one-shot bits and one timer, so the second call overwrites the first call's edge memory and the timer restarts or misses. The symptom is one machine that works and a second that intermittently misses starts or trips early.

  1. Assign each instance its own timer, one-shot, latch and step-register addresses from the stride map.
  2. Copy the ladder block per instance, or use one indexed block if your instruction set supports indirect addressing. Verify indirect support in the instruction reference before relying on it.
  3. Place any retentive state, such as latched faults or step registers, in memory that the controller retains through power loss. Check the memory type in the software's memory documentation.

Check: force instance 1 and instance 2 into opposite states at the same time. Start both, stop one, fault the other. Each must respond independently, then swap the states and repeat.

Flag the workaround as temporary in the project

Separate the two fixes in the paperwork. The temporary restore is the ladder conversion plus the stride map on this controller. The permanent repair is a platform that implements structured text, nested and array-capable user-defined types, and user blocks with private static memory.

  • Keep the original ST source outside the PLC as the reference for the converted ladder.
  • Put a header comment on each duplicated block naming its source block and the instance number.
  • Stop building workarounds when a change to one block has to be copied by hand into every instance, or when the memory map needs a redesign for each new machine type. That is a platform decision, not a programming one.

Check: the documentation lists every duplicated block with its source, so a future edit to one is applied to all.

Run the end-to-end check before release

  1. Re-read the capability table against the software version you are shipping and confirm the dated yes/no answers still hold.
  2. Replay the converted-routine test vectors from the conversion step on the final program, not on the bench copy.
  3. Run the two-instance isolation test on every repeated block, including the last instance in the stride map.
  4. Cycle power and confirm retentive step registers and latches restore to the intended state and that non-retentive memory clears as designed.
  5. Read scan time from the controller diagnostics with unrolled loops and duplicated blocks in place, and compare it to the watchdog or scan-time limit shown in the software. Look up the limit; do not assume one.
  6. Recalculate last_used for each array with the range rule and confirm no overlap.

FAQ

Why does AutomationDirect not offer structured text or function blocks in its PLCs?

A 2017 statement said its 2016 survey showed limited IEC 61131-3 acceptance: 85% of Control Engineering respondents and 75% of its customers were unfamiliar or only somewhat familiar with the standard. It concluded implementing it was not warranted then. That is a 2017 position, so check the current software help for what the editor supports today.

Why does one ladder subroutine misbehave when I call it for two machines?

The subroutine shares one set of timer, one-shot and latch addresses across both calls, so the second call overwrites the first's edge memory. Give each machine its own set of addresses from an address-stride map and test both in opposite states at once.

When should I stop and call AutomationDirect support?

Call official AutomationDirect technical support when the capability table has an unresolved row, and have the programming software version and controller model ready to ask whether the current release supports ST, nested user-defined types or user blocks with private memory. If the design needs nested types or static-variable blocks and support cannot confirm them, stop converting and select a platform that implements them. Do not release a ladder workaround that has not passed the two-instance isolation and power-cycle checks.

Back to blog