First off, a big thank you to the hardware and software developers of the APM autopilot. Without it I wouldn't have been able to pull off a working autonomous system in such a short time. Thanks as well to Jack Dunkle for helping me out with a late encoder addition.
A couple months ago I saw the SparkFun AVC announcements and decided to enter. I bought parts early, but didn't get it all working right until the end. Here are all the details on my entry, for anyone interested.
- Traxxas Rustler VXL
- Reasonably fast RC car straight out of the box. I probably could have saved some bucks and gone with the non-VXL (brushed motor) version and not known the difference. Came with a 2-channel RC receiver/transmitter pair.
- I thought about switching out the included 8.4V NiMH battery, but it ended up being more than sufficient.
- 5V switching regulator
- The Traxxas ESC puts out 6V so an external regulator was necessary. This one died a unfortunate squealing death the week before the competition and I actually ended up replacing it with a simple LM7085T.
- ArduPilot Mega / OilPan IMU
- The APM was a no-brainer given that electrically it does everything I needed, and there are code libraries for almost all the sensors I was using. I'm naturally lazy and this was clearly the path of least effort.
- I actually didn't end up using the IMU (except for magnetometer roll/pitch correction), but that's okay since I'm hoping to repurpose this hardware on a quad.
- SkyLab SKM53 GPS
- This was the first thing I purchased (bought a GPS, had to build a robot around it). It uses the standard MediaTek MT3329 module. In retrospect I should have gone with the module + adapter from DIY Drones.
- HMC5843 Magnetometer
- Not much of a choice here.
- US Digital E4P-100-079-HT Encoder
- A couple weeks before the competition I panicked and thought that I needed a wheel or drivetrain encoder (probably not true) and so I added this on the motor shaft the Tuesday before the competition.
- Maxbotix LV-MaxSonar-EZ3 Rangefinder
- My code gets data from this, then disregards it and drives into walls. Ultimately I decided that there wasn't enough time to get this up and running in a way that wouldn't be a liability. So, my obstacle avoidance ended up being careful waypoint placement and a bit of luck.
My code runs three loops, nominally at 100Hz, 10Hz, and 1Hz. Here's what each one does.
- Check for user input (autonomous mode or emergency stop). Since I only had a 2-channel transmitter, I mapped the extreme steering limits to these inputs. Also turns off autonomous mode if GPS fix is lost.
- Update the DCM. I pretty much blindly copied this from the APM software. The magnetometer needs roll/pitch values for calculating the heading.
- Calculate motor velocity (counts/sec) from encoder change since the last 100Hz loop. The encoder itself is read by pin change interrupts. I found that I had trouble monitoring high speeds (~150000 counts/sec and up), but it might have been a side effect of the monitoring code, since I think the encoder-reading code is less than <10 instructions per interrupt. That's blazing though, since 100000 counts/sec is about 25MPH on my car.
- Control the velocity with a PID controller. The encoder lets me do closed-loop control on the throttle input to the ESC. In retrospect this probably wasn't necessary.
- Update the servo outputs, or relay the inputs from the receiver if in manual mode.
- Update the indicator lights (A = autonomous, B = GPS fix).
- Read the latest data from the GPS, compass, and rangefinder.
- Calculate distance to the target waypoint.
- <4m, slow down in preparation for steering.
- <2m, increment the waypoint index (waypoint achieved).
- Calculate distance to the last waypoint.
- >4m, speed back up.
- Calculate the absolute heading to target waypoint (GPS data only).
- Calculate the heading error (absolute heading - compass heading).
- Scale the steering output linearly to the heading error. This is pretty crude, and might have caused my trouble in the 3rd race.
- Spit out human-readable debug information over serial.
And that's it! I tried to do some fancy stuff with the rangefinder, detecting obstacles approaching at close to vehicle speed (stationary obstacles straight ahead) and then making the car swerve to avoid them. It worked too poorly and so I took it out, since I figured it would probably hurt more than help.
The last little bit is dealing with GPS coordinates (latitude and longitude). I converted them immediately to Cartesian coordinates on a plane. In a small area, this works very well. I had two sets of conversion factors, one for Pasadena and one for Boulder.
Compass mounting calibration was key for getting this to work. My method was setting a waypoint, pointing the vehicle toward the waypoint, and changing an offset parameter until the heading error was close to 0.
I was able to avoid the barrels and get the arch bonus by careful waypoint placement to minimize the effect of GPS error (waypoints far apart). Also, I was lucky.
I came to Boulder Thursday night without working throttle/steering controllers and no waypoint code written, but I managed to work it out Friday morning and afternoon, testing in my friend's apartment's parking lot. Friday evening I tried to write obstacle avoidance but scrapped it and slept instead.
Saturday morning I got to SparkFun a little early and set up my waypoints (7 of them), and ran some test runs. I made it around about half the time, doing better after I altered the steering trim to eliminate a leftward (and water-trap-ward) drift.
- Race 1
- At Turn 1, I got tangled up with another competitor. My robot achieved its waypoint, turned sharp, and lodged a steering wheel between the other bot's drive wheel and chassis, stopping us both. This was unfortunate. I think SparkFun has a great picture of this.
- I tried to complete the course anyway, but the GPS data seemed to be poor and I had to hit the emergency stop to avoid going into the pond.
- Race 2
- Right after Race 1, I moved my GPS module to a different spot and carried my car around the course, watching the steering. Things seemed to be much better, but it's also possible that the improving weather helped just as much.
- I also got paranoid about the pond and added some code to reduce the speed on that stretch, reducing the possibility of jumping the curb and giving me more time to activate the emergency stop.
- The race went great, I made it through the arch and felt quite good about things when I made it to Turn 2. It seemed like my car was going slow, and I realized that I didn't add code to speed back up after the pond. D'oh, that's what I get for mucking around with things. I ended up doing most of the race at 60% desired speed.
- Right before Turn 4 I went straight through the biggest pothole on the course (filled with water), but the electronics stayed dry and I made it across the finish line at 1 minute, 25 seconds (0 minutes, 55 seconds after the 30 second arch deduction). That put me in second place, behind Team Minuteman at 26 seconds post-deduction.
- Race 3
- Having recorded a good time in Race 2, I went all out, getting rid of the pond slowdown and doubling my speed parameter.
- Something went horribly wrong and right off the starting line I headed for the curb, hitting that before going berserk and crashing into a stroller off-course, breaking my right front caster block and bearing carrier.
- There are two things I can think of that might have happened.
- My crude steering controller is unstable at high velocities (most likely).
- The first waypoint somehow got marked as achieved, and the car started going for the second waypoint.
- The car wasn't drivable without spare parts I don't have, so I missed out on the mass run.
I ended up with 3rd place and $100, which is much better than I expected. Congratulations to Team Tobor and Team Minuteman, from what I've read, heard, and observed, they were both running very slick systems. Thanks to SparkFun for putting this competition on, I'll definitely be back next year!