· 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.

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.,
0x03becomes0x83).
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,
ONis0xFF00andOFFis0x0000. Any other value is illegal, although some lax implementations tolerate0x0001as 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
| Code | Name | Access Type | Memory Area |
|---|---|---|---|
| 01 | Read Coils | Read | 0xxxx (Outputs) |
| 02 | Read Discrete Inputs | Read | 1xxxx (Inputs) |
| 03 | Read Holding Registers | Read | 4xxxx (R/W) |
| 04 | Read Input Registers | Read | 3xxxx (Read Only) |
| 05 | Write Single Coil | Write (1 bit) | 0xxxx |
| 06 | Write Single Register | Write (1 word) | 4xxxx |
| 15 | Write Multiple Coils | Write (N bits) | 0xxxx |
| 16 | Write Multiple Registers | Write (N words) | 4xxxx |
Industrial Conclusion
In the plant, reliability is non-negotiable.
- Always use 0x10 instead of 0x06 if there is even the slightest chance the data might grow to 32 bits in the future.
- Handle Modbus exceptions (
0x83,0x84, etc.) in your code. A timeout is not the same as an “Illegal Data Address”. - Validate atomicity.
For critical real-time implementations, check my guides on RS485 latency optimization.



