LearnSCADA

Reference · Appendices A–E

Quick Reference

The book's appendices in one place: material to return to long after the first read.

A · Function Code Reference

The eight common codes do almost all everyday work; the rest appear mainly on specific devices. Codes are shown in decimal and hex, since manuals vary.

The common function codes

DecHexNameActs on
10x01Read CoilsCoils (bit, R)
20x02Read Discrete InputsDiscrete inputs (bit, R)
30x03Read Holding RegistersHolding regs (R)
40x04Read Input RegistersInput regs (R)
50x05Write Single CoilCoils (bit, W)
60x06Write Single RegisterHolding regs (W)
150x0FWrite Multiple CoilsCoils (bit, W)
160x10Write Multiple RegistersHolding regs (W)

Less-common function codes

DecHexName
70x07Read Exception Status (serial)
80x08Diagnostics (serial)
110x0BGet Comm Event Counter (serial)
120x0CGet Comm Event Log (serial)
170x11Report Server ID
200x14Read File Record
210x15Write File Record
220x16Mask Write Register
230x17Read/Write Multiple Registers
240x18Read FIFO Queue

Request and response data fields

For the common codes, the PDU is the function code byte followed by these fields (byte counts in parentheses). All multi-byte values are big-endian, high byte first.

CodeRequest dataResponse data
0x01 / 0x02start addr (2), qty (2)byte count (1), packed bits
0x03 / 0x04start addr (2), qty (2)byte count (1), values (2×N)
0x05addr (2), value (2)echo of request
0x06addr (2), value (2)echo of request
0x0Fstart (2), qty (2), bytes (1), bitsstart (2), qty (2)
0x10start (2), qty (2), bytes (1), valuesstart (2), qty (2)

For Write Single Coil (0x05), the value is 0xFF00 for ON and 0x0000 for OFF; no other value is valid. Coil reads return bits packed eight per byte, the first requested coil in the least significant bit.

Per-request limits

OperationMaximum per request
Read coils / discrete inputs2000 bits
Read holding / input registers125 registers
Write multiple coils1968 bits
Write multiple registers123 registers
PDU size253 bytes

Frame sizes for a register read (FC03 / FC04, N registers)

TransportRequestResponseWrapping
RTU8 bytes5 + 2N bytesunit address + PDU + 2-byte CRC
TCP12 bytes9 + 2N bytes7-byte MBAP header + PDU, no CRC

An exception response returns the function code with its high bit set (the original plus 0x80), followed by one exception code byte (Appendix B). A failed 0x03 returns 0x83.

↑ Back to top

B · Exception Code Reference

When a server understands a request but can't perform it, it returns the requested function code with its high bit set (code + 0x80), then one of these codes. A 0x03 request fails as 0x83, a 0x10 as 0x90.

CodeNameMeaning
01Illegal FunctionThe device does not support this function code
02Illegal Data AddressThe address (or address range) does not exist here
03Illegal Data ValueA value or quantity is outside the allowed range
04Server Device FailureAn unrecoverable error occurred in the device
05AcknowledgeRequest accepted; long processing under way, poll later
06Server Device BusyThe device is busy; resend the request later
08Memory Parity ErrorA memory error during a file-record access
0AGateway Path UnavailableA gateway is misconfigured or overloaded
0BGateway No ResponseThe gateway reached the target, which did not reply

Diagnosing the common three

  • 02 Illegal Data Address: the most common, and almost always an addressing error: a forgotten 4xxxx reference-number conversion, a count running past the end of a table, or the wrong table entirely.
  • 03 Illegal Data Value: a rule was broken, such as asking for more than 125 registers or writing a coil value other than 0xFF00/0x0000. It's about a number's validity for this request, not its meaning.
  • 01 Illegal Function: the device simply doesn't implement the function code you sent; check its manual for supported codes.

An exception is better news than silence: it proves the device received and understood the request. A request that draws no reply at all is a connection problem, not an exception; see Appendix D and Chapter 16.

↑ Back to top

C · pymodbus Quick Reference

The pymodbus patterns used in this course, for pymodbus version 3. This course pins 3.7.4:

TERMINAL — install the pinned version
pip install "pymodbus==3.7.4" pyserial
pymodbusThe course's code
3.6, 3.7Runs unchanged (the course pins 3.7.4)
3.8 – 3.9zero_mode removed
3.10+Renames: ModbusSlaveContext → ModbusDeviceContext, slaves= → devices=, slave= → device_id=
3.15Datastore classes deprecated ahead of v4

