Jump to content

How to Make Your First Arduino Robot

Programming

Note: A tutorial for the newer version of our Arduino robotics kit is available here: Funduino UNO Robotics Kit Guide

 

All right, now that we've finished building our robot, it's time to make it actually do something! We do this by writing a program (or "sketch", in Arduino terms) and downloading it to the microcontroller. Head on over to the getting started with Arduino tutorial to learn how to get the Arduino IDE and driver installed.

1. Hello World
Once you have the Arduino IDE and driver installed on your computer, we can go ahead and start writing our first sketch, the classic "Hello World" program! Fire up the Arduino IDE and you'll be presented with a blank sketch. Go ahead and copy and paste the code below into the IDE, then click the right arrow button in the upper left hand corner to build and upload the program to your Arduino:
 

void setup()
{
    Serial.begin(9600);
}


void loop()
{
  Serial.println("Hello World!");
}

The status text in the lower left hand corner of the Arduino IDE will say "Done uploading" when the program has finished uploading to the Arduino. Once the program has been uploaded, click on the magnifying glass in the upper right hand corner to open up the Serial Monitor. A window will open up, and you should see the text "Hello World!" being printed over and over again in it. Congratulations, you just wrote your first Arduino sketch!

Let's go over the program above. The first function is setup():

void setup()
{
    Serial.begin(9600);
}

Setup() is a special function that the Arduino runs when it first turns on. This is where we put our initialization code, which in this case was configuring and starting the serial port on the Arduino. The line Serial.begin(9600) tells the Arduino to configure the onboard serial port to operate at 9600 baud and to start it.

The next function is loop():

void loop()
{
  Serial.println("Hello World!");
}

Loop() is a special function that the Arduino keeps calling as long as it is turned on. This function is where the body of Arduino programs reside. The body of this program is really simple and only has one line, which is the command Serial.println(). This command tells the Arduino to send the string of text passed as a parameter over the serial port (in this case, back to our computer).

2. Servos

Now lets write another program. This time, let's use the servo motors on our robot to make it drive forward!

#include <Servo.h>

// create servo objects
Servo leftMotor;
Servo rightMotor;

void setup()
{
    leftMotor.attach(13);
    rightMotor.attach(12);
}


void loop()
{
  leftMotor.write(180);
  rightMotor.write(180);
}

The first line of the program includes an Arduino library. There are many libraries that have been written for Arduino and they allow us to easily do things that would otherwise require us to write a lot of code. In this program, we're including the Servo library, which will allow us to easily use our servo motors.

