Autonomous RC Car Part 6
LiDAR; 9-DoF IMU; I2C failures — Oh My!
In Part 0 we discussed putting an inexpensive 2d LiDAR unit on the car. This may seem like a low-value target considering our test track is an open field and the goal of finding a steering angle is more useful than obstacle avoidance. The value of this may be fully realized later, but we can begin getting some data from the RPLIDAR unit into our Raspberry Pi.
LiDAR
First, I have to comment on the packaging of the RPLIDAR unit. It’s seriously impressive. Three layers of cut foam protect the unit and allow space for the USB control interface. The cable is in a small, flat box with the quick start setup card. It has that ‘designed by Apple’ feel — more of a product and not just a replacement part like the Neato XV-11.
I do wish that the connector was at 90º so the wire wouldn’t stick down. As it is, you can’t place the unit on a flat surface (that’s why it’s sitting atop its box) and I’ll have to modify the mount plate on the car to accommodate the wire sticking down. I’m concerned this will interfere with the drivetrain components on the car, but we’ll get to mounting later. For now, let’s connect this to a computer and see if we can get some data out of it. Fortunately, there’s an open source Python module for just this task. You can install it with pip install rplidar
.
Connect the unit to your computer, do an ls /dev/*USB*
to find the UART (mine is named /dev/tty.SLAB_USBtoUART
), drop the string into the code below and run. You should get 10 scans and then exit.
The library gives you three values for each point: quality, angle, and distance. Quality of a measure of how much light was received by the sensor. Maybe points with a low quality score should be removed from the data set. Angle and distance are self-explanatory. A simple line plot of the output makes this more clear. There’s some noise here for sure, but more than enough signal for obstacle avoidance.
ROS?
The obvious question to ask now is, why aren’t you using ROS — the Robot OS? Yes, ROS has some really great tools for visualizing LiDAR point clouds, path planning, SLAM (simultaneous location and mapping), odometry, IMU… the list goes on. However, ROS has some drawbacks as well. It’s difficult to install, written in Python 2, requires quite a bit of memory, and the rosbag
file format is hard to work with. Perhaps most importantly, this is an exploratory project designed to surface the difficult problems inherent in building autonomous vehicles and ROS will hide that difficulty and complexity. I’m going to see how far I can get without it and remain fully aware that I don’t want to rebuild the entire ROS toolchain.
Telemetry
First, we need to get our sensor data from the Arduino into the Raspberry Pi. We discussed using I2C for this and, without ROS, it’s still a good choice. First, make sure the I2C interface is enabled on the Raspberry Pi.
- Run
sudo raspi-config
- Select
Interfacing Options
- Select
P5 I2C
- Select
Yes
to enable - Exit, and you’re ready to test it
- Run
i2cdetect -y 1
to make sure it’s working
I2C is a ‘master/slave’ protocol (one day engineers will move past this terminology). It consists of both a BUS and and ADDRESS. A single device can have multiple busses, but the one built in to the Raspberry Pi is always bus 1. This is easily confirmed with ls /dev/i2c-1
.
Let’s connect something to the Raspberry Pi and see if this works. My Adafruit 9DoF Stick IMU showed up in the mail, so we can use that. We’ll have to connect our Arduino code from Part 4 later, but since the 9DoF Stick speaks I2C natively, this makes for a good test. Good integration tip: When possible, start by testing with things that already meet a specification before writing code to that spec.
What we’re looking for is the I2C data and clock pins. These are SDA (Serial DAta) and SCL (Serial CLock). We’ll also need 3.3v and ground which are easily spotted at pins 1 and 39 respectively. We’ll connect these to the IMU corresponding pins with green as SLC, blue as SDA, red for 3.3v and black for ground.
Run i2detect -y 1
again and we’ll see our sensors on pins 1E and 6B. If we had other devices on conflicting addresses these can be changed by scratching off traces on the back of the 9DoF Stick. Refer to the I2C Address Table in the schematic for the corresponding addresses.
Then grab data from one of the devices.
It returned 0x00
but it was supposed to. Use an address with nothing attached and it’ll throw an error. No error? It’s working. Let’s build something in Python that’s more interesting than showing zeros. Adafruit provides libraries to get more intelligible data out of the IMU, so we’ll use that. It depends on the WiringPi library, so we’ll have to install and compile that first.
sudo apt-get install libi2c-dev
git clone git://git.drogon.net/wiringPi
cd wiringPi
./build
- Check that
/usr/local/lib/libwiringPi.so
exists
Then we can install the 9DoF Stick libraries. Let’s install them and run the provided example.
git clone https://github.com/akimach/LSM9DS1_RaspberryPi_Library.git
cd LSM9DS1_RaspberryPi_Library
make
sudo make install
cd LSM9DS1_RaspberryPi_Library/example
sudo python LSM9DS1_Basic_I2C.py
Moving the IMU around makes the numbers change. By inspection it’s obvious that there’s some noise in the system but that’s a problem for another time. Our goal was to get data through I2C and we have. Next we need to get steering angle information from the Arduino to the Pi. This has one added complexity as the Arduino is 5v and the Raspberry Pi and the IMU chip need 3.3v. In order to protect our equipment we’ll use a 4-channel Logic Level Shifter. Bi-directional shifters like this require two reference voltages — 3.3v and 5v in our case — so the unit knows how to step the channel pins. Fortunately, the Raspberry Pi provides both 3.3v and 5v pins.
I’m sure someone will say this is unnecessary. It is unlikely that I2C will damage a component, but I2C is an ‘open drain’ protocol which means the lines are HIGH in the default state. The 9DoF Stick includes the necessary pull-up resisters — in this case 4.7kΩ — so at 5v Ohms Law says this will 1.06mA most of the time. Using 3.3v makes this 0.7mA and keeps a standard voltage for all our I2C components.
To connect our Arduino to the I2C bus, we need to find the SDA and SCL pins just like on the Raspberry Pi. Here’s the pinout diagram. Can you find them?
Cool. Let’s wire this up.
Note that the Arduino is now being powered by the Raspberry Pi. We’ll have to be careful to remove this power when programming the Arduino so we don’t damage it. Or we could use an FTDI adapter and not connect the power pins to the Arduino. Or maybe connect the Raspberry Pi UART serial pins to the RX/TX pins of the Arduino so we can program the board using the Arduino CLI tools. Anyhow, here’s what it looks like.
Now we need to get our Arduino connected as an I2C slave… and this is where it all goes wrong.
I2C Failure
When I load up the ‘slave_writer’ example and program this Arduino, it should show up on the i2ccdetect
output as address 0x08
. It doesn’t, and nothing I’ve done has made it show up. The 9DoF Stick addresses show up just fine, but I can’t get an Arduino to speak I2C as either a master or slave. I have tried:
- Other Arduino Nano’s and Uno’s
- Disabling the small pull-up resisters on the Arduino (Add
pinMode(SCL, INPUT); pinMode(SDA, INPUT)
; to yoursetup()
function) - Wiring up the ‘master_reader’ and ‘slave_writer’ examples
- Using the LSM9DS1_Basic_I2C sketch provided by SparkFun
- Using various I2C scanner sketches for an Arduino to try and detect addresses
I did find this really cool interactive I2C scanner for Arduino. There are many versions of this spread through forum posts and older Github accounts. I tried a few and liked this one best. Truncated output looks like this:
Note the distinct lack of addresses being populated. So, if I2C doesn’t work, then what?
Serial Communication
I2C isn’t the only way to communicate between a Raspberry Pi and an Arduino. We’ve been using the serial monitor in the Arduino Editor to pull info out for debugging, right? We’ll use that same interface for getting telemetry from the Arduino. This isn’t ideal because now we have *two* protocols to support (I2C for the 9-DoF Stick IMU and serial for the received signal information), but maybe we can resolve that later. If we wire the RX/TX pins of the Arduino to the UART (Universal Asynchronous Receive Transmit) pins of the Raspberry Pi, we can listen to the serial output of the Arduino from the RPi.
This should look familiar. All we’ve added is the grey and yellow wires to the diagram. Fortunately we had two channels free in our logic level shifter so this is a small change. As always, check the pinouts if your RPi or Arduino versions differ from mine. Let’s write some code and see how this works.
This will output a ‘.’ as a heartbeat every second or return whatever string was most recently sent to the buffer. We can test this with the command line. In one terminal we will cat
the output of the device, and in another we’ll echo
some string.
There we go! Bidirectional serial communication between a Raspberry Pi and an Arduino.
Moving On
We’ve gained some new capabilities in this installment: LiDAR, IMU data, and a bidirectional communication channel. We can use these together to record the training data we’ll need for our behavioral cloning model. Next, we need to move past the breadboard stage and get our electronics soldered up and mounted on the car. It took so long for me to fail at getting the I2C stuff working and rework toward the serial solution that these beautiful Pi Hats arrived from Adafruit.
Once we have some more securely mounted hardware we can write some Python to record data coming from our Arduino, LiDAR, IMU and the output of our lane detection OpenCV code. These will be the inputs to the model we will train.