Skip to main content

ControlForge DF1 Protocol Guide

James M. Belcher Founder, JMB Technical Services LLC April 2026 | ControlForge v1.0.533


1. Architecture Overview

ControlForge implements an Allen-Bradley DF1 full-duplex serial client callable directly from IEC 61131-3 Structured Text. No RSLinx, no OPC server, no proprietary drivers. Connect a USB-to-RS-232 adapter to a SLC 500, MicroLogix 1000/1100/1400, or PLC-5 and start reading/writing data files with plain function calls.

RoleFunctionsUse Case
ClientDF1_CLIENT_CREATE / DF1_CLIENT_READ_* / DF1_CLIENT_WRITE_*Read/write SLC 500 and MicroLogix data files over RS-232
DiagnosticsDF1_CLIENT_ECHO / DF1_CLIENT_GET_DIAGNOSTIC_STATUS / DF1_CLIENT_SCAN_NODESNetwork troubleshooting and device discovery
CPU ControlDF1_CLIENT_SET_CPU_MODESwitch processor between Program, Run, and Test modes
PollingDF1_CLIENT_ADD_POLL_ITEM / DF1_CLIENT_GET_STATSAutomatic cyclic data collection with tag mapping

All functions are controlled entirely from IEC 61131-3 Structured Text in ControlForge's browser-based IDE.

System Diagram

DF1 Protocol Background

DF1 is Allen-Bradley's point-to-point serial protocol, introduced in the 1980s and still supported by every SLC 500 and MicroLogix processor. ControlForge implements DF1 full-duplex (the default for channel 0 RS-232):

FeatureFull-DuplexHalf-Duplex
TopologyPoint-to-point (1:1)Multi-drop (1:N)
Error RecoveryCRC + sequence numbers + ACK/NAKBCC + ENQ polling
ControlForge SupportYesNot yet
Typical UseProgramming port (CH0)DH-485 bridging

Data File Addressing

Allen-Bradley SLC/MicroLogix use a file:element addressing scheme:

PrefixFile TypeWord SizeAddress ExampleDescription
NInteger16-bit signedN7:0General-purpose integer storage
FFloat32-bit IEEEF8:0Floating-point storage (2 words per element)
BBit16-bit wordB3:0Bit-addressable (B3:0/0 through B3:0/15)
TTimer3 wordsT4:0CTL word + PRE + ACC
CCounter3 wordsC5:0CTL word + PRE + ACC
SStatus16-bitS:0Processor status file
IInput16-bitI:0Physical inputs
OOutput16-bitO:0Physical outputs

Addressing Note: The number after the prefix is the file number (e.g., N7 is integer file 7). The number after the colon is the element (word offset). N7:0 means "integer file 7, word 0." Default file numbers: N7, F8, B3, T4, C5, S2 — but user-created files can have any number (N9, N10, F20, etc.).


2. Connection Management

2.1 DF1_CLIENT_CREATE -- Create Named Connection

ok := DF1_CLIENT_CREATE('slc', '/dev/ttyUSB0');
ParamTypeRequiredDefaultDescription
nameSTRINGYesUnique connection name
portSTRINGYesSerial port path (/dev/ttyUSB0, COM3)
baudINTNo19200Baud rate (2400, 4800, 9600, 19200, 38400)
localNodeINTNo0DF1 source node address (0-254)
remoteNodeINTNo1DF1 destination node address (0-254)

Returns TRUE on success. The connection is created but not yet connected -- call DF1_CLIENT_CONNECT next.

(* Defaults: 19200 baud, local node 0, remote node 1 *)
ok := DF1_CLIENT_CREATE('slc', '/dev/ttyUSB0');

(* Explicit baud rate for older SLC 500 *)
ok := DF1_CLIENT_CREATE('slc', '/dev/ttyUSB0', 9600);

(* Full specification — node addresses for multi-drop scenarios *)
ok := DF1_CLIENT_CREATE('slc', '/dev/ttyUSB0', 19200, 0, 1);

SLC 500 Channel 0 defaults: 19200 baud, 8 data bits, no parity, 1 stop bit (8N1), full-duplex. These are the factory defaults and match ControlForge's defaults. Only change baud if you have explicitly reconfigured the SLC channel.