When something fails, check your installed version with pip show pymodbus.

Connecting

PYTHON — TCP client
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient("192.168.1.50",
                         port=502)
client.connect()
PYTHON — serial (RTU) client
from pymodbus.client import ModbusSerialClient
client = ModbusSerialClient(
    port="/dev/ttyUSB0", baudrate=9600,
    parity="N", stopbits=1, bytesize=8)
client.connect()

Reading

PYTHON — reads
rc = client.read_coils(0, count=8)
rd = client.read_discrete_inputs(0, count=8)
rh = client.read_holding_registers(0, count=4)
ri = client.read_input_registers(0, count=2)

if not rh.isError():
    print(rh.registers)   # list of ints
if not rc.isError():
    print(rc.bits)        # list of bools

On a serial bus, add the unit address to each call, e.g. read_holding_registers(0, count=4, slave=1).

Writing

PYTHON — writes
client.write_coil(0, True)
client.write_register(0, 1500)
client.write_coils(0, [True, False, True])
client.write_registers(0, [10, 20, 30])

Decoding wide values

PYTHON — struct decoders
import struct

def to_signed16(v):
    return v - 65536 if v >= 32768 else v

def regs_to_float(regs, order="big"):
    if order == "little":
        regs = [regs[1], regs[0]]
    raw = struct.pack(">HH", regs[0], regs[1])
    return struct.unpack(">f", raw)[0]

Here order is the word order: "big" means the high word is in the first register; "little" means the low word comes first.

A minimal server

PYTHON — server
from pymodbus.server import StartTcpServer
from pymodbus.datastore import (
    ModbusSequentialDataBlock,
    ModbusSlaveContext, ModbusServerContext)

block = ModbusSequentialDataBlock(0, [0]*100)
dev = ModbusSlaveContext(
    di=block, co=block, hr=block, ir=block,
    zero_mode=True)
ctx = ModbusServerContext(slaves=dev,
                          single=True)
StartTcpServer(context=ctx,
               address=("0.0.0.0", 5020))

This sketch shares one block across all four tables for brevity, so a write to a holding register also shows up as an input register; give each table its own block (as in Chapter 12) when that matters. Always close a client with client.close() when finished, and check the result of client.connect(), which returns False if the server can't be reached.

↑ Back to top

D · Wiring and Settings Reference

The physical side of Modbus RTU over RS-485. The protocol logic is identical everywhere; these are the hardware details that change from site to site.

RS-485 wiring

  • A two-wire RS-485 bus is a daisy chain, not a star: the pair runs device to device in one line.
  • Connect A to A and B to B across all devices; join the signal grounds. If labels disagree between makers and you get silence, try swapping the two data wires.
  • Place a termination resistor (typically 120 Ω) at each far end of the line on longer or faster runs; short bench links usually work without.
  • Add bias resistors somewhere on the bus to hold the line in a known idle state between messages when devices behave erratically.
RS-485 wiring. A USB-to-RS-485 adapter at the left end and three devices are daisy-chained on one twisted pair, A to A and B to B, with a common ground wire. A 120 ohm termination resistor sits across A and B at each far end only. A pulse travels along the pair. USB to RS-485 (client) device 1 device 2 device 3 ABG ABG ABG ABG 120 Ω 120 Ω terminate the two far ends only · daisy chain, no stars AB end of line end of line
A two-wire RS-485 bus: one pair daisy-chained A to A and B to B, signal ground joined, 120 Ω across the pair at each far end. At the right end the terminator sits at device 3's terminals.

Serial settings

Both ends of a serial link must use identical settings. Modbus RTU always uses 8 data bits. Settings are written compactly: 9600 8-E-1 means 9600 baud, 8 data bits, even parity, 1 stop bit.

SettingCommon values
Baud rate9600, 19200, 38400, 115200
Data bits8 (always, for RTU)
ParityNone, Even, Odd (Even is the default)
Stop bits1, or 2 when parity is None

Either way each byte is an 11-bit character (start + 8 data + parity + 1 stop, or start + 8 data + 2 stops). Devices that use 8N1 (10-bit characters) are common too; they simply must match.

USB-to-RS-485 adapters on Linux

  • A plugged-in adapter usually appears as /dev/ttyUSB0. Confirm with ls /dev/ttyUSB* or dmesg | grep ttyUSB.
  • Serial-port access requires membership in the dialout group: sudo usermod -a -G dialout $USER, then log out and back in.
  • A "permission denied" on the port almost always means the dialout membership is missing, not a wiring fault.
