<?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>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>
