Introduction: Arduino Powered Autonomous Vehicle
A few months back I started playing around with Arduino micro controllers as a learning exercise (and for fun); this project is the culmination of that. The goal of the project was to create a vehicle that can autonomously navigate through a series of waypoints (GPS coordinates) while avoiding any obstacles it encounters along the way.
The project uses an assortment of electronic sensors and components, and pulled together the knowledge I had learned and synthesized from many sources along the way.
In the attached video you can see a short clip of the car on its way, in this run it navigated through five GPS waypoints on a course on my neighborhood streets totaling about 300 meters.
Step 1: Component List & Project Cost
The main components were the following:
- A basic radio controlled (RC) vehicle. Can be a basic one like I used which are available in the $15 range. If you want to spend a bit more, get one with proportional steering and four wheel drive. The one I used was similar to this one from Amazon.com (though it came from Wal-Mart and at a lower price at the time).
- An Arduino Uno micro controller. Amazon.com $24
- A motor shield to control the two electric motors and allow for a separate motor power supply. Adafruit Motor Shield v2 $19.95.
- A GPS for navigation. Adafruit Ultimate GPS Shield $49.95 (or breakout for $39.95)
- A magnetometer for compass navigation. Adafruit HMC5883 Magnetometer $9.95
- An HC-SR04 ultrasonic distance sensor for object avoidance. Amazon.com $6.00
- An LCD display to display vehicle status and information. Yourduino.com $5.75 (I later upgraded to a 4-line LCD for about $12)
- An infrared sensor & remote. Optional, added some convenience but not required for the project. I already had these components from a previous kit from Yourduino.com
- And of course an Arduino sketch (a C++ program) to control everything (code attached in this Instructable)
Additionally, the project used the following smaller components and accessories:
- A thin wood board as a mounting platform; acrylic or other would have worked (and probably looked better!), but this is what I was able to find at local hobby shops
- Breadboard(s) for making connections. I used a long narrow breadboard for the main connections, and a very small breadboard (that originally came with a proto-shield) so that I could mount the magnetometer as far from the other electronics as possible
- Jumper wires
- Surgu for mounting the ultrasonic sensor. Amazon.com $12
The following tools were used:
- Soldering iron & solder
The rough project cost is around $120 - 150 depending on what components you may already have.
A note on project cost: other than the mounting board, almost all of the other components are re-usable; either things you already have that you can use for this project, or things that we can eventually disassemble from this project and re-use elsewhere.
Step 2: Vehicle Chassis and Mounting Platform
I had seen posts on the internet about hacking inexpensive radio controlled (RC) cars and directly connecting an Arduino to the existing circuit board. I happened to have such a car around that my 3 year old no longer played with; it was a $15 Wal-Mart RC car.
Unfortunately, my early soldering skills left a lot to be desired and I burned through a couple the delicate surface mount components, so I ended up with a partially functioning vehicle.
Plan B: I ripped out the car's entire control board and purchased an Adafruit Motor Shield (v2). Problem solved. Now I had full control over the vehicle's motors...though they were pretty basic.
The car was controlled by two DC motors: one controlled the drive, and using the pulse wave modulation (PWM) of the motor controller I was able to control the speed across a range of speeds; the other controlled the steering. This inexpensive RC car did not have proportional steering; the left and right wheels are joined, and there is a spring in the middle that holds the wheels in neutral (center) position when the DC motor is not engaged. When the motor is engaged, it goes to a full/hard turn left or right. That allowed me to turn the vehicle, but provides limitations later when I want more sophisticated navigation. For a future enhancement I will try to replace the DC motor with a servo for full proportional steering control.
I used a thin board as a mounting surface on which I attached the breadboards, Arduino, LCD, etc. I placed the battery supplies beneath the board and passed the cables through holes I drilled.
In the first photo above, you see (1) the LCD, (2) the main breadboard, (3) the small breadboard for the magnetometer, (4) the Arduino (you are seeing the GPS Shield as you look down), and (5) the magnetometer sitting up high on its pole mounted perch.
Step 3: Program Logic
The Arduino is controlled through a C++ program ("sketch"). The main action happens in the Arduino sketch loop() function which runs repeatedly. The basic program control logic is:
- Check to see if the kill switch was pressed (if enable in the configuration).
- Process any new GPS information and update the course and distance to the target. Move on to the next waypoint if we have reached the current destination.
- Read compass to get current bearing and decide the desired direction to turn the car
- Move the vehicle and check for any obstacles we need to avoid.
- Update LCD display
The code to handle each of these is in separate functions.
Fully documented source code is attached.
Step 4: LCD Display
The LCD provides invaluable insight what the vehicle is doing, critical for debugging and tuning the code. It also looks cool!
While running, the main screen shows the following information:
1. tH = Target Heading, the course to the current waypoint;
2. cH = Current Heading, the direction the vehicle is actually facing
3. Err = Error (in degrees) between the target heading and compass heading; this is a signed value indicating which direction (left or right) the vehicle needs to turn to intercept the target heading;
4. Dist = Distance (meters) to the current waypoint; you will notice the small inline bar graph showing the remaining distance to this waypoint.
5. Snr = Sonar distance, i.e. the distance to any objects in front of the vehicle. Also has an line bar graph from 0 to the maximum detectable distance;
6. Spd = Speed of vehicle (0-255)
7. Mem = Memory (in bytes) of free memory; the Arduino Uno only has 2k so I had to watch this closely;
8. WPT n OF x; shows where the vehicle is in the list of waypoints.
Step 5: Object Avoidance
To drive autonomously, the vehicle needs to be able to check for and avoid obstacles it encounters as it drives. I handle this with a "ping" ultrasonic sensor and some computer logic.
The sensor is a basic ultrasonic sensor. I combined that with the Arduino NewPing library, which is a big improvement over the original Ping library (among other things, it only requires a single shared pin for both send & receive).
The sensor is pretty basic, and has a very narrow field of view. For this project, I am only using a single sensor with a fixed position (not a sweeping "radar" type implementation).
I mounted the sensor to the front bumper of the vehicle with some Surgu. This was my first time using the product, and it works very well. I only used a single 3.5g packet, and that was sufficient for this purpose.
The sensor has a tendency to return the occasional odd or random value...not sure why. In his excellent series of articles, Miguel Grinberg offers a simple solution: use a moving average. I adopted his MovingAverage class to the project with good results.
The checkSonar() routine continually takes new measurements, adding each new measurement to the moving average; the average is then used for program logic.
If an object is detected, the following logic is applied:
- Slow down
- If the vehicle is going straight (not turning), turn in the direction closest to our waypoint (technically, closest to the course to our waypoint).
- If the vehicle is already turning, then turn in the opposite direction to try to avoid the object.
- If we get within a definable distance (TOO_CLOSE) of the object, stop, backup, and try again.
Once we have a clear path ahead, normal navigation resumes.
Step 6: Waypoint Management
In order to navigate a course, we need a way to manage the various waypoints. I started by creating a simple WaypointClass that holds a longitude and latitude value. I then created an array of WaypointClass members to hold the waypoints, and a currentWaypoint variable to keep track of the current waypoint.
We continually check the distance to the current waypoint; if the distance falls within a configurable tolerance (say 5 meters), we say that the current waypoint has been reached and advance to the next waypoint in the array.
A waypoint with 0 / 0 values signifies the end of the program.
Step 7: GPS Navigation
We use the GPS to answer the basic question "Where are we right now?" Since we the waypoints are known constants, with the current local information we can then calculate the distance and course to the current waypoint.
I used the excellent Adafruit Ultimate GPS Shield for the GPS. I was very impressed with this unit. My workshop is in the house in an upstairs bedroom where I didn't expect to receive a GPS signal at all. To my surprise, I could get a good quality fix with 8 - 10 satellites! The acquisition time was amazingly fast...a few seconds, and much faster than my expensive TomTom car based GPS. Note: since the GPS shield was on the top of my vehicle, I saved money by not purchasing the separate antenna assembly and instead just used the built-in antenna...it worked great.
I used the Adafruit GPS library, mainly because of the good sample code that allowed the GPS reading to occur in an interrupt routine; that really freed up the rest of my code so that timing was no longer a concern.
I borrowed code from the TinyGPS library to create functions to calculate distance-to-waypoint and course-to-waypoint.
Step 8: Compass Navigation
The GPS works great for providing accurate location data, but the scale on which this project operates is too small for it to provide accurate heading information (the car can perform a 360 degree turn within a radius of about 6 feet, which is smaller than the typical accuracy of the GPS).
I utilized a digital magnetometer (which I refer to in the code as the "compass" through technically it isn't a compass). The "compass" provides a super fast readout of the vehicles current heading.
With our current location from the GPS and our current heading from the compass, we calculate the course to our destination and which way to turn (left/right) to intercept the target course.
Note that due to limitations in the steering ability of this inexpensive RC car, there was no proportional steering and no way to use more sophisticated PID logic. Instead, I did a poor man's approach: I set a configurable "heading tolerance" of +/- 10 degrees. Meaning if our desired heading was 180 degrees, any course from 170 to 190 was acceptable to the program (note: I played with this and tuned it...about 8 degrees seemed to work best). This prevented constant steering corrections in the short term. In the long term, the problem was self-correcting: the "course to waypoint" is not static, it is constantly recalculated, so if our actual heading is off from the target heading, as we continue moving forward a new target heading is calculated and will eventually fall outside of the steering tolerance, resulting in a turn towards the target path.
The magnetometer is very sensitive to electrical interference, so originally I mounted the compass on a mini-breadboard as far from the DC motors as possible to avoid the main culprits of interference. Unfortunately, there was still too much interference, leading to inaccurate and random compass readings. I ended up having to mount the magnetometer on a pole sitting about 10" above the car; that seemed to work well.
Step 9: Motor & Speed Control
The vehicle's speed is controlled through pulse wave modulation (PWM) provided by the Adafruit Motor Shield. Basically it allows us to apply partial power to the DC motor (i.e. run at 25%, 50%, 75%, 100%, etc). For practical purposes, the motor needs around 20% power just to have enough torque to overcome resistance and get rolling; and too fast of speeds made the vehicle too difficult to control. We defined a series of speeds as #define statements in the program, like SPEED_SLOW, SPEED_FAST, SPEED_NORMAL, SPEED_TURN, etc.
The program logic sets the speed based on the following logic:
- If the vehicle going straight (not turning), and no objects are detected, go fast
- If the vehicle is going straight and detects an object, slow down
- If the vehicle is turning (to avoid and object or just for intercept a navigation heading), slow down to the "turn speed"
The steering mechanism has a spring that holds the steering in the center position when power is not applied to the steering motor; this limits the steering to either a hard-left or hard-right; proportional steering is not possible.
In the photo above, you can see (1) the rear wheel drive motor and (2) the front wheel steering motor.
Step 10: Infrared Sensor and Remote
I added an infrared (IR) sensor (with corresponding handheld remote) to provide some convenient functionality :
- Kill switch. It is convenient to have a "kill switch" feature to stop the vehicle if it is behaving poorly.
- Controlled start. When you power up the Arduino, the vehicle may start moving before you are ready. The IR remote allows a way to add a "push button to start" functionality for a controlled start.
The IR sensor is only marginally effective as a kill switch; it is easily hindered by direct sunlight and has a limited range.
The remote control was nice for debugging the unit; I ultimately removed it in the final version to save memory, as the Arduino sketch was getting tight on the Arduino Uno's very tight 2K of SRAM space available for variables, data and the stack.
Step 11: Potential Future Enhancements
I have a list of potential enhancements that would be nice:
- Better RC car as the chassis. My son has this car (Amazon.com $40), and it is pretty awesome...4 wheel drive, works great on grass, rocks, dirt, etc. No proportional steering though.
- Add proportional steering...either get an RC car with this, or hack or upgrade an existing car. A servo may be one solution.
- Add a SD card for logging the GPS track. This would require upgrading to an Arduino Mega, as the SD card requires about 700 bytes of RAM which I don't have available on the Uno.
- Add a better remote/kill switch.
- Add a camera for still photos or video. A first person view video would be entertaining, and helpful for debugging.
- Add a radio to send live telemetry data so I can track progress and information in real time. A two way radio might also allow for remote upload of waypoints instead of having to recompile the Arduino sketch each time.
Step 12: Final Thoughts
This was a fun project. I learned a ton, and about a wide range of topics. I also benefited from a lot of information and resources that others have shared on the internet. The full program code is documented and attached, I hope you find it helpful.