TERMINAL — find the adapter and grant access
ls /dev/ttyUSB*
dmesg | grep ttyUSB
sudo usermod -a -G dialout $USER

Modbus TCP defaults

ItemValue
Standard port502
Practice port (this course)5020 (no admin rights needed)
Secure variant (Modbus/TCP Security)TLS with certificate authentication, port 802
Unit identifier1 or 0xFF for native devices; selects the target behind a gateway

Quick silence checklist

When a bus returns nothing, check in order, most common first.

SerialTCP
1. Port name1. IP address and reachability
2. Port permission2. Port
3. Serial settings (baud and parity)3. Firewall on 502
4. Unit address4. Unit identifier, if a gateway is involved
5. Wiring (try an A/B swap)

On Windows, Test-NetConnection <ip> -Port 502 checks TCP reachability and the port in one line.

↑ Back to top

E · Glossary

Concise definitions of the terms used in the course. For deeper treatment, see the chapter that introduces each: the data model in Part I, frames and transports in Part II, the hands-on code in Part III, and deployment in Part IV.

TermDefinition
ADUApplication Data Unit: a complete frame, meaning the address/header, the PDU, and any error check.
ASCII (Modbus)A serial form that sends each byte as two hex text characters, framed by a colon and a carriage return/line feed, checked by an LRC.
Baud rateSerial speed in bits per second; must match at both ends.
Bias resistorA resistor that holds an idle RS-485 line in a defined state between messages.
Big-endianByte order in which the most significant byte is sent first. Modbus uses it within every data field (the RTU CRC is the exception: low byte first).
BroadcastA write to unit address 0, meant for all serial devices at once; draws no response.
Client (master)The device that initiates requests; the only party that may start a conversation.
CoilA single read/write bit, typically an output you can command on or off.
CRCCyclic redundancy check: the two-byte checksum on an RTU frame, sent low byte first.
Data blockIn a server, the storage for one of the four tables (di, co, ir, hr).
Discrete inputA single read-only bit, typically a sensed status.
Exception responseA reply marking refusal: the function code with its high bit set, plus an exception code.
Float (IEEE 754)A 32-bit real number spread across two registers; subject to word order.
FrameOne complete message as bytes on the wire.
Function codeThe one-byte verb of a request, naming the operation (e.g. 0x03 read holding registers).
GatewayA device bridging Modbus transports, commonly TCP to serial RTU; routes by unit ID.
Half-duplexOne pair of wires carrying traffic in both directions, one transmitter at a time (RS-485).
Holding registerA 16-bit read/write register, typically a setting or value you can change.
Input registerA 16-bit read-only register, typically a measured value.
LRCLongitudinal redundancy check: the one-byte checksum used by Modbus ASCII.
MBAP headerThe 7-byte Modbus TCP header: transaction ID, protocol ID, length, unit ID.
PDUProtocol Data Unit: the function code plus data; the transport-independent core of a message.
PollingRepeatedly sending requests in a loop to keep a picture of devices current.
Reference numberConventional addressing (4xxxx, 3xxxx, etc.); strip the leading digit and subtract 1 for the protocol address.
RegisterA 16-bit value slot (0–65535); the native unit of Modbus numeric data.
RS-485A differential two-wire serial standard, the usual physical layer for Modbus RTU.
RTU (Modbus)The compact binary serial form, framed by silence and checked by a CRC.
SCADASupervisory Control and Data Acquisition: software that monitors and controls a process at scale.
ScalingStoring a fractional value as a scaled integer (e.g. 235 for 23.5); the factor comes from the register map.
Server (slave)The device that answers requests; never speaks unless asked.
Termination resistorA resistor (≈120 Ω) at each end of an RS-485 line to prevent reflections.
Transaction IDA number in the MBAP header that pairs a TCP response with its request.
Two's complementThe scheme for signed integers; a 16-bit value ≥ 32768 represents a negative number (subtract 65536).
Unit address / IDThe number identifying which device a request is for (the serial address; the TCP unit ID).
Word orderWhich register holds the high half of a multi-register value; not universally agreed, a common source of garbled values.
Zero modeA pymodbus server setting making client address N map directly to slot N (clean zero-based addressing).

No terms match that filter.

↑ Back to top