MicroLogix 1100/1400: Support up to 38400 baud on the built-in RS-232 port (CH0). The default is 19200.

2.2 DF1_CLIENT_CONNECT / Disconnect / IsConnected

(* Open the serial port and establish DF1 session *)
ok := DF1_CLIENT_CONNECT('slc');

(* Check connection state *)
IF DF1_CLIENT_IS_CONNECTED('slc') THEN
(* read/write operations *)
END_IF;

(* Graceful disconnect *)
DF1_CLIENT_DISCONNECT('slc');

DF1_CLIENT_CONNECT opens the serial port, configures the baud rate and framing (8N1), and sends a diagnostic status request to verify the remote node is responding.

Linux serial permissions: The ControlForge process needs read/write access to the serial port. Add the user to the dialout group: sudo usermod -aG dialout controlforge. This persists across reboots.

2.3 DF1_CLIENT_DELETE / DF1_CLIENT_LIST

(* Remove a connection *)
DF1_CLIENT_DELETE('slc');

(* List all DF1 connections *)
names := DF1_CLIENT_LIST();
(* Returns: ['slc', 'micro1', 'plc5'] *)

3. Read / Write Operations

3.1 DF1_CLIENT_READ_WORDS -- Read Multiple Words

values := DF1_CLIENT_READ_WORDS('slc', 'N7:0', 10);
(* Returns: [100, 200, 0, -32768, 1234, 0, 0, 0, 42, 999] *)
ParamTypeDescription
nameSTRINGConnection name
addressSTRINGStarting file:element address (e.g., N7:0, F8:10, B3:0)
countINTNumber of words to read (1-120)

Returns []INT — an array of 16-bit signed integers. For float files (F8:*), two consecutive words form one IEEE 754 float (low word first).

Maximum read size: 120 words per request. This is a DF1 protocol limitation — the maximum command data field is 242 bytes. For larger reads, issue multiple requests with incrementing addresses.

3.2 DF1_CLIENT_WRITE_WORDS -- Write Multiple Words

ok := DF1_CLIENT_WRITE_WORDS('slc', 'N7:20', [100, 200, 300]);
ParamTypeDescription
nameSTRINGConnection name
addressSTRINGStarting file:element address
values[]INTArray of 16-bit values to write

Returns TRUE on success.

Write protection: SLC 500 processors in RUN mode allow writes to data files (N, F, B, etc.) but not to program files. In PROGRAM mode, all files are writable. In REMOTE RUN, writes to data files are allowed and the processor can be switched to PROGRAM remotely.

3.3 DF1_CLIENT_READ_WORD / WriteWord -- Single Word Operations

(* Read a single integer *)
speed := DF1_CLIENT_READ_WORD('slc', 'N7:5');
(* Returns: 1750 *)

(* Write a single integer *)
ok := DF1_CLIENT_WRITE_WORD('slc', 'N7:20', 1234);

These are convenience wrappers around the multi-word functions. Use them when you need exactly one value — they produce the same DF1 command under the hood.


4. Diagnostics and CPU Control

4.1 DF1_CLIENT_ECHO -- Diagnostic Echo

