Skip to main content
Explore ERDILRF

Press ↑↓ to move, Enter to open, Esc to close.

Laser Rangefinder Serial Protocol: Frames & Checksums

Explore knowledge

Short answer: ERDILRF’s catalog uses exactly two wire protocols — an 8-byte 55 AA command/response frame (“family A”) and a 4-byte free-running 5C stream (“family B”) — and the single most common integration bug across both is checksum handling, not baud rate or wiring. Below is the frame format for each, the checksum formulas as documented in ERDILRF’s own manuals, and a working Python decoder straight from the open-source drivers repo.

Family A: the 55 AA command/response frame

Family A covers the LR1000–LR2000 series, LRF1200A1, LRF3000A1, and the SPD1200N0/N2/N4/D03 modules — every model that talks over a request/reply exchange rather than free-running. Every frame, in both directions, is exactly 8 bytes at 115200 bps, 8 data bits (source: LR2000E2.md, LRF200H.md and the rest of the family-A product pages):

55 AA <function> <D1> <D2> <D3> <D4> <checksum>

A single-shot ranging request is 55 AA 88 FF FF FF FF 84; continuous ranging starts with 55 AA 89 FF FF FF FF 85; 55 AA 8E FF FF FF FF 8A stops it. The reply to a ranging command carries the status byte and the two-byte distance in the D3/D4 position: 55 AA <fn> STA FF DIS_H DIS_L SUM. The returned distance is the actual value multiplied by ten — so a reply distance field of 0x0429 (1065 decimal) means 106.5 m, not 1065 m (source: family-A product pages, “scale” field).

The checksum trap: transmit and reply use different formulas

This is the detail that catches nearly every first integration attempt. ERDI’s own manuals state it directly, and the open-source driver’s code comments repeat the warning for exactly that reason: the checksum you send is not computed the same way as the checksum you receive.

  • Transmit checksum (what your microcontroller sends): sum of the function code plus the four data bytes, lower 8 bits. SUM(Function Code + D1 + D2 + D3 + D4) & 0xFF.
  • Reply checksum (what the module sends back): sum of both header bytes plus the function code plus the four data bytes, lower 8 bits. SUM(0x55 + 0xAA + Function Code + D1 + D2 + D3 + D4) & 0xFF.

Apply the transmit formula to a reply frame, or the reply formula to a request you’re building, and every checksum check will fail even though the module is working correctly and answering every request. The open-source driver keeps these as two separate, separately-tested functions for exactly this reason — treat them as two different algorithms, not one formula reused in both directions.

Working Python decoder (MIT licensed, from the official drivers repo)

This is the actual family-A codec from protocol_a.py in the erdi-lrf-drivers repository (MIT license), showing the two checksum functions and a frame builder:

HEADER = b"\x55\xaa"
FRAME_LEN = 8

FN_SINGLE = 0x88       # single-shot ranging
FN_CONTINUOUS = 0x89   # start continuous ranging
FN_STOP = 0x8E         # stop ranging

def tx_checksum(function: int, data: bytes) -> int:
    """Transmit checksum: SUM(Function Code + DATA1..DATA4), lower 8 bits."""
    return (function + sum(data)) & 0xFF

def rx_checksum(frame_without_checksum: bytes) -> int:
    """Reply checksum: SUM(header H + header L + function + DATA1..DATA4)."""
    return sum(frame_without_checksum) & 0xFF

def build_command(function: int, data: bytes = b"\xff\xff\xff\xff") -> bytes:
    """Build one 8-byte request frame."""
    return HEADER + bytes([function]) + bytes(data) + bytes([tx_checksum(function, data)])

def single_shot() -> bytes:
    """55 AA 88 FF FF FF FF 84 -- single-shot ranging."""
    return build_command(FN_SINGLE)

def continuous() -> bytes:
    """55 AA 89 FF FF FF FF 85 -- start continuous ranging."""
    return build_command(FN_CONTINUOUS)

The full module also includes find_frames(), which scans a raw byte buffer for checksum-valid 8-byte frames and resyncs one byte at a time when it hits a bad frame — useful if you’re reading a continuous stream off a UART without a hardware frame delimiter. Install the package directly rather than retyping this: pip install -e python/ from a checkout of the erdi-lrf-drivers repo, then from erdi_lrf import ErdiLRF.

Family B: the 5C free-running stream

