1Your bytes
Spaces, commas, colons, 0x prefixes and line breaks are all fine — paste a Wireshark dump if you like. A trailing single digit is treated as half a typed byte, not an error.
2The CRC
On the wireC4 0B
as sent on the wire, low byte first
As a 16-bit number0x0BC4
what a CRC library hands back. Swap the two bytes to get the wire order above.
The complete frame01 03 00 00 00 02 C4 0B
3The other CRC-16s
Every one of these is called “CRC-16” by somebody. If your code disagrees with the wire, the value you are producing is almost always one of the rows below.
| Variant | Hex | On the wire | Who uses it |
|---|
4Build a request
Fill in what you want to ask a slave and the frame is written for you, CRC included.
ON is FF 00 and OFF is 00 00. No other value is legal for FC05.
01 03 00 00 00 02 C4 0B
How the Modbus CRC actually works
Modbus RTU puts a 16-bit CRC on the end of every frame. The algorithm is CRC-16/MODBUS, and it is fully described by four facts:
- The polynomial is
0x8005, used in its reflected form0xA001— bytes are fed in least-significant-bit first. - The register is initialised to 0xFFFF, not to zero.
- There is no final XOR and no reflection of the output.
- The result is appended low byte first, which is the opposite order to every other 16-bit field in the protocol.
So a read of two holding registers from slave 1 — 01 03 00 00 00 02 — has a CRC of 0x0BC4, and the frame that goes on the bus ends C4 0B. The CRC covers the slave address and everything after it, and nothing else: no start byte, no length, and never the CRC bytes themselves.
A receiver has a shortcut available. Run the same CRC over the whole received frame, CRC bytes included, and a good frame gives exactly 0x0000. That is a check, not a value to transmit.
Why yours does not match
In rough order of how often each one turns out to be the answer:
- The two bytes are swapped. Your library returned
0x0BC4and you wrote0B C4. The wire wantsC4 0B. The slave will not answer and will not tell you why. - You started at 0 instead of 0xFFFF. That is CRC-16/ARC, the row directly under MODBUS in the table above — same polynomial, different seed. It is the single most common mix-up, because a lot of sample code is written for ARC.
- The CRC bytes went into the calculation. If you are recomputing the CRC of a frame you captured, strip the last two bytes first. Feeding them back in gives you the CRC of a CRC.
- The bytes are decimal, not hex. A datasheet that says “register 40010” and a capture that says
0Aare talking about different notations. Everything this page accepts is hex. - It is not a CRC at all. Modbus ASCII — the frames that start with a colon — ends with an LRC, the two's complement of the byte sum, followed by CR LF. Modbus TCP has no checksum of its own at all; it carries an MBAP header instead and leaves integrity to TCP.
Modbus CRC questions
Everything people ask after the first frame still does not work.
How is the Modbus CRC calculated?
CRC-16/MODBUS uses the polynomial 0x8005 in its reflected form, 0xA001. The register starts at 0xFFFF, every byte of the frame from the slave address onwards is folded in least-significant-bit first, and there is no final XOR. The 16-bit result is appended to the frame low byte first, which is the opposite order to everything else in Modbus.
Why is my CRC the other way round?
Because Modbus sends the CRC low byte first while addresses, quantities and register values are all big-endian. A CRC of 0x0BC4 goes on the wire as C4 0B. Most libraries hand you the 16-bit number, so if you write it out as-is the two bytes are swapped and every slave on the bus drops the frame in silence. This page shows both, so you can see which one you have.
Do the CRC bytes themselves go into the calculation?
No. The CRC covers everything before it: the slave address, the function code and the data, and nothing else. Running the CRC over a complete frame including its own CRC bytes gives 0x0000, which is a valid way to check a received frame but is not the value you transmit. If you paste a complete frame here, the tool notices and offers to drop the last two bytes.
Does Modbus TCP have a CRC?
No. Modbus TCP replaces the CRC with a seven-byte MBAP header — transaction id, protocol id, length and unit id — and lets TCP itself handle the integrity check. If you are building a TCP frame, take an RTU frame, drop the two CRC bytes and put the MBAP header in front. The frame decoder converts between the two.
Does Modbus ASCII use this CRC?
No. Modbus ASCII frames start with a colon, carry each byte as two hex characters and end with an LRC, which is simply the two's complement of the sum of the payload bytes, followed by CR LF. If your frame starts with a colon you want an LRC, not a CRC — paste it into the frame decoder and it will check the LRC for you.
Does anything I type here leave my browser?
Nothing you type or read leaves your browser. The only network request the tool makes is one anonymous ping (a GET to /tools/_/…, no cookies, no identifiers) when a decode or read succeeds, so we know the tool is used. The CRC itself is a few lines of JavaScript running in this page; there is no server involved, and the frame you are working on stays in the URL fragment, which browsers never send.
You got the frame right. Now keep the readings.
A correct CRC is the first five minutes. Synacl is the rest: an ESP32 gateway polls the slave on a timer, the registers land on a dashboard with history, and a rule tells you when one of them moves.
- Free tier, no card. One gateway, a handful of devices, real history.
- Open-source firmware on any ESP32 you already own — flash it from the browser.
- Address, scale and offset per tag, so the register you just found becomes a named reading.