<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tommy Yu</title>
    <description>The latest articles on DEV Community by Tommy Yu (@erdilrf_tommy).</description>
    <link>https://dev.to/erdilrf_tommy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4178071%2Fda62cd76-d93e-4296-9242-ea1e0099188a.png</url>
      <title>DEV Community: Tommy Yu</title>
      <link>https://dev.to/erdilrf_tommy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/erdilrf_tommy"/>
    <language>en</language>
    <item>
      <title>Spot size and latency: the two rangefinder numbers that decide whether your robot sees the obstacle</title>
      <dc:creator>Tommy Yu</dc:creator>
      <pubDate>Sat, 24 Oct 2026 13:40:49 +0000</pubDate>
      <link>https://dev.to/erdilrf_tommy/spot-size-and-latency-the-two-rangefinder-numbers-that-decide-whether-your-robot-sees-the-obstacle-3pkc</link>
      <guid>https://dev.to/erdilrf_tommy/spot-size-and-latency-the-two-rangefinder-numbers-that-decide-whether-your-robot-sees-the-obstacle-3pkc</guid>
      <description>&lt;p&gt;Two rangefinders can quote the same maximum range and still behave completely differently on the same vehicle. In my experience the difference usually comes down to two numbers that are buried in the datasheet, or missing from it entirely: &lt;strong&gt;spot size at the target&lt;/strong&gt; and &lt;strong&gt;measurement latency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This note is a practical worksheet for comparing laser rangefinder modules before you bolt one onto a drone, a robot or a gimbal. It is written from the vendor side (disclosure at the end), but the arithmetic is the arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Spot size: the number that connects the datasheet to the installation
&lt;/h2&gt;

&lt;p&gt;Beam divergence is the angle at which the beam spreads after it leaves the module. For small angles, the spot diameter at distance &lt;code&gt;R&lt;/code&gt; is approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spot_diameter ≈ R × θ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with &lt;code&gt;θ&lt;/code&gt; in radians. Millimetres of milliradians are easier: &lt;code&gt;1 mrad ≈ 1 mm per metre&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4.5 mrad at 100 m → about 0.45 m across&lt;/li&gt;
&lt;li&gt;10 mrad at 100 m → about 1.0 m across&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the difference between "illuminating a drone-sized target" and "illuminating the hillside behind it". A wide beam is forgiving to aim and friendly on a moving gimbal; a narrow beam concentrates the return on small targets but demands better pointing stability and a stiffer mount.&lt;/p&gt;

&lt;p&gt;Two practical consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small targets at long range&lt;/strong&gt;: if the spot is wider than the target, most of the emitted energy lands on the background. The receiver still gets a return, but it is the background's return. The reading can look plausible and still be wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alignment tolerances&lt;/strong&gt;: a 1 mrad pointing error moves the spot by 10 cm at 100 m. On a flexing carbon arm, that is not a rounding error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before trusting a range figure, ask for the divergence, the receiver aperture and the target reflectivity used in the test. If a datasheet gives only "max range", it has told you almost nothing about your installation.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Latency and update rate: the number your control loop actually feels
&lt;/h2&gt;

&lt;p&gt;A 10 Hz update rate is one sample every 100 ms. At 5 m/s, the vehicle has moved &lt;strong&gt;0.5 m&lt;/strong&gt; between two readings. A 1 kHz rate is one sample every millisecond — roughly 1 mm of travel at the same speed.&lt;/p&gt;

&lt;p&gt;That gap is not cosmetic. If the loop closes on stale data, it will overshoot, oscillate, or brake late. The fix is not always "buy the fastest module": it is to match the rate to the motion.&lt;/p&gt;

&lt;p&gt;A rough way to size it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;required_rate ≈ (allowed position error) / (max closing speed)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you can tolerate 20 cm of error while closing at 4 m/s, you need at least 20 Hz &lt;em&gt;and&lt;/em&gt; you need the latency of the whole chain — module, UART, parser, control loop — to be inside that budget. The module datasheet is only the first term.&lt;/p&gt;

