View all lessons
Part 1
Fundamentals
Part 2
Modbus RTU
- Lesson 5RS485 explained: A, B, common and termination20 min
- Lesson 6Baud rate and parity: why 8E1 is the default16 min
- Lesson 7The Modbus RTU frame byte by byte, plus CRC18 min
- Lesson 8Multiple Modbus devices on one RS485 bus16 min
- Lesson 9Read your first Modbus device with mbpoll22 min
- Lesson 10Modbus RTU troubleshooting: symptom to cause20 min
Part 3
Modbus TCP
Part 4
Advanced
- Lesson 16Writing to a Modbus device without breaking it18 min
- Lesson 17Word order and floats: same bytes, other value22 min
- Lesson 18Calculate your poll interval and bus load18 min
- Lesson 19Modbus security: the protocol will not help16 min
- Lesson 20Modbus integration: PLC, Home Assistant, cloud20 min
- Lesson 21Modbus commissioning checklist and cheat sheet18 min
Writing to a Modbus device without breaking it
Write to a Modbus register without stopping the plant. Learn the difference between FC05, FC06 and FC16 and adopt a safety routine you use every single time.
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.
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.
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.
| Bytes | Field | What it says |
|---|---|---|
01 | server address | server 1 on this bus |
06 | function code | FC06, write single register |
00 01 | register address | PDU address 1, register 2 in 1-based datasheet counting |
00 03 | register value | the value 3 |
98 0B | CRC-16 | low 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.
- Read the register you are about to change and write the value down, on paper or in your notes, not in your head.
- 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.
- Write the new value.
- 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
-
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.
-
Sending 0x0001 to switch a relay on. FC05 knows
0xFF00and0x0000and 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. -
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.
-
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
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
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
Write the new value
Send your test value with a single write and let the client show you the response.
- 4
Read it back
Use the exact command from step 2 and confirm the register now holds your test value.
- 5
Restore the old value and check again
Write your note back and read once more. Only now is the exercise finished.
- 6
Provoke exception 03
Send
0x0001to a coil instead of0xFF00and 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
0xFF00for on and0x0000for 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
Want to see how it works?
The ModbusCloud Gateway reads the devices from this course without you programming a single register.