Here I explain some of the basics, and you actually get to see me improve the code, upload it to the onboard processor, and you even get to see the results. Obviously, there's a lot to be done before this is ready for retail, but I'm confident that the next 12 months will deliver good results, as long as I stay focused on this project instead of being distracted by a Plan B project.
Update: Source Code and Hardware Layout
I don't have a drawing of the connections between the Arduino, its Motor Shield, the Parallax Ultrasonic Distance Sensor, the RC car's plastic frame and electric motors, nor the 2 sets of battery connections. Schematics will be on their way tomorrow, thanks to Fritzing.
In the meantime, I will at least provide a list of what's connected where, so it's at least recorded somewhere. To begin, I'm using a Jeep Rubicon-branded RC car frame, produced by New Bright. Almost any RC car chassis would work, but this one is large enough that it can't move very fast, even at maximum power. This means usually nothing breaks when it runs into the wall. One unusual feature of this particular RC car is that the steering is controlled by an electric motor instead of an electric servo. This will complicate programming the steering function somewhat. I have stripped the electronics board from the frame where it sat behind the speaker. I left the front (steering) and rear motors, and the speaker which is too well glued to the plastic chassis to remove without serious damage.
On the white plugboard, I have a rather complicated wire setup, which is to simplify setting up 4 ultrasonic distance sensors, for front, rear, left, and right. Each sensor requires power and ground, and the one in this example has a 3rd pin for data. The other sensors I'm using have another pin for data because they are an older, cheaper model I found online. To simplify, the sensor I'm using faces forward on the frame, gets its power from the 5v Arduino pin, has its GND pin connected to the Arduino GND pin, and the DATA pin connected to Arduino pin 7. This is identical to the layout for the Arduino Ping example.
Early in this project, out of ignorance, I uncoupled the Motor Shield's power line from the Arduino power line. I thought this was necessary to protect the Arduino's processor, but it's not, as the ATMega168 can apparently convert 19v down to 5v. But this modification is optional and sometimes recommended, and it means I can disconnect the motor batteries and just work on the software and sensors.
I've connected the rear motor to the Motor Shield screw terminal block A, and the front steering motor to block B. There's a detachable 8-AA battery holder attached to the screw terminal block, with negative to GND and positive to VIN, which provides 12v to the motors. Instead of varying the amount of voltage or amperage going to a motor, The Motor Shield's relays instead give full power for fractions of a second -- this is called Pulse Width Modulation (PWM), and means you can power the motor for just 1/256th of a second, if you wanted.
Now, for the code:
const int motorApwm = 3; // Motor A output pin (hardwired on motor shield)
const int pingPin = 7; // pin used by the PING sensor
const int motorBbrk = 8; // Motor B brake pin (hardwired on motor shield)
const int motorAbrk = 9; // Motor A brake pin (hardwired on motor shield)
const int motorBpwm = 11; // Motor B output pin (hardwired on motor shield)
const int motorAdir = 12; // Motor A direction pin (hardwired on motor shield)
const int motorBdir = 13; // Motor B direction pin (hardwired on motor shield)
const int motorAsns = A0; // Motor A sensor pin (hardwired on motor shield)
const int motorBsns = A1; // Motor B sensor pin (hardwired on motor shield)
int brakeAHigh = LOW; // turn off the parking brake
int pwmAspeed = 0; // set motor speed to 0/255
int dirALowForward = LOW; // Which direction the motor should spin
//LOW = forward, HIGH = reverse
int brakeBHigh = LOW; // turn off the steering lock
int pwmBturn = 0; // set steering motor speed to 0
int dirBLowLeft = LOW; // Which direction the motor should spin
//LOW = left?, HIGH = right?
long lastCm = 14; // Used for speedometer
long carSpeed = 0; // Used for speedometer
void setup() {
//Set up the motors for the correct outputs
pinMode(motorAdir, OUTPUT);
pinMode(motorApwm, OUTPUT);
pinMode(motorAbrk, OUTPUT);
pinMode(motorAsns, INPUT);
pinMode(motorBdir, OUTPUT);
pinMode(motorBpwm, OUTPUT);
pinMode(motorBbrk, OUTPUT);
pinMode(motorBsns, INPUT);
pinMode(buttonPin, INPUT);
Serial.begin(9600); // To see the values on the PC
}
void loop() {
long duration, inches, cm; // These 3 variables are used for the
// Distance sensor's math functions below
digitalWrite(motorAdir, dirALowForward); // Tell Motor A to go forward or reverse
digitalWrite(motorAbrk, brakeAHigh); // Apply or release Motor A's brakes
digitalWrite(motorBdir, dirBLowLeft); // Tell Motor B to turn left or right
digitalWrite(motorBbrk, brakeBHigh); // Apply or release Motor B's steering brakes
// copied from ultrasonic_range
pinMode(pingPin, OUTPUT); // Set the Ping pin to Send
digitalWrite(pingPin, LOW); // Clear the line to the sensor
delayMicroseconds(2); // wait 0.002 seconds
digitalWrite(pingPin, HIGH); // send the ping
delayMicroseconds(5); // wait 0.005 seconds
digitalWrite(pingPin, LOW); // Clear the line to the sensor
// We sent that ping 0.007 seconds ago
pinMode(pingPin, INPUT); // Set the Ping pin to Receive
duration = pulseIn(pingPin, HIGH); // set the variable named Duration
// to be the number of microseconds until
// the ultrasonic ping comes back
inches = microsecondsToInches(duration); // Calls the function that converts microseconds to inches
cm = microsecondsToCentimeters(duration);// Calls the function that converts microseconds to centimeters
carSpeed = lastCm - cm; //Speedometer. Sets carSpeed to be the previous distance minus the current distance to the wall.
lastCm = cm; // Replaces the previous distance to wall with current distance to wall.
// Depending on the distance and car speed, speed up or slow down
if (cm>30){
if (carSpeed<20){
if (pwmAspeed < 128) {
pwmAspeed += 10;
}
dirALowForward = LOW;
brakeAHigh = LOW;
}else if (carSpeed>20){
if (pwmAspeed > 15) {
pwmAspeed -= 10;
}
dirALowForward = LOW;
brakeAHigh = LOW;
}
}else if (cm>1000){
if (carSpeed<20){
if (pwmAspeed < 245) {
pwmAspeed += 20;
}
dirALowForward = LOW;
brakeAHigh = LOW;
}else if (carSpeed>20){
if (pwmAspeed > 5) {
pwmAspeed -= 20;
}
dirALowForward = LOW;
brakeAHigh = LOW;
}
} else { // if we're closer than 30cm
if (carSpeed<20){
if (pwmAspeed < 128) {
pwmAspeed += 5;
}
dirALowForward = HIGH; // reverse
brakeAHigh = LOW;
}else if (carSpeed>20){
if (pwmAspeed < 128) {
pwmAspeed += 5;
}
dirALowForward = HIGH; // reverse
brakeAHigh = HIGH; // brakes on
}
}
if (pwmAspeed > 255) {
pwmAspeed = 255; // Makes sure we never give more than 100%
}
//Debugging output, so we can see these values in real time on the PC
Serial.print(inches);
Serial.print(" in, ");
Serial.print(cm);
Serial.print(" cm, ");
Serial.print(lastCm);
Serial.print(" lastcm, ");
Serial.print(carSpeed);
Serial.print(" car speed, ");
Serial.print(pwmAspeed);
Serial.print(" pwmAspeed, ");
Serial.print(dirALowForward);
Serial.println();
analogWrite(motorApwm, pwmAspeed); // Power the motor at the speed set above
digitalWrite(motorAbrk, brakeAHigh); // Apply the brake, or don't. I have this twice, might remove one.
delay(100); // Wait 0.1s, which is necessary for ultrasonic ping operation. Depending on the amount of code, this should be reduced.
}
// Sound moves 1 inch every 74 microseconds. Divide by 2 to remove the sound's return distance
long microsecondsToInches(long microseconds)
{
return microseconds / 74 / 2;
}
// Sound moves 1 centimeter every 29 microseconds. Divide by 2 to remove the sound's return distance
long microsecondsToCentimeters(long microseconds)
{
return microseconds / 29 / 2;
}
---
If you look closely, you can see where I changed the distance in the video, from 10cm to 30cm. I'm still fine-tuning the acceleration and deceleration curves. I want to start recording the speed and distance in a file on the SD Card shield, but pins 11, 12 & 13 would then be in use for too many devices.
Update 2: Breadboard and Schematic views
Here are the images for which you've all been eagerly awaiting -- my breadboard and schematic images. Fritzing makes this as easy as laying out an office in Visio, or maybe easier. First, the breadboard image.
My Arduino configuration.
In this image, you see a breadboard with an ultrasonic sensor, the Motor Shield atop the Arduino, a 9V battery powering the Arduino, and a 12V 8-AA battery pack powering the Motor Shield, which powers the electric motor. Essentially, I can disconnect and detangle this setup from this RC car frame, and attach it to any other RC car, to make it self-driving.
Schematic drawing of Arduino and Motor shields
with sensor, motor, and batteries
In this drawing, you see more clearly that there are only a few wires to be connected, and most of those are moving power from one component to another. If the Motor Shield VIN were not severed, the Arduino could probably run off the 12V power supply to reduce complexity. But that leads to the possibility of the Arduino processor running out of power if the motor were drawing too much. As I said above, the 2-battery setup has advantages and disadvantages. Also, the 9V battery could be replaced with any USB charger, including a large USB battery pack I also own.
If you have any ideas, suggestions, improvements, or criticisms, please comment.