&lt;p&gt;Some modules auto-switch: they run fast at short range and drop to a lower rate at long range, where more averaging is needed to get a usable return. That is a reasonable engineering trade, but it means the "update rate" line on the datasheet may be a best case. Ask what the rate is at the range you care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A comparison checklist that survives procurement
&lt;/h2&gt;

&lt;p&gt;When you compare modules, write these down for each candidate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Divergence (mrad) and the resulting spot at your operating range.&lt;/li&gt;
&lt;li&gt;Update rate at your operating range, not just the headline maximum.&lt;/li&gt;
&lt;li&gt;Latency from measurement to UART frame out, plus the frame size at your baud rate.&lt;/li&gt;
&lt;li&gt;Interface and protocol: UART TTL levels, frame format, checksum, unit of the distance field.&lt;/li&gt;
&lt;li&gt;Behaviour when the return is weak: last-value hold, error flag, or a plausible-looking wrong number.&lt;/li&gt;
&lt;li&gt;What the test target was — reflectivity, size, ambient light.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Point 5 is the one that bites in the field. A module that holds the last good value silently is harder to debug than one that returns an explicit error. Whichever behaviour your module has, make the parser handle it explicitly rather than treating every frame as truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Why I wrote this down
&lt;/h2&gt;

&lt;p&gt;We build 905 nm and 1535 nm laser rangefinder modules, and most integration questions we get are not about the laser at all — they are about spot size, timing and protocol framing. Two of our engineering notes go deeper on the geometry and on update-rate selection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Beam divergence and spot size: &lt;a href="https://erdilrf.com/knowledge/rangefinder-beam-divergence-spot-size/" rel="noopener noreferrer"&gt;https://erdilrf.com/knowledge/rangefinder-beam-divergence-spot-size/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Update rate for moving platforms: &lt;a href="https://erdilrf.com/knowledge/rangefinder-update-rate-moving-platforms/" rel="noopener noreferrer"&gt;https://erdilrf.com/knowledge/rangefinder-update-rate-moving-platforms/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Module list and datasheets: &lt;a href="https://erdilrf.com/models/" rel="noopener noreferrer"&gt;https://erdilrf.com/models/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are comparing modules, the checklist above is the part worth copying into your own spreadsheet. The range number is the least interesting column in it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I work on integration support at ERDI TECH LTD (erdilrf.com), which makes laser rangefinder modules. The arithmetic and the checklist above apply to any module; the links point to our own engineering notes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>robotics</category>
      <category>drones</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Same 4 bytes, 10x different distance: notes from writing a laser rangefinder UART driver</title>
      <dc:creator>Tommy Yu</dc:creator>
      <pubDate>Mon, 12 Oct 2026 03:23:10 +0000</pubDate>
      <link>https://dev.to/erdilrf_tommy/reading-a-905-nm-laser-rangefinder-module-over-uart-arduino-raspberry-pi-and-ros-2-gal</link>
      <guid>https://dev.to/erdilrf_tommy/reading-a-905-nm-laser-rangefinder-module-over-uart-arduino-raspberry-pi-and-ros-2-gal</guid>
      <description>&lt;p&gt;&lt;em&gt;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.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Quick quiz. Here's a frame from a laser rangefinder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5C 02 11 EC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's the distance?&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Here's what's worth knowing before you write a line of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two protocol families, two read loops
&lt;/h2&gt;

&lt;p&gt;Our 905 nm catalogue speaks two different UART protocols.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Family A&lt;/strong&gt; uses an 8-byte &lt;code&gt;55 AA&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Family B&lt;/strong&gt; streams &lt;code&gt;5C&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;These two need different read loops, so figure out which one you have first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Family A: the checksum isn't the same in both directions
&lt;/h2&gt;

