LearnSCADA

Part II · Chapter 6

Modbus ASCII

Modbus ASCII is RTU's slower but more legible cousin. It carries exactly the same PDU, but spells every byte out as two readable text characters, so you can almost read a frame straight off a terminal.

By the end of this chapter you can

  • Turn any byte into the two ASCII characters that carry it.
  • Recognize an ASCII frame by its colon and CR/LF delimiters.
  • Compute an LRC by hand.
  • Convert a request between RTU and ASCII, and say when you'd meet ASCII.

ASCII is rare on new equipment, but it still turns up on legacy systems and on links where RTU's strict timing can't be guaranteed, so it's worth a short chapter.

Bytes as text

The defining idea is simple: every byte travels as two ASCII characters that spell its value in hexadecimal. The byte 0x03 isn't sent as the byte 0x03; it's sent as the printable characters 0 and 3. The byte 0xF8 goes as F and 8. Since each byte becomes two characters, an ASCII frame is about twice as long on the wire as the same RTU frame, which is the main reason ASCII is slower.

One binary byte, 0x03, splits into its two hex digits. The high nibble 0000 becomes the character '0', which is ASCII code 0x30, and the low nibble 0011 becomes the character '3', ASCII code 0x33. Two characters go on the wire instead of one byte. ONE BINARY BYTE: 0x03 0000 0011 high nibble: 0 low nibble: 3 '0' ASCII 0x30 '3' ASCII 0x33 2 characters for 1 byte Likewise 0xF8 travels as 'F' (0x46) and '8' (0x38).
Figure 6.1 · In Modbus ASCII each binary byte is written as two ASCII hexadecimal characters. The byte 0x03 travels as the characters 0 and 3.

The payoff for that expansion is readability. A frame made entirely of printable characters passes cleanly through terminals, modems, and links that were never designed to carry arbitrary binary data and might choke on it. And a person watching the line sees recognizable text rather than an opaque byte stream, which made ASCII genuinely useful for debugging in the days of simpler tools.

Framing by delimiters

RTU marks the edges of a frame with silence, a timed gap of 3.5 character times. ASCII takes a different and in some ways friendlier approach: delimiter characters. Every frame begins with a colon (:) and ends with a carriage return and line feed (written CR/LF). A receiver watches for a colon to know a frame is starting, and for CR/LF to know it's over.

A Modbus ASCII frame arriving one character at a time: colon, 0 1 for the address, 0 3 for the function, 0 0 0 0 0 0 0 4 for the data, F 8 for the LRC, then carriage return and line feed. The characters arrive with two long pauses, and the receiver still assembles the frame correctly because it only watches for the colon and the CR LF. CHARACTERS AS THEY ARRIVE : 0 1 0 3 0 0 0 0 0 0 0 4 F 8 CR LF start address function data: start 0000, quantity 0004 LRC end Colon seen: a new frame starts A long pause… the receiver just keeps waiting Another pause: still fine in ASCII (fatal in RTU) CR LF seen: frame complete, check the LRC :010300000004F8 + CR LF — 17 characters
Figure 6.2 · A Modbus ASCII frame. A leading colon marks the start; address, function, and data follow as ASCII-hex characters; a one-byte LRC and a final CR/LF close the frame.

Delimiter framing is ASCII's quiet advantage. Because the edges of a frame are real characters, not precise timing, ASCII tolerates pauses and irregular delays that would break an RTU frame. The receiver doesn't care how long the gaps between characters are; it only cares about the colon and the closing newline. That forgiveness lets ASCII survive on slow or jittery links, including software and operating-system paths where RTU's tight timing is hard to guarantee.

The LRC check

ASCII protects its frames with a simpler check than RTU's CRC: a one-byte LRC, or longitudinal redundancy check. Add up the binary values of the message bytes (address, function code, and data, but not the colon or the CR/LF), keep only the lowest byte of the sum, and take its two's complement (negate it, modulo 256). That byte is appended, as two hex characters like everything else, just before the CR.

PYTHON — the Modbus ASCII LRC
def lrc(data):
    return (-sum(data)) & 0xFF

fields = bytes([0x01, 0x03, 0x00, 0x00, 0x00, 0x04])
frame = ":" + fields.hex().upper() + f"{lrc(fields):02X}" + "\r\n"
print(repr(frame))
OUTPUT
':010300000004F8\r\n'

That first function is the entire algorithm. For our running example (address 01, function 03, start 00 00, quantity 00 04), the bytes sum to 0x08, and the two's complement of 0x08 is 0xF8. So the LRC is 0xF8, riding in the frame as the characters F and 8.

