Disclosure up front: I work for ERDILRF in Chengdu, the company that makes these modules. Every frame, baud rate and unit below is transcribed from our manuals. I'm not publishing any bench measurements here, and there's a section at the end on what hasn't been verified.
Quick quiz. Here's a frame from a laser rangefinder:
5C 02 11 EC
What's the distance?
If you said "depends on the model," you've probably been burned before. On one of our H-series modules it's 43.54 m. On a VB-series module it's 4.354 m. Same four bytes.
When you wire one of these up for the first time, there are two ways it usually goes wrong. Either the terminal stays completely silent, or you get numbers that look plausible and are off by a factor of ten. The second one is worse, because nothing complains. In my experience both usually come down to protocol details, not wiring.
Here's what's worth knowing before you write a line of code.
Two protocol families, two read loops
Our 905 nm catalogue speaks two different UART protocols.
Family A uses an 8-byte 55 AA command/response protocol. That covers LR1000E2/E4, LR1200E2/E4, LR1500E2/E4, LR2000E2/E4, LRF1200A1, LRF3000A1, SPD1200N0/N2/N4 and SPD1200D03. You ask, it answers. For the LRF3000A1 example here, the manual specifies 115200 bps by default. Select the baud rate from the exact model's manual and confirm the delivered configuration. SPD1200D03 has an unresolved source conflict: its protocol section states 115200 bps, while its technical table states 15200 bps (factory) / 9600 bps. Confirm the actual batch configuration with ERDI before choosing a host baud rate.
Family B streams 5C frames on its own, at 460800 bps by default (adjustable), 8 data bits, 1 stop bit, no parity, little-endian. That's LRF50H/100H/200H/300H/600H/1500H plus LRF22VB and LRF50VB. It just keeps talking.
These two need different read loops, so figure out which one you have first.
Family A: the checksum isn't the same in both directions
Single-shot request:
55 AA 88 FF FF FF FF 84
Reply:
55 AA 88 STA FF DIS_H DIS_L SUM
STA = 1 means success. Distance is DIS_H<<8 | DIS_L (big-endian), and the manual says results are output as the real value x 10, so one count is 0.1 m. Continuous mode uses function code 0x89, stop is 0x8E.
Here's the part that eats an afternoon:
def tx_sum(frame: bytes) -> int:
# request: Function + D1..D4
return sum(frame[2:7]) & 0xFF
def rx_sum(frame: bytes) -> int:
# reply: 0x55 + 0xAA + Function + D1..D4
return sum(frame[0:7]) & 0xFF
The two differ by exactly 0x55 + 0xAA = 0xFF. Validate replies with the transmit formula and you'll be off by one on almost every frame. It also matches now and then, which is what makes it so confusing. You start suspecting cables, noise, the power supply...
A hand-checked example (constructed, not captured from hardware):
f = bytes.fromhex("55AA8801FF04D25D")
assert rx_sum(f) == f[7] # 0x35D -> low byte 0x5D
assert f[3] == 1 # STA: success
print(((f[5] << 8) | f[6]) / 10) # 1234 counts -> 123.4 m
Family B: units follow the model, not the family
Frame: 5C dist_lo dist_hi check. The check byte is the bitwise inverse of the sum of the bytes between header and check.
def parse_b4(frame: bytes) -> int:
assert frame[0] == 0x5C
assert (~(frame[1] + frame[2])) & 0xFF == frame[3]
return frame[1] | (frame[2] << 8) # little-endian
raw = parse_b4(bytes.fromhex("5C0211EC")) # 4354
| Model | Raw unit |
5C 02 11 EC means |
|---|---|---|
| LRF50H/100H/200H/300H/600H/1500H | cm | 43.54 m |
| LRF22VB, LRF50VB | mm | 4.354 m |
My strong opinion: don't auto-detect. Make the caller name the model. Clever auto-detection that guesses wrong won't throw. It'll just hand your obstacle-avoidance code a number that's ten times off.
One more oddity: LRF1500H is the only 5-byte frame (3-byte distance), and the example frame in its manual doesn't pass its own checksum formula. The driver keeps that discrepancy visible instead of quietly "fixing" it.
Wiring: supply voltage isn't signal level
These are two separate specs. Don't infer one from the other.
For the LRF3000A1, the manual's 6-pin connector (A1002WV-S-6P) is: 1 GND, 2 power 3.3-5 V DC, 3 NC, 4 TTL_TXD (3.3 V TTL), 5 TTL_RXD (3.3 V TTL), 6 Enable (power on at low level). Cross TX/RX, share ground, and don't leave Enable floating. Follow whatever documentation shipped with your unit.
Other models are very different. LRF50H takes 9-36 V and LRF22VB takes 3.0-3.6 V. If your manual doesn't state a signal level, get it confirmed in writing before you connect anything.
Just use the driver
Once the trap list got this long, we wrapped it all up: model table, checksums and unit conversion. It's MIT-licensed.
Python (Raspberry Pi or a PC with a USB-TTL adapter):
pip install https://github.com/ErdiTech/erdi-lrf-drivers/releases/download/v0.1.0/erdi_lrf-0.1.0-py3-none-any.whl
erdi-lrf --port /dev/ttyUSB0 --model LRF3000A1 measure
from erdi_lrf import ErdiLRF
with ErdiLRF("/dev/ttyUSB0", model="LRF3000A1") as lrf:
print(f"{lrf.measure().metres:.1f} m")
with ErdiLRF("/dev/ttyUSB0", model="LRF100H") as lrf:
for m in lrf.stream():
print(m.metres, "(out of range)" if m.out_of_range else "")
The codecs are pure functions with no serial dependency, so you can unit-test them offline.
Arduino: install ErdiLRF from the Library Manager (PlatformIO: erdi-tech/ErdiLRF). There's an older, similarly named ERDILRF_LRF in the index; that's not this one.
#include <ErdiLRF.h>
ErdiLRF lrf(Serial1, ERDI_LRF3000A1);
void setup() {
Serial.begin(115200);
Serial1.begin(erdiDefaultBaud(ERDI_LRF3000A1)); // 115200 default for this LRF3000A1 example; use the exact model manual
}
void loop() {
ErdiMeasurement m;
if (lrf.measure(&m) == ERDI_OK) Serial.println(m.metres, 3);
delay(500);
}
Heads-up: SoftwareSerial on an Uno can't do 460800. Family B needs a hardware UART (Mega, ESP32, etc.), or drop the module to a lower documented rate first.
ROS 2:
cp -r ros2/erdi_lrf_ros2 ~/ros2_ws/src/
cd ~/ros2_ws && colcon build --packages-select erdi_lrf_ros2
source install/setup.bash
ros2 run erdi_lrf_ros2 range_node --ros-args -p port:=/dev/ttyUSB0 -p model:=LRF3000A1 -p rate:=10.0 -p frame_id:=lrf_link
It publishes sensor_msgs/Range. field_of_view defaults to 0 because the protocol manuals carry no beam divergence, so set it from your model's datasheet.
When the manual argues with itself
- SPD1200D03: the protocol section says 115200, the technical table says 15200/9600. The driver makes you pass the baud explicitly and returns no metric distance.
-
LRF1200A1 / LRF3000A1: continuous ranging is requested with
0x89but the recorded reply uses0x88, and the manual declines to authorise a reply parser. Single-shot is fine. - LRF50VB: the printed serial-number request fails the manual's own checksum function.
A driver that guesses here is more dangerous than one that throws. Open a ticket with the vendor instead.
What's not verified
- No measurement results or performance claims. Check your model's datasheet.
- The ROS 2 node has been tested in software replay only, not against a live ROS 2 runtime with hardware.
- Driver tests are built from the frames printed in the manuals. The Python snippets above were checked only against the example frames in this post.
Repo: https://github.com/ErdiTech/erdi-lrf-drivers (MIT)
Arduino library: https://github.com/ErdiTech/ErdiLRF
Technical note (DOI): https://doi.org/10.5281/zenodo.23297197
Model catalogue: https://erdilrf.com/models/
If your unit behaves differently from its manual, I'd really like to hear about it in the comments or as an issue.
Vendor disclosure: the author is affiliated with ERDI TECH LTD (ERDILRF). Samples, datasheets and protocol questions: https://erdilrf.com or [email protected].
Top comments (0)