&lt;p&gt;Single-shot request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;55 AA 88 FF FF FF FF 84
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight m68k"&gt;&lt;code&gt;&lt;span class="nl"&gt;55&lt;/span&gt; &lt;span class="nv"&gt;AA&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt; &lt;span class="nv"&gt;STA&lt;/span&gt; &lt;span class="nv"&gt;FF&lt;/span&gt; &lt;span class="nv"&gt;DIS_H&lt;/span&gt; &lt;span class="nv"&gt;DIS_L&lt;/span&gt; &lt;span class="nv"&gt;SUM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;STA = 1&lt;/code&gt; means success. Distance is &lt;code&gt;DIS_H&amp;lt;&amp;lt;8 | DIS_L&lt;/code&gt; (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 &lt;code&gt;0x89&lt;/code&gt;, stop is &lt;code&gt;0x8E&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the part that eats an afternoon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tx_sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# request: Function + D1..D4
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rx_sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# reply: 0x55 + 0xAA + Function + D1..D4
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two differ by exactly &lt;code&gt;0x55 + 0xAA = 0xFF&lt;/code&gt;. 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...&lt;/p&gt;

&lt;p&gt;A hand-checked example (constructed, not captured from hardware):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;55AA8801FF04D25D&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;rx_sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;             &lt;span class="c1"&gt;# 0x35D -&amp;gt; low byte 0x5D
&lt;/span&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;                     &lt;span class="c1"&gt;# STA: success
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# 1234 counts -&amp;gt; 123.4 m
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Family B: units follow the model, not the family
&lt;/h2&gt;

&lt;p&gt;Frame: &lt;code&gt;5C dist_lo dist_hi check&lt;/code&gt;. The check byte is the bitwise inverse of the sum of the bytes between header and check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_b4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0x5C&lt;/span&gt;
    &lt;span class="nf"&gt;assert &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# little-endian
&lt;/span&gt;
&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_b4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5C0211EC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# 4354
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Raw unit&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;5C 02 11 EC&lt;/code&gt; means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LRF50H/100H/200H/300H/600H/1500H&lt;/td&gt;
&lt;td&gt;cm&lt;/td&gt;
&lt;td&gt;43.54 m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LRF22VB, LRF50VB&lt;/td&gt;
&lt;td&gt;mm&lt;/td&gt;
&lt;td&gt;4.354 m&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My strong opinion: &lt;strong&gt;don't auto-detect.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring: supply voltage isn't signal level
&lt;/h2&gt;

&lt;p&gt;These are two separate specs. Don't infer one from the other.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Just use the driver
&lt;/h2&gt;