The next two lines create Servo objects, which we name leftMotor and rightMotor (it's always a good idea to name variables descriptively).

This time, instead of starting the serial port in the setup() function, we use the Servo .attach() method to tell the servo objects what pins the servos are connected to on the Arduino. The .attach() function has an Arduino pin number as a parameter, so since we connected our servos to pins 12 & 13 that's what we pass to the functions as arguments.

Now that the servo objects know what pins our servos are connected to on the Arduino, we can use the Servo .write() function to control the servo's direction and speed. The Servo .write() method takes a number between 0 and 180 as a parameter. The reason for this range is that a "standard servo" (one that is not continuous rotation like ours) usually rotates about 180 degrees, so having the parameter be 0-180 makes sense (i.e. with a standard servo passing 0 would make it turn all the way one direction, passing 90 would make it go to the middle, and passing 180 would make it go all the way the other direction). With a continuous rotation servo, passing the value 0 to the .write() function tells the servo to turn at full speed one direction, 90 makes it stop (also called "neutral"), and 180 makes it turn full speed the other direction. Go ahead and upload the program and see what happens!

Note: You might want to put something under the robot to keep the wheels from touching the ground, because they're going to start turning once the program uploads to the Arduino!

With any luck, when the program finished uploading, the wheels started turning! But wait, look at the directions of the wheels -- they're turning different directions! Why is this happening?

The reason is that one of the servos is "flipped" with respect to the other since it's pointing in the opposite direction, so in order to make the wheels on the servos both turn in the same direction, we need to send opposite values to them! Try changing the parameters in one of the .write() functions to 0, and after uploading you'll find that both wheels turn in the same direction!

3. Ultrasonic sensor

The next thing we're going to do is learn to use the Ultrasonic sensor on the robot to avoid obstacles. Read the tutorial on using an HC-SR04 ultrasonic sensor with Arduino to learn how the sensor works, then take a look at the program below:

const int serialPeriod = 250;       // only print to the serial console every 1/4 second
unsigned long timeSerialDelay = 0;

const int loopPeriod = 20;          // a period of 20ms = a frequency of 50Hz
unsigned long timeLoopDelay   = 0;

// specify the trig & echo pins used for the ultrasonic sensors
const int ultrasonic2TrigPin = 8;
const int ultrasonic2EchoPin = 9;

int ultrasonic2Distance;
int ultrasonic2Duration;


void setup()
{
    Serial.begin(9600);
  
    // ultrasonic sensor pin configurations
    pinMode(ultrasonic2TrigPin, OUTPUT);
    pinMode(ultrasonic2EchoPin, INPUT);
}


void loop()
{
    debugOutput(); // prints debugging messages to the serial console
    
    if(millis() - timeLoopDelay >= loopPeriod)
    {
        readUltrasonicSensors(); // read and store the measured distances
        
        timeLoopDelay = millis();
    }
}


void readUltrasonicSensors()
{
    // ultrasonic 2
    digitalWrite(ultrasonic2TrigPin, HIGH);
    delayMicroseconds(10);                  // must keep the trig pin high for at least 10us
    digitalWrite(ultrasonic2TrigPin, LOW);
    
    ultrasonic2Duration = pulseIn(ultrasonic2EchoPin, HIGH);
    ultrasonic2Distance = (ultrasonic2Duration/2)/29;
}


void debugOutput()
{
    if((millis() - timeSerialDelay) > serialPeriod)
    {
        Serial.print("ultrasonic2Distance: ");
        Serial.print(ultrasonic2Distance);
        Serial.print("cm: ");
        Serial.println();
        
        timeSerialDelay = millis();
    }
}

This program is from the using an HC-SR04 ultrasonic sensor with Arduino tutorial. It's more complicated than our previous ones, but it's explained in that tutorial (so read it if you haven't already). Go ahead and upload the program to your Arduino.

After uploading the program to your Arduino, open up the Serial Monitor again (click the magnifying glass in the upper right-hand corner), and you should see something similar to the following:

gallery_1_23_18351.png

The distance being printed out will depend on how far your robot is away from an object. Try putting your hand in front of the ultrasonic sensor on your robot and moving it back and fourth and the distance being printed out will change accordingly. Pretty cool, huh?

4. Navigating with the ultrasonic sensor

Now that we know how to use the servos to make the robot drive around, and how to use the ultrasonic sensor to measure obstacles in front of us, let's combine this knowledge to write a program that has the robot drive around and avoid obstacles!

Using the ultrasonic code above as a starting point (since it's more complicated than the servo program), let's add the servo code to it.

The first step is include the servo library by adding the following to the top of the file:

#include <Servo.h>

Next, add the lines to create the servo objects:

// create servo objects
Servo leftMotor;
Servo rightMotor;

Then configure the servo objects in the setup() function:

leftMotor.attach(13);
    rightMotor.attach(12);

At this point, you should have the following:

#include <Servo.h>

// create servo objects
Servo leftMotor;
Servo rightMotor;

const int serialPeriod = 250;       // only print to the serial console every 1/4 second
unsigned long timeSerialDelay = 0;

const int loopPeriod = 20;          // a period of 20ms = a frequency of 50Hz
unsigned long timeLoopDelay   = 0;

// specify the trig & echo pins used for the ultrasonic sensors
const int ultrasonic2TrigPin = 8;
const int ultrasonic2EchoPin = 9;

int ultrasonic2Distance;
int ultrasonic2Duration;

void setup()
{
    Serial.begin(9600);
  
    // ultrasonic sensor pin configurations
    pinMode(ultrasonic2TrigPin, OUTPUT);
    pinMode(ultrasonic2EchoPin, INPUT);
    
    leftMotor.attach(13);
    rightMotor.attach(12);
}


void loop()
{
    debugOutput(); // prints debugging messages to the serial console
    
    if(millis() - timeLoopDelay >= loopPeriod)
    {
        readUltrasonicSensors(); // read and store the measured distances
        
        timeLoopDelay = millis();
    }
}


void readUltrasonicSensors()
{
    // ultrasonic 2
    digitalWrite(ultrasonic2TrigPin, HIGH);
    delayMicroseconds(10);                  // must keep the trig pin high for at least 10us
    digitalWrite(ultrasonic2TrigPin, LOW);
    
    ultrasonic2Duration = pulseIn(ultrasonic2EchoPin, HIGH);
    ultrasonic2Distance = (ultrasonic2Duration/2)/29;
}


void debugOutput()
{
    if((millis() - timeSerialDelay) > serialPeriod)
    {
        Serial.print("ultrasonic2Distance: ");
        Serial.print(ultrasonic2Distance);
        Serial.print("cm: ");
        Serial.println();
        
        timeSerialDelay = millis();
    }
}

Now we need to add the code to make the robot drive around. Before we jump in to writing the code, let's think about what we want to happen:

  • If there's nothing in front of the robot, drive forward
  • If there is an obstacle in front of the robot, turn left

We will use a finite state machine to accomplish this.

Start by adding a new function to the file, named stateMachine(), then add a call to this function inside of loop() so that it gets called every loop. Now, add the following towards the top of the file where the other variables are (above the setup() function):

// define the states
#define DRIVE_FORWARD     0
#define TURN_LEFT         1

int state = DRIVE_FORWARD; // 0 = drive forward (DEFAULT), 1 = turn left

We are going to use the state variable to keep track of whether the robot should be driving forward or turning left (you can add more states, like turning right, later if you want to). Now we can begin working on the stateMachine() function itself.

To implement the state machine, we're going to use if-else statements, like this:

if(state == DRIVE_FORWARD) // no obstacles detected
    {
        
    }
    else if(state == TURN_LEFT) // obstacle detected -- turning left
    {
        
    }

We set the state variable to DRIVE_FORWARD by default, so when the robot turns on, the program will branch into the first if statement. Inside the DRIVE_FORWARD state, we need to check whether there is an obstacle in front of us and if there isn't, we want to drive forward. If there is an obstacle in front of us, we want to change the state to TURN_LEFT. So let's go ahead and add the code to do that:

if(ultrasonic2Distance > 6 || ultrasonic2Distance < 0) // if there's nothing in front of us (note: ultrasonicDistance will be negative for some ultrasonics if there's nothing in range)
        {
            // drive forward
            rightMotor.write(180);
            leftMotor.write(0);
        }
        else // there's an object in front of us
        {
            state = TURN_LEFT;
        }

Next, we need to write the code for the TURN_LEFT state. In this state, we want to start the robot turning left and keep it turning left until it has turned 90 degrees. Here's the code for the TURN_LEFT state:

unsigned long timeToTurnLeft = 1100; // it takes around 1.1 seconds to turn 90 degrees
        
        unsigned long turnStartTime = millis(); // save the time that we started turning

        while((millis()-turnStartTime) < timeToTurnLeft) // stay in this loop until timeToTurnLeft (1.1 seconds) has elapsed
        {
            // turn left
            rightMotor.write(180);
            leftMotor.write(180);
        }
        
        state = DRIVE_FORWARD;

This may be a little confusing, so let's break it down line by line:

  • timeToTurnLeft: we put the amount of time it takes our robot to turn 90 degrees in here (in milliseconds)
  • turnStartTime: we store the amount of time that the Arduino has been powered on in here
  • millis()-turnStartTime: this gives us the amount of time that the robot has been turning left
  • while((millis()-turnStartTime) < timeToTurnLeft): the program will stay in the while loop until the robot has been turning for timeToTurnLeft milliseconds

Inside the while loop, we make the robot turn. The while loop keeps looping until the proper time has elapsed, then the program exists the while loop and the state is set back to DRIVE_FORWARD.

Note: Using a loop in this manner is quick and easy, but it's not a good practice because it stops other things from happening in the program. A loop like this is called "blocking" because the program gets "stuck" in it for a certain amount of time. We will show a non-blocking example later.

OK, so at this point you should have something similar to the following:

#include <Servo.h>

// create servo objects
Servo leftMotor;
Servo rightMotor;

const int serialPeriod = 250;       // only print to the serial console every 1/4 second
unsigned long timeSerialDelay = 0;

const int loopPeriod = 20;          // a period of 20ms = a frequency of 50Hz
unsigned long timeLoopDelay   = 0;

// specify the trig & echo pins used for the ultrasonic sensors
const int ultrasonic2TrigPin = 8;
const int ultrasonic2EchoPin = 9;

int ultrasonic2Distance;
int ultrasonic2Duration;

// define the states
#define DRIVE_FORWARD     0
#define TURN_LEFT         1

int state = DRIVE_FORWARD; // 0 = drive forward (DEFAULT), 1 = turn left

void setup()
{
    Serial.begin(9600);
  
    // ultrasonic sensor pin configurations
    pinMode(ultrasonic2TrigPin, OUTPUT);
    pinMode(ultrasonic2EchoPin, INPUT);
    
    leftMotor.attach(13);
    rightMotor.attach(12);
}


void loop()
{
    debugOutput(); // prints debugging messages to the serial console
    
    if(millis() - timeLoopDelay >= loopPeriod)
    {
        readUltrasonicSensors(); // read and store the measured distances
        
        stateMachine();
        
        timeLoopDelay = millis();
    }
}


void stateMachine()
{
    if(state == DRIVE_FORWARD) // no obstacles detected
    {
        if(ultrasonic2Distance > 6 || ultrasonic2Distance < 0) // if there's nothing in front of us (note: ultrasonicDistance will be negative for some ultrasonics if there's nothing in range)
        {
            // drive forward
            rightMotor.write(180);
            leftMotor.write(0);
        }
        else // there's an object in front of us
        {
            state = TURN_LEFT;
        }
    }
    else if(state == TURN_LEFT) // obstacle detected -- turn left
    {
        unsigned long timeToTurnLeft = 1100; // it takes around 1.1 seconds to turn 90 degrees
        
        unsigned long turnStartTime = millis(); // save the time that we started turning

        while((millis()-turnStartTime) < timeToTurnLeft) // stay in this loop until timeToTurnLeft (1.1 seconds) has elapsed
        {
            // turn left
            rightMotor.write(180);
            leftMotor.write(180);
        }
        
        state = DRIVE_FORWARD;
    }
}


void readUltrasonicSensors()
{
    // ultrasonic 2
    digitalWrite(ultrasonic2TrigPin, HIGH);
    delayMicroseconds(10);                  // must keep the trig pin high for at least 10us
    digitalWrite(ultrasonic2TrigPin, LOW);
    
    ultrasonic2Duration = pulseIn(ultrasonic2EchoPin, HIGH);
    ultrasonic2Distance = (ultrasonic2Duration/2)/29;
}


void debugOutput()
{
    if((millis() - timeSerialDelay) > serialPeriod)
    {
        Serial.print("ultrasonic2Distance: ");
        Serial.print(ultrasonic2Distance);
        Serial.print("cm");
        Serial.println();
        
        timeSerialDelay = millis();
    }
}

Go ahead and upload the program to the Arduino and see what happens!



Success! It drives forward until an obstacle is detected, then turns left! The robot is driving so slowly because the plastic wheels are slipping on my tile floor. Rubber wheels would be a nice upgrade for this 'bot!

And there you have it, a fully functional robot!


For comments or questions, head over to the forum!
Contributors