UNIT 10 · LESSON 6 OF 6

Packet Design, Checksums, and Timeouts

What turns it into a protocol you can trust?

INTERACTIVEA simple sum misses errors a CRC catches
A message, a corrupted copy, and whether a sum and a CRC detect the changesent01030000000Areceived010300000A00checksentrecomputed8-bit sum0x0E0x0EMISSEDCRC-160xCDC50x6A43detectedThe sum is unchanged, so this error would be accepted; the CRC changes and the frameis rejected.
A message, a corrupted copy, and whether a sum and a CRC detect the changesent01030000000Areceived010300000A00checksentrecomputed8-bit sum0x0E0x0EMISSEDCRC-160xCDC50x6A43detectedThe sum is unchanged, so this error would beaccepted; the CRC changes and the frame is rejected.

Try this

Transmission error
Sum misses; CRC detects.

The sender appends a check value computed from the message; the receiver recomputes it and compares. An 8-bit sum is cheap but blind to reordered bytes and to changes that cancel out. A CRC treats the message as a polynomial and keeps the remainder of dividing it by a fixed generator: every single- and double-bit error in a short message, every odd number of flipped bits for this generator, and every burst up to 16 bits long changes it. Here the message is a Modbus request, protected by CRC-16/MODBUS.

What you will be able to do
  • Frame packets on a byte stream with a length field, a delimiter with escaping, or an idle gap, and resynchronise after an error.
  • Compute a simple checksum and explain which errors it misses that a CRC detects.
  • Explain the error-detection guarantees of a 16-bit CRC and how it is sent.
  • Design acknowledgements, retries and sequence numbers so that lost or repeated packets do no harm.
  • Choose a response timeout from the transmission times, the protocol’s gaps and the device’s processing time.
Before you start
  • UART frames and throughput (lessons 1 and 2); RS-485 turnaround (lesson 5).
  • Bit operations and hexadecimal (unit 2, lesson 1).
Steps in this lesson
  1. Where does a packet start?
  2. Detecting corruption
  3. Acknowledge, retry and number
  4. Timeouts from the link’s own timing
  5. Worked example: a Modbus register read at 9600 baud
  6. Common misconceptions

The puzzle

A controller sends “open valve 3” to a device over RS-485. Noise corrupts one byte; the device acts on “open valve 7”. Another time the reply is lost, the controller sends the command again, and a counter on the device increments twice. A byte stream delivers bytes, not messages. What turns it into a protocol you can trust?

STEP 1

Where does a packet start?

A receiver that joins mid-stream, or loses a byte, must find the next packet boundary. Three common ways:

  • Length field: a header byte gives the packet length. Simple, but one corrupted length makes the receiver misread everything after it; it needs a start marker and a check to recover.
  • Delimiter with escaping: a reserved byte marks the end of a packet, and any occurrence of it in the data is replaced by an escape sequence. SLIP uses 0xC0 as END and escapes data bytes 0xC0 and 0xDB as 0xDB 0xDC and 0xDB 0xDD. After an error, the receiver simply waits for the next END.
  • Idle gap: silence marks the boundary. Modbus RTU ends a frame after 3.5 character times of silence (fixed at 1750 µs above 19 200 baud). It needs no reserved bytes but requires the sender never to pause mid-packet and the receiver to time gaps precisely.

STEP 2

Detecting corruption

The sender appends a check value computed over the packet; the receiver recomputes it and discards the packet if it differs. An 8-bit sum of the bytes is cheap, but it cannot see bytes in the wrong order, and changes that add and subtract the same amount cancel.

A cyclic redundancy check treats the packet as a long binary polynomial and appends the remainder of dividing it by a fixed generator polynomial. It costs a little more (a table lookup per byte), and its detection is far stronger. A 16-bit CRC such as the one Modbus uses (generator x¹⁶ + x¹⁵ + x² + 1) detects every error burst up to 16 bits long, every odd number of flipped bits (its generator has x + 1 as a factor) and every pair of flipped bits in codewords up to 32 767 bits long. Random damage beyond that still slips through only about once in 2¹⁶ = 65 536 corrupted packets.

↑ This step uses the figure at the top of the page.

A CRC is identified by more than its polynomial: the initial value, bit order and final XOR must match at both ends. Check an implementation with the standard test message “123456789”: CRC-16/MODBUS gives 0x4B37.