Building the LRC. The bytes 01, 03, 00, 00, 00, 04 are added one at a time, and the running sum reads 01, 04, 04, 04, 04, 08. The low byte 08 is kept, and its two's complement, 100 minus 08, gives F8. As a check, 08 plus F8 is 100, whose low byte is 00. 01 03 00 00 00 04 + RUNNING SUM 00 01 04 08 keep low byte 08, then negate: 0x100 − 0x08 = 0xF8 LRC 0xF8 is sent as the characters 'F' '8', just before CR LF. Receiver's check: 08 + F8 = 0x100 → low byte 00, so the frame is intact.
Figure 6.3 · Building the LRC: sum the message bytes, keep the low byte, and take its two's complement. Here the answer is 0xF8.

The LRC is weaker than the CRC at catching some error patterns (two errors that cancel in the sum slip straight through, for example), which is part of why RTU became the preferred serial form. For ASCII's gentler use cases, it's adequate.

The same message, two ways

Seeing one complete request in both serial forms makes the relationship vivid. Our example (read four holding registers from unit 1, starting at address 0) looks like this:

FormOn the wire
Modbus RTU01 03 00 00 00 04 44 09 (8 bytes, binary)
Modbus ASCII:010300000004F8 + CR/LF (17 characters, text)

The same fields appear in the same order in both: 01, 03, 0000, 0004. RTU sends them as raw bytes and closes with a two-byte CRC; ASCII spells them out as hex characters inside a colon and a newline and closes with a one-byte LRC. The PDU is identical; only the encoding and the wrapper differ. It's the Chapter 4 lesson again, now between two serial dialects instead of between serial and Ethernet.

The same request in both serial forms. The top row shows the RTU frame as 8 binary bytes: 01 03 00 00 00 04 44 09. The bottom row shows the ASCII frame as 17 characters: colon, 0 1 0 3 0 0 0 0 0 0 0 4, F 8, CR, LF. Lines connect each RTU byte to the two characters that carry it. A highlight steps through the address, function, data, and check fields. MODBUS RTU · 8 BYTES, BINARY MODBUS ASCII · 17 CHARACTERS, TEXT 01 03 00 00 00 04 44 09 silence : 0 1 0 3 0 0 0 0 0 0 0 4 F 8 CR LF Address 01 → characters '0' '1' Function 03 → characters '0' '3' Data 00 00 00 04 → characters '0000' '0004' Different checks: 2-byte CRC 44 09 vs 1-byte LRC F8 Same fields, same order. Only the encoding and the wrapper differ.
Figure 6.4 · The identical request in both serial forms. RTU is compact binary closed by a CRC; ASCII is readable text bracketed by a colon and a newline and closed by an LRC.

RTU ⇄ ASCII converter

RTU bytes → ASCII frame

ASCII frame → bytes

When you will meet it

In practice, expect RTU and reach for it by default; ASCII is the exception. You'll meet it mainly on older equipment that predates the move to RTU, or when a device manual specifically calls for it. Some installations chose ASCII deliberately because their path (a radio link, a modem, a chain of converters) couldn't preserve RTU's timing but could reliably carry plain text.

Configuration is the same as RTU plus one choice: select ASCII instead of RTU as the transmission mode, and still match baud rate, data bits, parity, and stop bits at both ends. One difference to expect: because every character is plain ASCII text, ASCII mode normally uses 7 data bits (the specification's default is 7-E-1), where RTU always uses 8.

With both serial forms in hand, we've covered Modbus on wires. The remaining transport carries Modbus across the networks that run modern facilities: Modbus TCP, which swaps the serial address for a small header and drops the checksum entirely.

Check your understanding

1. How does Modbus ASCII send the byte 0x3A?

Every byte becomes two characters spelling its hexadecimal value. (A lone 0x3A would also be a colon, the start character, which is one reason ASCII spells bytes out.)

2. How does an ASCII receiver find the edges of a frame?

ASCII uses delimiter characters, so pauses between characters don't matter. Timing-based framing is RTU's method.

3. The message bytes of a frame sum to 0x08. What is the LRC?

The LRC is the two's complement of the low byte of the sum: 0x100 − 0x08 = 0xF8. (0xF7 is the one's complement, a common slip.)

4. Why might an installation deliberately choose ASCII over RTU?

ASCII is slower and its check is weaker, but delimiter framing survives the pauses and jitter that break RTU frames.