response := DF1_CLIENT_ECHO('slc', [16#DEAD, 16#BEEF]);
(* Returns: [16#DEAD, 16#BEEF] — exact echo of sent data *)
ParamTypeDescription
nameSTRINGConnection name
data[]INTArray of words to echo (1-100)

Returns the echoed data. This is a DF1 Diagnostic Status command (CMD 0x06, FNC 0x00) — the remote node must echo the data verbatim. Use this to verify the serial link without touching PLC data files.

Troubleshooting tip: If DF1_CLIENT_ECHO fails but the serial port opens successfully, check: (1) baud rate mismatch, (2) TX/RX wires swapped, (3) wrong node address, (4) SLC channel not configured for DF1 full-duplex.

4.2 DF1_CLIENT_GET_DIAGNOSTIC_STATUS

status := DF1_CLIENT_GET_DIAGNOSTIC_STATUS('slc');
(* Returns: [status_word1, status_word2, ...] — processor-dependent *)

Returns the remote node's diagnostic status counters. The content varies by processor type — SLC 500 returns NAK/ENQ/timeout counters, MicroLogix returns similar but with different offsets.

4.3 DF1_CLIENT_SET_CPU_MODE -- Change Processor Mode

(* Switch to RUN mode *)
ok := DF1_CLIENT_SET_CPU_MODE('slc', 1);

(* Switch to PROGRAM mode *)
ok := DF1_CLIENT_SET_CPU_MODE('slc', 0);
ParamTypeDescription
nameSTRINGConnection name
modeINT0 = PROGRAM, 1 = RUN, 2 = TEST

Returns TRUE on success.

Safety warning: Switching a running SLC 500 to PROGRAM mode immediately stops all outputs. Outputs go to their configured fault state (typically OFF). Use this only during commissioning or maintenance, never in production without proper safety procedures. The TEST mode runs the program but forces all outputs OFF — useful for logic verification.

nodes := DF1_CLIENT_SCAN_NODES('slc', 0, 31);
(* Returns: [1, 5, 12] — node addresses that responded *)
ParamTypeDescription
nameSTRINGConnection name
startNodeINTFirst node address to probe (0-254)
endNodeINTLast node address to probe (0-254)

Returns []INT — an array of node addresses that responded to a diagnostic echo. This sends a minimal echo command to each address in the range and collects responses.

Scan time: Each non-responding node incurs a timeout (~500ms default). Scanning 0-31 with one active node takes ~15 seconds. Narrow your scan range when possible.


5. Automatic Polling

5.1 DF1_CLIENT_ADD_POLL_ITEM -- Register Cyclic Read

ok := DF1_CLIENT_ADD_POLL_ITEM('slc', 'N7:0', 'line_speed', 1);
ok := DF1_CLIENT_ADD_POLL_ITEM('slc', 'N7:1', 'motor_temp', 1);
ok := DF1_CLIENT_ADD_POLL_ITEM('slc', 'N7:10', 'batch_count', 1);
ok := DF1_CLIENT_ADD_POLL_ITEM('slc', 'F8:0', 'pressure', 2); (* float = 2 words *)
ParamTypeDescription
nameSTRINGConnection name
addressSTRINGFile:element address to read
tagSTRINGControlForge tag name to store the value
countINTNumber of words (1 for INT, 2 for FLOAT)

Returns TRUE on success. Once registered, ControlForge automatically reads these addresses on a cyclic schedule and updates the named tags. The poll rate is determined by the task scan time — a 100ms task polls all items every 100ms.

Poll items are coalesced into efficient multi-word reads when addresses are contiguous in the same data file. For example, N7:0 through N7:9 as 10 separate poll items will be read with a single 10-word read command.

5.2 DF1_CLIENT_GET_STATS -- Connection Statistics

stats := DF1_CLIENT_GET_STATS('slc');
(* Returns: {
"tx_count": 15234,
"rx_count": 15230,
"nak_count": 2,
"timeout_count": 4,
"crc_error_count": 0,
"retry_count": 6,
"avg_response_ms": 12,
"poll_items": 8
} *)

Returns a MAP with connection health metrics. Monitor these in your ST program to detect degrading serial links before they fail completely.

StatDescription
tx_countTotal commands sent
rx_countTotal responses received
nak_countNAK responses from remote (command rejected)
timeout_countCommands with no response
crc_error_countResponses with CRC mismatch
retry_countAutomatic retransmissions
avg_response_msAverage round-trip time
poll_itemsNumber of registered poll items

6. Complete Example: SLC 500 Data Logger

This example connects to an SLC 500 over RS-232, sets up automatic polling of production data, and writes a setpoint back:

PROGRAM POU_SLC500_DataLogger
VAR
state : INT := 0;
ok : BOOL;
speed : INT;
temp : INT;
pressure_raw : ARRAY[0..1] OF INT;
new_setpoint : INT := 1500;
stats : STRING;
END_VAR

CASE state OF
0: (* Create connection — SLC 500 on CH0, default 19200 baud *)
ok := DF1_CLIENT_CREATE('slc', '/dev/ttyUSB0');
IF ok THEN state := 1; END_IF;

1: (* Connect *)
ok := DF1_CLIENT_CONNECT('slc');
IF ok THEN state := 2; END_IF;

2: (* Verify link with echo test *)
IF DF1_CLIENT_IS_CONNECTED('slc') THEN
DF1_CLIENT_ECHO('slc', [16#1234]);
state := 3;
END_IF;

3: (* Register poll items for automatic cyclic reads *)
DF1_CLIENT_ADD_POLL_ITEM('slc', 'N7:0', 'line_speed', 1);
DF1_CLIENT_ADD_POLL_ITEM('slc', 'N7:1', 'motor_temp', 1);
DF1_CLIENT_ADD_POLL_ITEM('slc', 'N7:2', 'batch_count', 1);
DF1_CLIENT_ADD_POLL_ITEM('slc', 'F8:0', 'pressure', 2);
DF1_CLIENT_ADD_POLL_ITEM('slc', 'B3:0', 'status_bits', 1);
state := 10;

10: (* Running — read polled values and write setpoints *)
speed := DF1_CLIENT_READ_WORD('slc', 'N7:0');
temp := DF1_CLIENT_READ_WORD('slc', 'N7:1');

(* Write new setpoint if changed *)
IF new_setpoint <> speed THEN
DF1_CLIENT_WRITE_WORD('slc', 'N7:20', new_setpoint);
END_IF;

(* Monitor connection health *)
stats := DF1_CLIENT_GET_STATS('slc');
END_CASE;
END_PROGRAM

7. Complete Example: MicroLogix 1400 with Node Scanning

PROGRAM POU_MicroLogix_Setup
VAR
state : INT := 0;
ok : BOOL;
nodes : ARRAY[0..31] OF INT;
int_values : ARRAY[0..9] OF INT;
float_words : ARRAY[0..3] OF INT;
END_VAR

CASE state OF
0: (* Create — MicroLogix 1400 supports 38400 baud *)
ok := DF1_CLIENT_CREATE('ml', '/dev/ttyUSB1', 38400);
IF ok THEN state := 1; END_IF;

1: (* Connect *)
ok := DF1_CLIENT_CONNECT('ml');
IF ok THEN state := 2; END_IF;

2: (* Scan for other nodes on the link *)
nodes := DF1_CLIENT_SCAN_NODES('ml', 0, 15);
state := 3;

3: (* Read 10 integers starting at N7:0 *)
int_values := DF1_CLIENT_READ_WORDS('ml', 'N7:0', 10);
state := 4;

4: (* Read 2 floats (4 words) starting at F8:0 *)
float_words := DF1_CLIENT_READ_WORDS('ml', 'F8:0', 4);
(* float_words[0..1] = F8:0, float_words[2..3] = F8:1 *)
state := 5;

5: (* Write bit file — set B3:0 word to enable all 16 bits *)
ok := DF1_CLIENT_WRITE_WORD('ml', 'B3:0', 16#FFFF);
state := 10;

10: (* Running — cyclic read/write *)
int_values := DF1_CLIENT_READ_WORDS('ml', 'N7:0', 10);
DF1_CLIENT_WRITE_WORDS('ml', 'N7:20', [int_values[0] + 1, int_values[1]]);
END_CASE;
END_PROGRAM

8. Wiring and Hardware Setup

RS-232 Cable Pinout (DB-9)

SLC 500 and MicroLogix use a null modem connection on Channel 0:

Cable type: Use a standard null modem cable (Allen-Bradley 1761-CBL-PM02 equivalent). Pins 2 and 3 are crossed. If using a straight-through cable, you need a null modem adapter.

USB adapters: FTDI-based adapters (FT232R) are recommended. Prolific PL2303 chipsets have known Linux driver issues. ControlForge auto-detects the adapter and sets the FTDI latency timer to 1ms for responsive communication.

SLC 500 Channel 0 Configuration

Configure via RSLogix 500 under Channel Configuration > Channel 0:

ParameterSetting
DriverDF1 Full-Duplex
Baud Rate19200 (match ControlForge)
ParityNone
Error DetectionCRC
Duplicate DetectEnabled
Source Node1 (match remoteNode in ControlForge)

MicroLogix Channel Configuration

MicroLogix 1100/1400 configure Channel 0 through RSLogix 500 > Channel Configuration or via the front panel LCD. The defaults (19200, 8N1, DF1 full-duplex) work with ControlForge out of the box.


9. Troubleshooting

Common Issues

SymptomCauseFix
Connect succeeds but reads failBaud rate mismatchMatch baud in DF1_CLIENT_CREATE to SLC channel config
All reads return timeoutTX/RX wires swappedUse null modem cable or swap pins 2 and 3
Intermittent CRC errorsElectrical noise on RS-232Shorten cable, add ferrites, verify ground
NAK responses on writesProcessor in wrong modeCheck CPU mode — data file writes need RUN or REMOTE RUN
Echo works, reads failWrong node addressVerify remoteNode matches SLC Channel 0 Source Node
Permission denied on LinuxSerial port accesssudo usermod -aG dialout $USER, then log out/in
Slow scan rateToo many poll itemsCoalesce contiguous addresses; reduce poll count

DF1 Wire Protocol Reference

┌──────┬──────┬──────┬──────┬──────┬──────────────────┬───────┬───────┐
│ DLE │ STX │ DST │ SRC │ CMD │ STS │ TNS(2) │ DATA │ DLE │
│ 0x10 │ 0x02 │ 1B │ 1B │ 1B │ 1B │ LE │ 0-242 │ 0x10 │
├──────┼──────┼──────┼──────┼──────┼──────┼───────────┼───────┼───────┤
│ │ │ │ │ │ │ │ │ ETX │
│ │ │ │ │ │ │ │ │ 0x03 │
├──────┼──────┼──────┼──────┼──────┼──────┼───────────┼───────┼───────┤
│ │ │ │ │ │ │ │ │ CRC(2)│
└──────┴──────┴──────┴──────┴──────┴──────┴───────────┴───────┴───────┘
  • DLE byte stuffing: Any 0x10 in the data field is sent as 0x10 0x10
  • CRC-16: Over all bytes between (but not including) DLE/STX and DLE/ETX
  • TNS (Transaction Number): 16-bit incrementing sequence number — ControlForge manages this automatically
  • ACK/NAK: DLE+ACK (0x10 0x06) or DLE+NAK (0x10 0x15) frame acknowledgment

You never build frames manually — ControlForge handles all framing, byte stuffing, CRC calculation, ACK/NAK handshaking, and retransmission.


Appendix A: Function Quick Reference

FunctionParamsReturnsDescription
DF1_CLIENT_CREATE(name, port [, baud] [, localNode] [, remoteNode])BOOLCreate connection (default 19200, nodes 0/1)
DF1_CLIENT_CONNECT(name)BOOLOpen serial port and establish DF1 session
DF1_CLIENT_DISCONNECT(name)BOOLClose serial port
DF1_CLIENT_IS_CONNECTED(name)BOOLCheck connection state
DF1_CLIENT_READ_WORDS(name, address, count)[]INTRead multiple 16-bit words from data file
DF1_CLIENT_WRITE_WORDS(name, address, values)BOOLWrite multiple 16-bit words to data file
DF1_CLIENT_READ_WORD(name, address)INTRead single 16-bit word
DF1_CLIENT_WRITE_WORD(name, address, value)BOOLWrite single 16-bit word
DF1_CLIENT_ECHO(name, data)[]INTDiagnostic echo — verify serial link
DF1_CLIENT_GET_DIAGNOSTIC_STATUS(name)[]INTRemote node diagnostic counters
DF1_CLIENT_SET_CPU_MODE(name, mode)BOOL0=PROGRAM, 1=RUN, 2=TEST
DF1_CLIENT_GET_STATS(name)MAPConnection health metrics
DF1_CLIENT_SCAN_NODES(name, startNode, endNode)[]INTDiscover responding nodes
DF1_CLIENT_ADD_POLL_ITEM(name, address, tag, count)BOOLRegister cyclic read
DF1_CLIENT_DELETE(name)BOOLRemove connection
DF1_CLIENT_LIST()[]STRINGList all DF1 connections

ControlForge v1.0.533 | Allen-Bradley DF1 Full-Duplex | RS-232 Serial Client

© 2026 JMB Technical Services LLC. All rights reserved. Back to All Guides