In this lesson

What this lesson covers

  • FC05, FC06, FC15 and FC16, with the limit of 1968 coils and 123 registers
  • Why FC05 accepts only the values 0xFF00 and 0x0000
  • The routine that saves the old value, and what an echo does not prove

Read first: Reading exception codes and choosing a function code, How to read a Modbus datasheet, step by step

Writing to a Modbus register is the one action in this protocol that changes a machine, and reading again does not undo it. The frame is no harder than a read: the same server address, a different function code, and your value where the quantity used to be. After this lesson you write with a fixed routine, and you know which writes give you no confirmation at all.

What goes wrong when you just write

A load balancer writes the charge current limit of an EV charger read over Modbus into a holding register every few seconds. You write a test value into that same register to prove your connection works, and the charger obeys for a moment before the balancer overwrites it. The same test on a heat pump ends differently: nothing overwrites your value, the compressor runs at a setpoint nobody chose until somebody notices the energy bill.

Reading cannot do that. FC03 and FC04 hand you a copy of what sits in the device and leave every value alone. FC05, FC06, FC15 and FC16 change the state of the machine, and the Modbus function code reference shows how small the difference in the frame is: a single byte.

Which function code fits which write?

Two questions decide it: are you writing single bits or 16 bit registers, and are you writing one object or several? That gives you four codes. FC05 writes one coil, FC06 writes one holding register, FC15 writes up to 1968 coils in one request, and FC16 writes up to 123 registers. Discrete inputs and input registers have no write code at all, because they are read only by definition, and a device asked to write one answers with exception 01 or 02.

ActionFunction codeEffect on the deviceRiskReading registersFC03 and FC04None. The device doesnot change state.lowWriting one coilor registerFC05 and FC06One value moves. You canwrite the old one back.Writing a blockof registersFC15 and FC16Up to 123 registers in onetransaction. After a timeout youdo not know which part arrived.Broadcast to address 0FC06 or FC16 onserver address 0Every device on the segmentexecutes it and nobody confirms.high
Reading is always allowed, broadcast writing almost never. Know which rung you are standing on before you touch a setpoint.

Why FC05 accepts only two values

Because the standard says so, in one sentence: a value of 0xFF00 requests the output to be on, 0x0000 requests it to be off, and all other values are illegal and will not affect the output. The spec example switches a coil on with the request 01 05 00 AC FF 00 4C 1B, and the device replies with those same eight bytes.

So 0xFF00 is not the number 65280 and not a boolean 1. It is a fixed constant, and the device tests for it before it does anything. Send 0x0001 instead and you get exception 03 back, with the coil still in its old position. That is good news in its way: an exception means your frame arrived intact at the right address. The full set of codes sits in the lesson on function codes and exception codes.

Exception codes and where to lookWrong in your requestThe device is alive and answering. Correct your address, your functioncode or your value.01Illegal function02Illegal data address03Illegal data valueProblem in the deviceYour request is valid, but the device cannot carry it out right now.Look inside the device itself.04Server device failure05Acknowledge06Server device busy08Memory parity errorProblem in the gatewayThis answer comes from the gateway, not from the end device behind it.0AGateway path unavailable0BGateway target device failed to respond
A refused write comes back as exception 03 when the value is wrong and as 01 or 02 when the block or the address is wrong. The bucket tells you where to search.

Reading the response to a write

Take the spec example for FC06: write the value 3 into the register the datasheet calls register 2, which is PDU address 1. On an RTU bus that is eight bytes.

BytesFieldWhat it says
01server addressserver 1 on this bus
06function codeFC06, write single register
00 01register addressPDU address 1, register 2 in 1-based datasheet counting
00 03register valuethe value 3
98 0BCRC-16low byte first

The reply is those same eight bytes, sent once the value has been written, and FC05 behaves identically. FC15 and FC16 differ on one point: they confirm with the starting address and the quantity instead of the data, so the FC16 request 01 10 00 01 00 02 04 00 0A 01 02 92 30 comes back as 01 10 00 01 00 02 plus its own CRC. Two registers written from PDU address 1, not one data byte repeated.

An echo proves one thing only: the server put your value in that register. Whether the application uses it is a separate question, and often the answer is no until you flip a mode setting or restart the device.

Writing a block without half states

Three separate FC06 writes are three transactions, and in between the device runs on a mixture of new settings and old ones. If a minimum temperature arrives before its matching maximum, the device briefly holds a maximum below its minimum, and it keeps controlling on that pair until the second write lands.