STEP 3

Acknowledge, retry and number

A detected error only discards the packet; recovering it needs the protocol:

  • the receiver acknowledges good packets (or the reply itself is the acknowledgement);
  • the sender retries after a timeout, a bounded number of times, then reports the failure;
  • sequence numbers let the receiver recognise a retry of a packet it has already executed (because only the reply was lost) and answer it again without acting twice. Alternatively, make commands idempotent: “set valve 3 to open” can be repeated safely; “toggle valve 3” cannot.

STEP 5

Worked example: a Modbus register read at 9600 baud

The request “device 1, read 10 holding registers from 0” is six bytes, 01 03 00 00 00 0A, followed by its CRC-16/MODBUS 0xCDC5 sent low byte first: C5 CD. Modbus RTU characters are 11 bits, so at 9600 baud each takes 1.146 ms. The 8-byte request takes 9.2 ms. The device waits t3.5 = 3.5 × 1.146 ≈ 4.0 ms of silence to see that the request has ended, takes up to 10 ms to process it, and replies with 5 + 2 × 10 = 25 bytes, 28.6 ms. The reply is complete

4.0+10+28.6≈42.7 ms4.0 + 10 + 28.6 \approx 42.7\ \text{ms}

after the request ends. A 50 ms timeout leaves only 7 ms of margin; reading 60 registers (125 bytes, 143 ms) with the same timeout would fail every time.

MYTHS AND FACTS

Common misconceptions

A checksum is a checksum

An 8-bit sum misses reordered bytes and cancelling errors that a well-chosen CRC such as CRC-16/MODBUS catches.

The CRC matched, so the data is right

Undetected errors are rare, not impossible; and a CRC protects against noise, not against a deliberate attacker (unit 15).

Retry until it works

Bound the retries, and make repeated commands harmless with sequence numbers or idempotent commands.

One timeout fits every request

The response time grows with the response length and falls with the baud rate.

Check yourself

Answer in your head, then open the card.

A packet 41 42 C0 43 is sent with SLIP framing. What bytes go on the wire?

41 42 DB DC 43 C0: the data byte C0 is escaped as DB DC, and C0 marks the end of the packet.

The bytes 10 20 30 and 30 20 10 have the same 8-bit sum. Would CRC-16/MODBUS tell them apart?

Yes. Swapping 10 and 30 flips exactly two bits, 16 bit positions apart (0x10 ⊕ 0x30 = 0x20 in both bytes), and CRC-16/MODBUS detects every double-bit error in codewords up to 32 767 bits: 0xD169 against 0xC369.

At 19 200 baud with 11-bit characters, what is the Modbus t3.5 gap, and what is it at 115 200 baud?

3.5 × 11 / 19 200 ≈ 2.0 ms; above 19 200 baud it is fixed at 1750 µs.

A controller times out and resends “increment counter”, but the device had executed the first one and only its reply was lost. How can the protocol prevent a double increment?

Give each request a sequence number; the device remembers the last one it executed and, on a repeat, resends the previous reply without incrementing. Or change the command to an idempotent “set counter to N”.

Sources (3)
  1. FreeMODBUS, modbus/rtu/mbcrc.c and mbrtu.c — usMBCRC16: table-driven CRC-16 starting from 0xFF, 0xFF; mbrtu.c: “If baudrate > 19200 then we should use the fixed timer values t35 = 1750us. Otherwise t35 must be 3.5 times the character time”, with the character time computed from 11 bits; a frame is complete when t3.5 of silence expires
  2. Linux kernel, lib/crc/crc16.c and lib/crc/crc-itu-t.c — “CRC table for the CRC-16. The poly is 0x8005 (x^16 + x^15 + x^2 + 1)”; crc-itu-t.c: “CRC ITU-T V.41 0x1021 (x^16 + x^12 + x^5 + 1)”: CRCs are named by their generator polynomial
  3. Linux kernel, drivers/net/slip/slip.h — SLIP framing: END 0300 (0xC0) “indicates end of frame”, ESC 0333 (0xDB) “indicates byte stuffing”, ESC ESC_END (0xDB 0xDC) means an END data byte, ESC ESC_ESC (0xDB 0xDD) an ESC data byte