&lt;p&gt;Once the trap list got this long, we wrapped it all up: model table, checksums and unit conversion. It's MIT-licensed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python (Raspberry Pi or a PC with a USB-TTL adapter):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;https://github.com/ErdiTech/erdi-lrf-drivers/releases/download/v0.1.0/erdi_lrf-0.1.0-py3-none-any.whl
erdi-lrf &lt;span class="nt"&gt;--port&lt;/span&gt; /dev/ttyUSB0 &lt;span class="nt"&gt;--model&lt;/span&gt; LRF3000A1 measure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;erdi_lrf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ErdiLRF&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ErdiLRF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/dev/ttyUSB0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LRF3000A1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;lrf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lrf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;metres&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ErdiLRF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/dev/ttyUSB0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LRF100H&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;lrf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lrf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metres&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(out of range)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out_of_range&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The codecs are pure functions with no serial dependency, so you can unit-test them offline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arduino:&lt;/strong&gt; install &lt;code&gt;ErdiLRF&lt;/code&gt; from the Library Manager (PlatformIO: &lt;code&gt;erdi-tech/ErdiLRF&lt;/code&gt;). There's an older, similarly named &lt;code&gt;ERDILRF_LRF&lt;/code&gt; in the index; that's not this one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;ErdiLRF.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="n"&gt;ErdiLRF&lt;/span&gt; &lt;span class="nf"&gt;lrf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Serial1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ERDI_LRF3000A1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;Serial1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;erdiDefaultBaud&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ERDI_LRF3000A1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// 115200 default for this LRF3000A1 example; use the exact model manual&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ErdiMeasurement&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lrf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ERDI_OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metres&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ROS 2:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ros2/erdi_lrf_ros2 ~/ros2_ws/src/
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/ros2_ws &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; colcon build &lt;span class="nt"&gt;--packages-select&lt;/span&gt; erdi_lrf_ros2
&lt;span class="nb"&gt;source install&lt;/span&gt;/setup.bash
ros2 run erdi_lrf_ros2 range_node &lt;span class="nt"&gt;--ros-args&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; port:&lt;span class="o"&gt;=&lt;/span&gt;/dev/ttyUSB0 &lt;span class="nt"&gt;-p&lt;/span&gt; model:&lt;span class="o"&gt;=&lt;/span&gt;LRF3000A1 &lt;span class="nt"&gt;-p&lt;/span&gt; rate:&lt;span class="o"&gt;=&lt;/span&gt;10.0 &lt;span class="nt"&gt;-p&lt;/span&gt; frame_id:&lt;span class="o"&gt;=&lt;/span&gt;lrf_link
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It publishes &lt;code&gt;sensor_msgs/Range&lt;/code&gt;. &lt;code&gt;field_of_view&lt;/code&gt; defaults to 0 because the protocol manuals carry no beam divergence, so set it from your model's datasheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the manual argues with itself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SPD1200D03:&lt;/strong&gt; the protocol section says 115200, the technical table says 15200/9600. The driver makes you pass the baud explicitly and returns no metric distance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LRF1200A1 / LRF3000A1:&lt;/strong&gt; continuous ranging is requested with &lt;code&gt;0x89&lt;/code&gt; but the recorded reply uses &lt;code&gt;0x88&lt;/code&gt;, and the manual declines to authorise a reply parser. Single-shot is fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LRF50VB:&lt;/strong&gt; the printed serial-number request fails the manual's own checksum function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A driver that guesses here is more dangerous than one that throws. Open a ticket with the vendor instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's not verified
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No measurement results or performance claims. Check your model's datasheet.&lt;/li&gt;
&lt;li&gt;The ROS 2 node has been tested in software replay only, not against a live ROS 2 runtime with hardware.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/ErdiTech/erdi-lrf-drivers" rel="noopener noreferrer"&gt;https://github.com/ErdiTech/erdi-lrf-drivers&lt;/a&gt; (MIT)&lt;br&gt;
Arduino library: &lt;a href="https://github.com/ErdiTech/ErdiLRF" rel="noopener noreferrer"&gt;https://github.com/ErdiTech/ErdiLRF&lt;/a&gt;&lt;br&gt;
Technical note (DOI): &lt;a href="https://doi.org/10.5281/zenodo.23297197" rel="noopener noreferrer"&gt;https://doi.org/10.5281/zenodo.23297197&lt;/a&gt;&lt;br&gt;
Model catalogue: &lt;a href="https://erdilrf.com/models/" rel="noopener noreferrer"&gt;https://erdilrf.com/models/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your unit behaves differently from its manual, I'd really like to hear about it in the comments or as an issue.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Vendor disclosure: the author is affiliated with ERDI TECH LTD (ERDILRF). Samples, datasheets and protocol questions: &lt;a href="https://erdilrf.com" rel="noopener noreferrer"&gt;https://erdilrf.com&lt;/a&gt; or &lt;a href="mailto:tommy@erdimail.com"&gt;tommy@erdimail.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>arduino</category>
      <category>raspberrypi</category>
      <category>ros</category>
      <category>hardware</category>
    </item>
  </channel>
</rss>