FC16 puts the whole set into one transaction, up to 123 registers, and the device applies them together. FC23 goes further: it combines a write and a read in one message, writing up to 121 registers before reading up to 125 back, so your check travels with your change. Not every device implements FC23, and the datasheet tells you which, as the register map reading lesson shows.

The routine you follow every time

Four steps, and only the third one carries any risk.

  1. Read the register you are about to change and write the value down, on paper or in your notes, not in your head.
  2. Check in the datasheet that the register is writable and that you have the right block, so you are not aiming FC06 at an input register.
  3. Write the new value.
  4. Read it back and check both things: the register holds your value, and the machine did what you expected.

When you had better not write

Broadcast is the clearest case. On a serial bus, server address 0 is the broadcast address, valid addresses run from 1 to 247, and 248 to 255 are reserved. Every device recognises a broadcast and none of them answer it, so you learn nothing: not whether it arrived, not which device skipped it, not what the values are now. That makes step 4 impossible by design.

The second case is a register the datasheet does not describe. An undocumented holding register is not free space, and a manufacturer that does not document a register does not guarantee its behaviour either.

Common mistakes

  1. Writing without noting the old value. People write first and look for the original afterwards, in a log they never enabled. Then the plant runs on a value nobody chose and nobody can reconstruct. Read and write the number down before you touch anything.

  2. Sending 0x0001 to switch a relay on. FC05 knows 0xFF00 and 0x0000 and nothing else. Anything in between is illegal by specification, comes back as exception 03, and leaves the coil exactly where it was. Libraries usually hide this behind a boolean, so it surfaces the moment you build the frame yourself.

  3. Treating an echo as proof. FC05 and FC06 return your request as soon as the value has been stored. That says nothing about what the application in the device does with it. Read the register back and check the machine, not the frame.

  4. Reaching for broadcast to save time. Nobody answers on address 0, so you cannot see whether the message arrived and you cannot see which device ignored it. Ten individual writes with ten confirmations take longer and tell you ten things.

Get hands-on

Write once, deliberately, with the full routine. Then provoke an error on purpose so you have seen a refused write as well.

  1. 1

    Pick a harmless register

    Choose a register the datasheet marks as writable and that controls nothing that moves. A display setpoint or a communication parameter is fine, a valve position is not.

  2. 2

    Read the old value and write it down

    Note the number on paper. This is the only thing standing between you and a device you cannot restore.

  3. 3

    Write the new value

    Send your test value with a single write and let the client show you the response.

  4. 4

    Read it back

    Use the exact command from step 2 and confirm the register now holds your test value.

  5. 5

    Restore the old value and check again

    Write your note back and read once more. Only now is the exercise finished.

  6. 6

    Provoke exception 03

    Send 0x0001 to a coil instead of 0xFF00 and record what comes back. According to the specification that is exception 03, and the coil does not move.

mbpoll -m rtu -a 1 -b 9600 -P none -t 4 -r 20 -c 1 -1 /dev/ttyUSB0
mbpoll -m rtu -a 1 -b 9600 -P none -t 4 -r 20 /dev/ttyUSB0 1234

No hardware. Run steps 2 to 5 against the pymodbus simulator. Build a server with a writable register using SimData and SimDevice, the way you did in the simulator lesson, and practise the routine where nothing can break. Both mbpoll and pymodbus are free to use, including commercially. The same four steps apply when a platform writes for you, whether that is a PLC, a cloud service or a Home Assistant Modbus integration.

Expected result: a value changed and restored, with your own note as proof, plus one exception you triggered yourself.

Summary

  • FC03 and FC04 change nothing, while FC05, FC06, FC15 and FC16 change the machine and cannot be undone by reading again.
  • FC05 accepts only 0xFF00 for on and 0x0000 for off, and any other value returns exception 03 with the coil unchanged.
  • FC05 and FC06 echo your request, FC15 and FC16 confirm address and quantity, and neither proves the value is used.
  • FC16 writes up to 123 registers in one transaction, two fewer than a read allows, because the request also carries address, quantity and byte count.
  • A broadcast write on address 0 is confirmed by nobody, so the read back step is impossible and you learn nothing.

Check yourself

Four questions about this lesson. Every answer comes with an explanation.

Question 1 of 4
You send the value 0x0001 to a coil with FC05 to switch it on. What happens?
You have to change three related setpoints at once. Why use FC16 instead of three separate FC06 writes?
You write a value, get an echo back, but the device behaves exactly as before. What is the most likely explanation?
How many registers can a single FC16 request write at most, and why is that fewer than you can read?

Stuck on a project?

Describe what you see and where it goes wrong. We will look at it and tell you what we would do.

Further reading in the knowledge base