If you’re going to build an autonomous car, an obvious requirement is that you need to be able to control the onboard computer (OBC), and the OBC handling all the autonomy has to actually be able to control the car. That’s what I’ll be talking about today.
The goals of this stage are as follows:
- Communication between the OBC and a laptop
- Send commands from the OBC to the steering servo and motor ESC
- Ability to override OBC commands with the remote control at any time
- Monitor the executed servo and ESC commands with the OBC for data recording purposes
Before diving in, a quick note about software. Both the OBC, and the laptop are running Ubuntu 14.04, and have ROS Indigo installed. ROS stands for Robot Operating System, and is a framework for developing robotic systems that provides tools and conventions for many of the common elements of a robotic system. If you’re not familiar with ROS, don’t worry. But a quick explanation about a few terms I’ll be using throughout these stories will help:
Nodes: Each computation process happening on a computer is called a node, and there will be many of these.
Topics: Communication between nodes is done through topics, which provide the name for what the data is, as well as a message containing the data itself.
Publishers and Subscribers: For a topic containing data to exist, a node needs to be publishing it, and in order for a node to receive information on a topic, they need to subscribe to it.
Onto the first goal: Communication between the OBC and the laptop. Although all the processing will be happening on the OBC, I’ll still need to be able to monitor what is happening from the laptop, and I’ll need to do so wirelessly. This means setting up an ad hoc network (think wifi, but instead of everything going through a router, two or more computers connect directly to each other). Fortunately ad hoc networks are fairly straightforward to setup, so not much to report here. To make things more convenient, I’ve added a script on the OBC to execute on startup that will automatically create the network. On the laptop I simply select the newly created ad hoc network like I would any other wifi network. Also, as a back up, I’ve setup an ethernet network, so that I can always login to the OBC with my laptop and an ethernet cable.
Goals 2–4 involve sending commands to the car’s steering servo and ESC, as well as monitoring the commands from the transmitter. The way an RC car works is on the transmitter you have a throttle trigger, steering wheel, and buttons, and the transmitter sends those controls to a receiver on the car. For a car, there will 2 or 3 channels of data being transmitted: one for steering, one for throttle, and an optional third channel sometimes used to activate reverse or some other function. The receiver converts the controls it receives to pulse width modulation (PWM) signals, which are then sent to the steering servo and the motor ESC. A PWM signal is essentially a series of on and off pulses. The position of a servo (or the percent of throttle for an ESC) is determined by the width (i.e. the duration) of the pulse received.
Therefore, two things need to be accomplished: the first is that I need to be able to send those PWM signals to the steering servo and ESC, and the second is that I need to be able to monitor the commands from the transmitter. On my transmitter, I’ve set a push button to act as the third channel for activating control override. So, in addition to monitoring the steering and throttle commands, I’ll also need to detect when that button is pressed.
An easy way to send and detect those PWM signals is with a microcontroller, such as the Arduino Nano that I’ll be using. How this will look is shown below. The transmitter will send commands to the receiver, which will be connected to the microcontroller so that it can monitor the incoming commands. When the microcontroller detects that override is active (i.e. that the third channel button has been pressed), the transmitter commands will be forwarded to the steering servo and ESC. When override is not active, the microcontroller will generate the PWM signals for the commands it receives from the OBC. With this setup the transmitter will always have priority over the OBC. Even if the OBC crashes, as long as the microcontroller has power, the transmitter will be able to control the car. Safety first.
Arduino has a nice servo library available, where you give it an integer between 0–255, and it generates the appropriate PWM signal. To measure PWM signals, interrupts on the microcontroller can be used, which detect when a signal going into one of its pins changes between high and low. Timing the length of that pulse will then identify what the command is. Also, ROS has a protocol for serial communication as well as libraries to support ROS functionality on Arduino devices called rosserial_arduino. All of which means the servo, ESC, Arduino, and the OBC, should play nice together.
The first step is to connect the microcontroller, steering servo, ESC, and receiver together. I’ve put some pin headers on a prototype board to provide connections for everything, and wired it underneath to connect to the microcontroller. For the pins, from right to left we have: 3 pins for ESC out (Vss, ground, and signal), 3 pins for steering out (Vss, ground, and signal), override signal from the receiver, throttle signal from the receiver, steering signal from the receiver, then the final two are the supply voltage and ground to the receiver. You may notice there is one extra pin, and also a transistor underneath the prototype board. The purpose of those will be discussed in the next story.
As mentioned above, there are ROS libraries and serial communication for Arduino devices. However, there is a considerable amount of overhead when using these libraries and sending/receiving messages via topics over serial. Furthermore, a small board like the Nano does not have much memory or processing power. This means that any processing the microcontroller does, and any serial communication, should be limited as much as possible.
With this in mind, I’ve divided the controls processing as follows:
- Any node wishing to control the car must publish messages on the cmd_car topic with steering and throttle values in the range [-1, 1]
- The car_command_converter node will run on the OBC, converting cmd_car messages to/from cmd_arduino messages in the range [0, 255] which the arduino servo library expects, as well as handling any fallback behaviour
- The arduino_controller node will use the cmd_arduino messages to generate the PWM signals for the steering servo and ESC, monitor the commands from the receiver, and publish the executed commands on the cmd_arduino_executed topic
With this configuration, the arduino has a minimal amount of processing and data to transfer. Messages being exchanged with the arduino are small, only two 8-bit integers for the throttle and steering values, and one boolean value for the override flag. The car_command_converter node is then responsible for publishing and subscribing to the larger messages with floating point values, and attaching time stamps to these messages.
I won’t walk through any of the code for these nodes, otherwise this would turn into a novel. But, if you are interested you can view the full code for these nodes (and everything else completed thus far) on GitHub.
After testing to verify all the nodes are operating properly, all the hardware can be mounted on the car. An old 4s lipo battery to power the OBC and a piece of acrylic to serve as a platform is all that’s needed for the car to be mobile.
Later on once I have the IMU and camera integrated I’ll invest the time to package everything nicely. But for now, everything works!
Now that the preliminary work to setup the controls is out of the way, the next phase will begin the autonomy work. A big part of being autonomous is knowing where you are, and figuring that out will be the subject of the next story. I’ll give some background on the localization problem in robotics, and explain (one of the ways) I’ll be using to estimate the car’s position.
If you are interested in the code, checkout the Github repo. Or for the previous stories, you can find them here: