Protocols

Modbus Function Codes

By CourseTron Team ·  7 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: A Modbus request is just a slave address, a one-byte function code, a data field and a CRC; five codes (01, 03, 05, 06, 16) cover almost every real job, and any failure comes back as the same function code with bit 7 set plus a one-byte exception code.

    Why the function code is the heart of Modbus

    Modbus has no sessions, no discovery and no data typing. The only thing that tells a slave what to do is the function code. Everything after it is interpreted according to that single byte. If you can read the function code and the byte counts, you can decode any capture from a logic analyser, a USB-RS485 dongle or Wireshark without documentation. For the wider context of masters, slaves and the two serial framings, read our primer on Modbus communication.

    Modbus organises data into four tables: coils (read/write bits), discrete inputs (read-only bits), input registers (read-only 16-bit words) and holding registers (read/write 16-bit words). Function codes are simply verbs that act on those tables. Addresses on the wire are zero-based, so the register a vendor manual calls "40108" is sent as address 0x006B (107). Forgetting this off-by-one is the most common reason a first read returns a neighbouring value.

    Anatomy of an RTU frame

    Modbus RTU request: Read Holding Registers (FC 03) Address0x11 Function0x03 Start address0x006B Quantity0x0002 CRC-16B7 47 (lo, hi) 1 byte1 byte2 bytes2 bytes2 bytes On the wire: 11 03 00 6B 00 02 B7 47 (8 bytes) Silence of 3.5 character times frames the message; CRC low byte is sent first. Data fields are big-endian; only the CRC is little-endian. CourseTron · coursetron.com
    Every RTU frame is address, function, data and CRC. Only the data field changes meaning with the function code.

    Key fact: Modbus data is big-endian, but the CRC-16 (polynomial 0xA001, reflected) is transmitted low byte first. Mixing those two up is the classic reason a hand-built frame is silently ignored.

    The five workhorse function codes

    FCNameTableRequest dataExample request
    01Read CoilsCoilsStart (2), quantity (2)01 01 00 13 00 08 CC 09
    03Read Holding RegistersHolding regsStart (2), quantity (2)11 03 00 6B 00 02 B7 47
    05Write Single CoilCoilsAddress (2), FF00 = ON / 0000 = OFF01 05 00 AC FF 00 4C 1B
    06Write Single RegisterHolding regsAddress (2), value (2)01 06 00 01 00 03 98 0B
    16 (0x10)Write Multiple RegistersHolding regsStart (2), qty (2), byte count (1), values01 10 00 01 00 02 04 00 0A 01 02 92 30

    Note that function code 16 is decimal; on the wire it is the byte 0x10. Datasheets mix decimal and hex freely, which trips up many people reading a capture for the first time.

    Reading: FC 01 and FC 03 in detail

    For FC 03, the example above asks slave 0x11 for two registers starting at address 107. A typical reply is 11 03 04 02 2B 00 64 9B A9. The third byte is the byte count (4), followed by two 16-bit values: 0x022B (555) and 0x0064 (100). The master must trust its own request to know what those words mean; nothing in the reply identifies a temperature or a setpoint. A common convention is a 32-bit float or integer across two consecutive registers, and the word order (ABCD versus CDAB) is a vendor choice you have to look up.

    FC 01 packs coils into bytes, least-significant bit first. Asking slave 1 for 8 coils from address 19 returns 01 01 01 CD 90 1D. The data byte 0xCD is 1100 1101 in binary, so coils 19, 21, 22, 25 and 26 are ON (bit 0 is address 19, so reading upward gives 1, 0, 1, 1, 0, 0, 1, 1 for addresses 19 to 26). If the quantity is not a multiple of eight, the unused high bits of the last byte are zero-padded.

    Writing: FC 05, 06 and 16

    Single writes are echoed. FC 05 to coil 0x00AC with value 0xFF00 switches it on, and a healthy slave replies with the identical eight bytes. Only 0xFF00 and 0x0000 are legal; 0x0001 is an illegal data value, a frequent bug when engineers reuse integer code. FC 06 behaves the same way for a single holding register: writing 3 to address 1 produces an exact echo of 01 06 00 01 00 03 98 0B.

    FC 16 is the one to prefer for multi-register data. Writing 0x000A and 0x0102 to addresses 1 and 2 sends 01 10 00 01 00 02 04 00 0A 01 02 92 30. The reply does not echo the data, only the start address and quantity: 01 10 00 01 00 02 10 08. Writing a 32-bit setpoint with two separate FC 06 calls is dangerous, because a drive may act on the first half before the second arrives. A single FC 16 write is applied atomically by most devices.

    Request / response round trip (FC 03) Master PLC / SCADA Slave 0x11 Meter / drive Request 11 03 00 6B 00 02 B7 47 Response 11 03 04 02 2B 00 64 9B A9 Blue = request frame, amber = response. Only the addressed slave answers; broadcast (address 0) gets no reply. CourseTron · coursetron.com
    Modbus is strictly master-initiated: one request, one response, then silence until the next poll.

    Exception responses

    When a slave understands the frame but cannot carry it out, it replies with the original function code OR-ed with 0x80, followed by a single exception code. A failed FC 03 therefore comes back with function byte 0x83. If the master asks slave 1 for a register that does not exist, the reply is 01 83 02 C0 F1: address 1, function 0x83, exception 02 (Illegal Data Address), CRC. The CRC is still computed over the whole short frame, so a corrupted exception is rejected like any other message.

    CodeNameTypical cause
    01Illegal FunctionDevice does not implement that FC (e.g. FC 16 on a read-only meter)
    02Illegal Data AddressStart plus quantity runs outside the mapped range; often an off-by-one
    03Illegal Data ValueQuantity too large (over 125 registers for FC 03) or coil value not FF00/0000
    04Slave Device FailureInternal fault while executing the action
    06Slave Device BusyLong job in progress; master should retry later
    Normal reply versus exception reply Normal 0103 (0000 0011)byte count + dataCRC Exception 0183 (1000 0011)02 codeC0 F1 Bit 7 of the function byte flags an error; the next byte says why. Total reply is only 5 bytes. CourseTron · coursetron.com
    The exception flag lives in bit 7 of the function code, so master software tests fc & 0x80.

    Key fact: No response is not an exception. A timeout means a wrong address, a CRC failure, a baud or parity mismatch, or a missing RS-485 termination; a slave never sends an exception for a corrupted frame.

    Practical debugging habits

    • Cap FC 03 at 125 registers and FC 01 at 2000 coils per request; the 256-byte RTU frame limit forces this.
    • Poll no faster than the device updates; 9600 baud moves roughly one 8-byte request and 9-byte reply in about 20 ms including the 3.5-character gaps.
    • Keep an eye out for 8E1 versus 8N1 mismatches; the spec default is even parity.
    • Log the raw hex. A tool such as Modbus Poll, pymodbus or a Wireshark dissector makes exception 02 obvious in seconds.

    The same protocol logic appears in FPGA and SoC peripherals: a Modbus slave core is a small FSM plus CRC engine, and teams verifying such blocks often drive them with UVM testbenches in Synopsys VCS or Cadence Xcelium. For a related serial standard, compare it with our RS-485 differential signaling article, and if you need help integrating industrial interfaces into silicon, Avecas design services works on exactly this kind of mixed-signal and protocol IP.

    Want to go from decoding frames to designing the hardware behind them? Explore the embedded and protocol tracks in our CourseTron courses and build a working Modbus slave in RTL.

    FAQ

    What is the difference between function codes 06 and 16?

    FC 06 writes exactly one 16-bit holding register and echoes the request. FC 16 (0x10) writes a block of registers in one frame and replies with only the start address and quantity. Use FC 16 for 32-bit values so both halves update together.

    Why does my slave return exception 02?

    The requested start address, or start plus quantity, falls outside the device register map. Check for the zero-based offset: manual address 40001 is wire address 0.

    How do I recognise an exception response in a capture?

    Look for a function byte of 0x80 or higher, for example 0x83 or 0x86, and a reply of only five bytes: address, function, exception code and two CRC bytes.

    Are function codes the same in Modbus TCP?

    Yes. The PDU (function code plus data) is identical, but TCP replaces the address and CRC with a 7-byte MBAP header, using the unit identifier for addressing and relying on TCP for integrity.

    Advertisement