I’ve been experimenting with the idea of using a Neato LIDAR module paired with a BeagleBone Black to form the core of an autonomous wheeled robot. This would be done using a SLAM algorithm running on the the BeagleBone Black, with the LIDAR providing the robot’s vision. So far, I’ve been able to hook up the LIDAR to one of the serial ports of the BeagleBone and read the ranging data that it outputs.
Enabling a Serial Port on the BeagleBone Black
On the BeagleBone Black, each I/O pin can be used for multiple purposes (but not at the same time). This means that before using a pin, we need to be sure it is in the correct mode. The mode can be set using device tree overlays. In this case I just wanted to enable the RX and TX pins for the 2nd serial port, which was easy to do by running the following commands from a terminal connection to the BeagleBone. Note that I’m using a Revision C BeagleBone Black with the Debian OS that came pre-installed.
cd /sys/devices/bone_capemgr.* echo BB-UART2 > slots
After doing this, I ran
cat slots to verify that it had been setup correctly. I also used
dmesg to check the logs and make sure there wasn’t anything unusual.
I originally tried this using the 1st serial port (BB-UART1), and while everything looked fine initially, things didn’t seem quite right when I ran the following commands to check the pin modes.
cd /sys/kernel/debug/pinctrl/44e10800.pinmux cat pingroups cat pins
cat pingroups told me which pin numbers were being used for the serial port, and
cat pins provided a list of the pins along with the mode that each one was in. For BB-UART1, it turned out that both the RX and TX pins were set to 0x20, which is pin mode 0, in input mode (note: reference the BeagleBone Black documentation for an explanation of the different pin modes). This didn’t seem right, as the TX pin should be in output mode. When I switched to BB-UART2, the pin modes for the RX and TX pins were 0x21 (input, mode 1), and 0x01 (output, mode 1), which is what I expected.
The Neato LIDAR uses a baud rate of 115200, so it is important to set that correctly like so:
stty -F /dev/ttyO2 115200
Wiring the Neato LIDAR
I connected the 4-wire strip from the Neato LIDAR to the BeagleBone Black as follows (red wire last):
|Neato LIDAR||BeagleBone Black|
|Black Wire (GND)||P9_1 or P9_2 (both ground)|
|Orange Wire (LDS_TX)||P9_22 (uart2_rxd)|
|Brown Wire (LDS_RX)||P9_21 (uart2_txd)|
|Red Wire (+5V)||P9_5 or P9_6 (both VDD_5V)|
Connecting the motor directly to a 3V power supply will cause it to spin at a speed within the acceptable range. Later on, I plan to drive the motor with a PWM signal and use the rpm data provided by the serial connection to keep the speed consistent, but this is good enough for now.
Testing the Serial Connection
At this point, I ran
cat /dev/ttyO2 and spun the LIDAR by hand to verify that the connection was working. Of course, this only produced random characters since the data was encoded. Next, I used
screen /dev/ttyO2 115200 to open a serial connection that would allow me to send commands to the LIDAR. For example, typing
getversion returns some basic information about the unit. There are a number of other useful commands for testing and calibration, which can be found by typing
help. Be warned that some of these might brick the device if used improperly. There is also some documentation for this on the xv11hacking website.
Reading Ranging Data
To view the data as hex values, I ran the following Python script while spinning the LIDAR motor manually.
print("Hello") with open("/dev/ttyO2", "rb") as f: byte = f.read(1) count = 1 while byte != "" and count <= 22*100: print(byte.encode('hex') + ":") byte = f.read(1) count += 1 print("Goodbye")
This produced the expected output of 22 bytes per packet, as detailed here. There were a few bad packets, but not enough to have much of an effect. However, the 4 data reading sections of each packet were invalid. Most of the data reading hex values were
50 80 00 00, but there were also a few that were
52 80 00 00. The second byte’s value of 80 is what indicates that the data is invalid in both of these cases. The first byte is apparently an error code, but I haven’t been able to find out what the error codes 50 and 52 mean.
After a bit of head scratching, I finally figured out that the data was invalid because the LIDAR was not spinning at the proper rate. Powering the LIDAR’s motor with 3V and updating my Python script to capture 1000 packets instead of 100 produced good data. Capturing only 100 packets simply didn’t give the LIDAR time to get up to speed.
Valid Sample Data:
fa:a0:27:4b:97:01:bb:01:97:01:b4:00:98:01:53:00:99:01:93:00:4e:28: fa:a1:35:4b:9a:01:b9:01:9a:01:8b:02:9a:01:71:02:9b:01:8b:02:a6:5f: fa:a2:35:4b:9c:01:90:02:9d:01:89:02:9e:01:72:02:a0:01:8a:02:d8:16: fa:a3:35:4b:a1:01:85:02:a2:01:7d:02:a4:01:76:02:a7:01:6a:02:aa:16: fa:a4:35:4b:a8:01:5c:02:aa:01:78:02:ad:01:6f:02:af:01:4b:02:bb:10: fa:a5:35:4b:b2:01:57:02:b4:01:61:02:a8:01:c3:00:97:01:9a:00:96:0b: fa:a6:35:4b:35:80:00:00:79:01:ab:00:21:80:ab:00:61:01:f9:00:7a:08: fa:a7:5b:4b:55:01:26:01:4a:01:44:01:41:01:41:01:37:01:45:01:78:16: fa:a8:5b:4b:2f:01:77:01:26:01:8b:01:1f:01:9b:01:17:01:a4:01:23:18:
Visualizing the Data
The only thing left at this point was to graph some of the sample data so that I could determine how accurate the Neato LIDAR’s readings were. For this part I used a Python script on my laptop.
import numpy as np import matplotlib.pyplot as plt def getDist(lsb, msb): if (msb == "80" or msb == "70"): return False return int(msb + lsb, 16)/1000. def addPoint(dist, theta, x, y): if (dist != False and dist < 6): print(dist) x.append(dist*np.cos((theta+90) * np.pi/180)) y.append(dist*np.sin((theta+90) * np.pi/180)) x= y= with open("C:\Users\Alex\Desktop\lidardata.txt") as f: line = f.readline() while line != "": bytes = line.strip(':n').split(':') if (len(bytes) == 22): theta = (int(bytes, 16) - 160) * 4 # print(line) print(theta) addPoint(getDist(bytes, bytes), theta, x, y) addPoint(getDist(bytes, bytes), theta + 1, x, y) addPoint(getDist(bytes, bytes), theta + 2, x, y) addPoint(getDist(bytes, bytes), theta + 3, x, y) line = f.readline() fig = plt.figure() ax = fig.gca() ax.set_xticks(np.arange(-10,10,0.2)) ax.set_yticks(np.arange(-10,10.,0.2)) ax.set_aspect('equal') plt.scatter(x,y) plt.grid() plt.show()
Running this script against a small sample of the data collected earlier yielded the graph below. Some sections were missed, but the measurements that were captured are surprisingly accurate (plus or minus a few centimeters in most cases). Note that the measurements shown on the graph are in meters.
For more information about the Neato LIDAR sensor, I recommend that you read the following posts:
- xv11hacking – LIDAR Sensor
- Neato’s LiDAR Module – Wayne’s Tinkering Page
- Steven’s Robotics Blog: Neato XV-11 LDS menu test
For more information about the BeagleBone Black serial ports, see the following links. Some the information in these posts may only be applicable to certain versions of the BeagleBone Black.
- Exporting and Unexporting an Overlay | Introduction to the BeagleBone Black Device Tree | Adafruit Learning System
- Beaglebone Coding 101: Using the Serial and Analog Pins | GigaMegaBlog
- BeagleBone: Serial Ports and XBees – Jerome Bernard Blog
- Enable serial/UART/tty on BeagleBone Black | Hipstercircuits