Modbus Protocol for Industrial IoT

Learn the protocol that runs factories: Modbus data tables (coils, discrete inputs, input and holding registers), RTU vs TCP, function codes, the notorious addressing off-by-one, RS-485 wiring rules, and a practical ESP32-to-energy-meter bring-up sequence.

Written by Projectech7 min readPublished
For B.E./B.Tech Electrical, Electronics and IoT students interfacing ESP32/Arduino with PLCs, energy meters, VFDs or other industrial equipment Topics: Modbus, RS-485, Industrial IoT, PLC, ESP32
Illustration of industrial Modbus networking: an energy meter, a VFD and a PLC connected over an RS-485 twisted-pair bus to an ESP32 gateway module.
Illustration generated for this guide.
In this guide

Your energy meter has an RS-485 port. Your VFD's manual mentions "Modbus RTU." The PLC in the lab speaks nothing else. Modbus is the 40-year-old protocol that still runs the world's factories — and it is probably the most useful industrial protocol a student can learn, because the moment your project touches real industrial hardware, Modbus is how you talk to it.

This guide explains the Modbus model (coils, registers, and why they exist), RTU vs TCP, the request/response transaction, addressing pitfalls, RS-485 wiring, and how to get an ESP32 or Arduino talking to a meter or PLC.

What Modbus is

Modbus is an open serial communication protocol created in 1979 for programmable logic controllers. Its longevity comes from radical simplicity: a master-slave (client-server) model where the master sends requests and slaves respond. No discovery, no publish/subscribe, no security layer — just "read these registers" and "write this coil."

That simplicity is also its limitation: Modbus has no authentication or encryption. It assumes a trusted local network (a factory floor, a control panel). Never expose raw Modbus to the internet — put a gateway with proper security in front of it.

The data model: coils, inputs, registers

Modbus devices expose four data tables. This addressing is the first thing to internalize:

Table Access Size Meaning
Coils Read/write 1 bit Outputs — relay states, run commands
Discrete inputs Read-only 1 bit Digital inputs — limit switches, status bits
Input registers Read-only 16 bit Measurements — temperature, voltage, flow
Holding registers Read/write 16 bit Setpoints, configuration — target speed, thresholds

A temperature sensor reading lives in an input register; a motor start command goes to a coil; a speed setpoint goes to a holding register. When a manual says "temperature at address 30001," it means input register 1 (the leading digit encodes the table — more on the addressing confusion below).

Values wider than 16 bits (32-bit floats, 32-bit integers) occupy two consecutive registers, and the byte/word order (endianness) varies by manufacturer. Reading a float that comes back as garbage almost always means the word order is swapped — the single most common Modbus integration bug.

RTU vs TCP vs ASCII

Variant Transport Framing Use
Modbus RTU Serial (RS-485/RS-232) Binary, CRC-checked Field devices: meters, VFDs, sensors
Modbus TCP Ethernet/IP MBAP header, no CRC (TCP handles it) PLCs, SCADA, gateways
Modbus ASCII Serial ASCII text Rare today; mentioned for completeness

RTU over RS-485 is what you will meet on meters, VFDs, and field instruments. Modbus TCP is what PLCs and SCADA software speak over Ethernet. Gateways translate between them. For student projects, RTU-to-your-microcontroller is the typical starting point, with TCP appearing when you connect to lab PLCs.

The transaction: function codes

Every exchange is: master sends a request frame (slave address + function code + data), slave responds or stays silent. The essential function codes:

Code Name Does
01 Read Coils Read binary outputs
02 Read Discrete Inputs Read binary inputs
03 Read Holding Registers Read configuration/setpoint registers
04 Read Input Registers Read measurement registers
05 Write Single Coil Set one output on/off
06 Write Single Register Write one setpoint
15 / 16 Write Multiple Coils/Registers Batch writes

A master reading a meter's voltage might send function 04 for 2 input registers; the meter replies with 4 bytes that decode to a float. Libraries (ModbusMaster for Arduino, ESP-IDF components, pymodbus on the PC side) handle framing and CRC — you work at the function-code level.

The addressing trap: off-by-one

Modbus documentation is notorious for two numbering conventions:

  • Protocol addresses are zero-based: the first holding register is address 0.
  • Documentation addresses are often one-based with a table prefix: holding register "40001" means protocol address 0.

So "read holding register 40001" in the manual = request address 0 in the protocol. Some libraries expect zero-based, some accept the 40001 style and subtract. When reads return the wrong value or an exception, check this first — it is the cause more often than wiring.