Family B covers the H-series (LRF50H through LRF1500H) and the VB-series (LRF22VB, LRF50VB). Rather than command/response, these modules free-run at a fixed default frequency (1 kHz on most H-series models) and push a 4-byte little-endian frame continuously: 5C <distance low byte> <distance high byte> <checksum>, at 460800 bps by default (source: LRF200H.md, erdi-lrf-drivers README). The checksum covers only the two distance bytes — not the 5C header: LRF200H’s own manual describes it as “From 02 to 11, opposite of the Check byte” for its worked example, meaning the checksum is computed over the distance-byte pair alone (source: LRF200H.md). The formula itself, per the manual’s own printed C function and the drivers repository’s implementation, is a bitwise inversion of the byte sum: checksum = (~sum(distance_bytes)) & 0xFF — with the header byte excluded from that sum.

Worked checksum example on that same frame, 5C 02 11 EC: the distance bytes are 0x02 and 0x11 — the header byte 0x5C is not part of the sum. 0x02 + 0x11 = 0x13; inverted (one’s complement) and masked to 8 bits, ~0x13 & 0xFF = 0xEC, which matches the frame’s trailing checksum byte exactly. That confirms the checksum is computed over the distance-byte pair only.

The critical warning beyond the checksum itself is units. The same frame, 5C 02 11 EC, decodes to the same raw distance value on an H-series and a VB-series module, but it means 43.54 m on an H-series module and 4.354 m on a VB-series module, because the two sub-families scale the same frame shape in centimetres versus millimetres (erdi-lrf-drivers README, fetched 2026-09-04). Always decode against the specific model you’re talking to — never infer scaling from “it’s a family-B module.”

Continuous vs single-shot: which to use

  • Single-shot (family A, 55 AA 88 ...) is the right mode when you need a measurement on demand — a button press, a periodic poll on your own schedule, or a low-duty-cycle application where continuous ranging would waste power for no benefit.
  • Continuous ranging (family A, 55 AA 89 ..., or the always-on stream that family B modules run by default) is the right mode when your application needs a steady stream of updates — altitude hold, obstacle avoidance, or any control loop that reads distance every cycle. Remember to send the stop command (55 AA 8E ...) on family A modules before switching modes or powering down cleanly.

Frame format quick reference

Protocol familyFrame lengthDefault baudDistance scalingModels
A — 55 AA command/response8 bytes115200 bpsValue × 10 (0.1 m/count)LR1000E2/E4, LR1200E2/E4, LR1500E2/E4, LR2000E2/E4, LRF1200A1, LRF3000A1, SPD1200N0/N2/N4/D03
B — 5C free-running (H-series)4 bytes (5 on LRF1500H)460800 bpsCentimetresLRF50H, LRF100H, LRF200H, LRF300H, LRF600H, LRF1500H
B — 5C free-running (VB-series)4 bytes460800 bpsMillimetresLRF22VB, LRF50VB

Sources: erdi-lrf-drivers README (fetched 2026-09-04); individual product pages in products/*.md. Note LRF10VB and SPD1200S2G are in the catalog but have no published communication manual, so no driver exists for either — the repository raises a clear error rather than guessing a protocol for them.

Frequently Asked Questions

Why does my checksum check fail even though the module answers correctly?

Almost always because the transmit and reply checksum formulas got swapped. Family-A modules use a different formula for the checksum you send (function code + data bytes) than the one the module sends back (header bytes + function code + data bytes). Verify you’re applying the reply formula only to reply frames.

What’s the difference between family A and family B protocols?

Family A is a command/response protocol: you send an 8-byte request, the module sends an 8-byte reply, at 115200 bps. Family B is a free-running stream: the module continuously pushes 4-byte frames at 460800 bps by default without waiting for a request.

Does the same frame format mean the same units across every model?

No — this is the most important caveat in the whole protocol. Family-B H-series modules report centimetres and VB-series modules report millimetres from an identically-shaped 4-byte frame. Always decode against the specific model, never against “the family.”

Should I use single-shot or continuous ranging?

Single-shot for on-demand or low-duty-cycle measurements; continuous ranging for a steady control-loop feed like altitude hold or obstacle avoidance. Family-B modules free-run continuously by design rather than offering a single-shot mode.

Is there an open-source driver I can use instead of writing my own decoder?

Yes — the erdi-lrf-drivers repository (MIT licensed) ships Python, Arduino and ROS 2 drivers covering every model with a published protocol, with the checksum and unit-scaling logic already implemented and tested.

Need a protocol clarified for your specific model?

If your model has a documented conflict in its manual (a few do — we list them openly rather than picking a side), or you just want confirmation of the exact frame for your delivered batch, contact us and we’ll confirm it in writing. For the physical wiring side of this, see our UART-TTL integration guide.