Advisory Schedule a Technical Discovery Call — Book your session today! »

· Eduardo Vieira · Industrial Protocols  · 3 min read

Modbus Function Codes: Technical Guide with C and Python Examples

Master codes 0x01, 0x03, 0x06, and 0x10. Practical integration examples with Libmodbus (C) and PyModbus (Python) for embedded and industrial systems.

Master codes 0x01, 0x03, 0x06, and 0x10. Practical integration examples with Libmodbus (C) and PyModbus (Python) for embedded and industrial systems.

Modbus function codes are the “verb” in the industrial communication sentence. They define exactly what action the slave (server) must perform. In this guide, we won’t just look at what they do, but how to implement them in real production code, both for Embedded Systems (C) and Scripting/SCADA (Python).

The Function Code Byte Structure

The function code is the second byte of the Modbus frame (after the address).

  • Range: 1 to 127 (0x01 - 0x7F).
  • Exception: If the slave responds with an error, it sets the most significant bit (MSB) of the function code (e.g., 0x03 becomes 0x83).

1. Reading Data (Read)

0x01: Read Coils

  • Data Type: Bit (R/W)
  • Usage: Read status of digital outputs, relays, enables.
  • Technical Detail: Bits are packed into bytes in the response. If you request 10 coils, you receive 2 bytes.

C Implementation (Libmodbus)

uint8_t bits[10];
int rc = modbus_read_bits(ctx, 0, 10, bits);
if (rc == -1) {
    fprintf(stderr, "Read error: %s\n", modbus_strerror(errno));
} else {
    printf("Coil 0: %d\n", bits[0]);
}

0x03: Read Holding Registers

  • Data Type: Word 16-bit (R/W)
  • Usage: The “Swiss Army Knife”. Used to read setpoints, configurations, and analog values.
  • Usage: To read floats (32-bit), you request 2 consecutive registers.

Python Implementation (PyModbus)

# Read 2 registers starting at 40100 (Address 99)
rr = client.read_holding_registers(address=99, count=2, slave=1)
if not rr.isError():
    # Convert to float (considering endianness)
    decoder = BinaryPayloadDecoder.fromRegisters(rr.registers, byteorder=Endian.Big, wordorder=Endian.Big)
    temp_celsius = decoder.decode_32bit_float()

2. Writing Data (Write)

0x05: Write Single Coil

  • Usage: Force a digital output (ON/OFF).
  • Curiosity: In the raw protocol, ON is 0xFF00 and OFF is 0x0000. Any other value is illegal, although some lax implementations tolerate 0x0001 as ON.

Embedded Implementation (Zephyr RTOS - Modbus Client)

/* Write TRUE to coil 10 */
uint16_t coil_val = 0xFF00; // Constant for ON
int err = modbus_write_coil(client_iface, 1, 10, &coil_val);

0x06: Write Single Register

  • Usage: Change a simple configuration parameter (e.g., Node ID, Baudrate).
  • Limitation: Only writes 16 bits. DO NOT USE to write 32-bit floats (causes broken atomicity: you write half the float, the process reads garbage, then you write the other half).

0x10: Write Multiple Registers

  • Usage: The safe way to write complex data (Floats, Strings, Long Ints).
  • Advantage: The write is atomic within the device (generally).

Robust C Implementation

uint16_t tab_reg[2];
float setpoint = 23.5f;

// Serialize float to registers (Big Endian)
modbus_set_float_abcd(setpoint, tab_reg);

// Write atomically
modbus_write_registers(ctx, 4001, 2, tab_reg);

3. Diagnostics and Control

0x08: Diagnostics (Serial)

Rarely used today, but vital for pure RS485 debug. Sub-code 0x0000 (Return Query Data) acts like a Ping: the slave returns exactly what it received. Useful for verifying wiring integrity.


Quick Reference Table

CodeNameAccess TypeMemory Area
01Read CoilsRead0xxxx (Outputs)
02Read Discrete InputsRead1xxxx (Inputs)
03Read Holding RegistersRead4xxxx (R/W)
04Read Input RegistersRead3xxxx (Read Only)
05Write Single CoilWrite (1 bit)0xxxx
06Write Single RegisterWrite (1 word)4xxxx
15Write Multiple CoilsWrite (N bits)0xxxx
16Write Multiple RegistersWrite (N words)4xxxx

Industrial Conclusion

In the plant, reliability is non-negotiable.

  1. Always use 0x10 instead of 0x06 if there is even the slightest chance the data might grow to 32 bits in the future.
  2. Handle Modbus exceptions (0x83, 0x84, etc.) in your code. A timeout is not the same as an “Illegal Data Address”.
  3. Validate atomicity.

For critical real-time implementations, check my guides on RS485 latency optimization.

Back to Blog

Related Posts

View All Posts »