Note: always verify against the device's own manual, and test with a PC-based Modbus scanner tool before writing firmware. Confirming the addresses work from a laptop isolates protocol issues from embedded issues.

RS-485 wiring: the physical layer

Modbus RTU almost always runs on RS-485: differential signaling over a twisted pair (A/B, sometimes labeled D+/D−), multi-drop (up to 32 devices on one bus), and tolerant of industrial noise. Wiring rules that prevent 90% of field problems:

  • Twisted pair, daisy-chained — no star topology; the bus runs device to device.
  • 120 Ω termination resistors at both ends of the bus (many modules have a jumper for this).
  • Common ground between devices — RS-485 is differential but still needs a ground reference.
  • Biasing on long or noisy buses to keep the line in a defined state when idle.
  • Baud rate and parity must match on every device: 9600 8-N-1 is the common default; check each device's settings.

The ESP32/Arduino needs an RS-485 transceiver module (MAX485-based boards are standard) between its UART pins and the bus — the microcontroller's UART is TTL-level, not differential. The transceiver's DE/RE pins control transmit/receive direction; libraries or a timer handle the switching.

Talking to a meter from an ESP32

The practical bring-up sequence:

  1. Wire it: ESP32 UART → RS-485 module → twisted pair → meter's A/B terminals, termination jumpers set, common ground.
  2. Configure the meter: set its slave address, baud rate, and parity from the meter's front panel or manual — write these down.
  3. Scan from a PC first: a USB-RS-485 adapter plus a Modbus scanner tool confirms the meter responds and your register addresses are right.
  4. Port to firmware: replicate the working scanner settings in the ModbusMaster library — same slave ID, baud, function codes, addresses.
  5. Decode carefully: apply the manual's scaling ("value × 0.1 = volts") and check 32-bit word order against a known reading.

A minimal Arduino-side read looks like this (ModbusMaster library):

#include <ModbusMaster.h>

ModbusMaster node;

void setup() {
  Serial2.begin(9600, SERIAL_8N1);  // UART to RS-485 module
  node.begin(1, Serial2);          // slave ID 1
}

void loop() {
  uint8_t result = node.readInputRegisters(0x0000, 2);  // 2 regs from address 0
  if (result == node.ku8MBSuccess) {
    uint16_t rawHigh = node.getResponseBuffer(0);
    uint16_t rawLow  = node.getResponseBuffer(1);
    // combine per manual's word order, apply scaling
  }
  delay(1000);
}

Modbus TCP and gateway patterns

When the other end is a PLC or SCADA system on Ethernet, you will meet Modbus TCP. The data model is identical (coils, registers, function codes) — only the transport changes: a TCP connection instead of serial, a header instead of CRC, and much higher throughput.

Two patterns for student projects:

ESP32 as Modbus TCP client. Libraries exist for Modbus TCP on the ESP32 over WiFi. The ESP32 polls the PLC's registers on a schedule and publishes the readings to MQTT — a protocol bridge from the factory floor to your dashboard. Mind the polling rate: PLCs expect deterministic timing, so keep Modbus polling on a dedicated task with a fixed interval rather than mixed into WiFi-handling code.

RTU-to-TCP gateway. A small device (a Raspberry Pi or even an ESP32) speaks RTU to field instruments on one side and serves them as Modbus TCP on the other. This is how serial-only meters join an Ethernet-based lab setup without rewiring the field bus.

A stronger security reminder here: Modbus TCP networks are IP networks, which means they are scannable and routable. The no-authentication property of Modbus that was merely unwise on a serial cable becomes genuinely dangerous on Ethernet. Keep Modbus TCP on an isolated network segment or behind a VPN, and never expose it directly to the internet — use a proper authenticated gateway for remote access.

Common mistakes checklist

  • Off-by-one addressing. Manual's 40001 = protocol address 0. Verify with a scanner first.
  • Swapped 32-bit word order. Garbage floats = endianness mismatch until proven otherwise.
  • Missing termination. Reflections on an unterminated bus cause intermittent CRC errors.
  • Star wiring. RS-485 must be daisy-chained, not star.
  • Mismatched baud/parity/slave ID. Every device on the bus must agree; check each one.
  • No common ground. Differential still needs a reference.
  • Forgetting scaling factors. Raw register values are rarely engineering units — apply the manual's multiplier.
  • Exposing Modbus to the internet. No authentication exists; always gateway it.

Where to go from here

More project guides

More in IoT & Embedded