Motor Encoders with Arduino

If Colin is going to create a map of the space he’s in, he needs to be able to find the distance between objects in the space. If he’s going to accurately control his own motion, he needs to be able to control the speed of his motors. For both of these things he needs information about how far and how fast his motors are turning. To give Colin that information I added encoders to his motors. Encoders are special sensors that track both how far his motor shafts have turned, and in what direction.

The following tutorial will discuss how to use shaft encoders with DC motors. First I’ll discuss the principles the encoders work on, then I’ll show you how to wire up an encoder and verify that it’s working. This tutorial owes a large dept to the Penn State Robotics Club Wiki, which has an excellent page on wheel encoders.

For this tutorial I’ll be using these magnetic encoders from Pololu. They are great for use with an Arduino because their output is a nice, easy to read, digital square wave. Pololu also offers an analog optical encoder, but to reliably read its output you need to use a ADC (analog to digital converter). Also, the optical encoder is susceptible to interference from bright light. So magnetic encoders are easier to use and more reliable for our purposes.


Encoder Principles

Pololu’s shaft encoders add a 6 pole magnetic disc to the shaft of the motor, as well as two Hall effect sensors. As the motor turns the disc rotates past the sensors. Each time a magnetic pole passes a sensor, the encoder outputs a digital pulse, also called a “tick.” The encoder setup is pictured below.

motor encoder installation

motor encoder installation example (from pololu.com)

The encoder has two outputs, one for each Hall effect sensor. The sensors are separated by 90 degrees. This means the square wave outputs of the sensors are 90 degrees out of phase. This is called a quadrature output. The picture below (taken from Pololu’s website) shows the typical output of an encoder.

encoder output

Output of an encoder (from pololu.com)

Why is it important that the output pulses are 90 degrees out of phase? This allows us to determine both the magnitude and the direction of the motor’s rotation. If output A is ahead of output B, then the motor is turning forward. If output A is behind B, the motor is turning backward. Pretty cool, right?


Encoder Setup and Wiring

Wiring up the encoders is pretty simple. See the diagram below for details on wiring up the encoder for motor B, and repeat for motor A.

wiring for a single motor and encoder

wiring for a single motor and encoder

Note that the encoder pin OUTA needs to be connected to a hardware interrupt pin (digital pin 2 or 3 on an Arduino Duemilanove or Uno). For this example it’s not really necessary to use hardware interrupts but if you don’t your Arduino won’t be able to do anything but track the encoder. Interrupts allow your Arduino to keep track of the encoders while it’s running another program.

It’s also important when installing your motors to not mount them too close to each other. If your motors are mounted back-to-back, the magnetic encoder wheels will interfere with each other. I initially had only about 5mm between the two encoder wheels. The magnets in the encoder wheels linked with each other and made it impossible for the two motors to turn at different speeds. Keeping the encoder wheels at least 20mm apart seems to avoid any interference problems.


Hardware Interrupts

It’s pretty easy to write a program that simply counts the number of ticks. But if, for some reason, you want your program to do anything but track the encoders you need to use hardware interrupts.

With hardware interrupts your main program runs until the state of one of the Arduino’s interrupt pins changes. Then the main program is stopped, a special interrupt method is run, then the main program is resumed. For example, an obstacle avoidance routine can run in the foreground but if one of the interrupt pins changes from low to high the position or speed of the motor can be updated in the interrupt method.

Because the main program is stopped when the interrupt method is triggered, the length of the interrupt method should be kept as short as possible. If the interrupt method is too long, then the Arduino will spend all of its time running the interrupt method and it will never actually run the main program.

For more details on hardware interrupts check out this page on the Arduino playground.


Example Sketch

The sketch below simply prints the values of the two count variables. When the motors are moved, the encoder output triggers the appropriate encoder event method. The encoder event methods either increment or decrement the count variables, keeping track of the number of ticks from each encoder. If the motor is turned forward, the count is incremented and it is decremented if the motor is turned backward.

The motors need to be moved manually for this sketch. They won’t move themselves. We will expand this to do simple motor speed control in a later tutorial.

We’ll define motor direction somewhat arbitrarily: If RH_ENCODER_A is HIGH and RH_ENCODER_B is LOW then the motor is turning forward. If RH_ENCODER_A is HIGH and RH_ENCODER_B is also HIGH, then the motor is turning backward. You can swap the wires for ENCODER_A and ENCODER_B to change the direction if required.

/*
 * Encoder example sketch
 * by Andrew Kramer
 * 1/1/2016
 *
 * Records encoder ticks for each wheel
 * and prints the number of ticks for
 * each encoder every 500ms
 *
 */

// pins for the encoder inputs
#define RH_ENCODER_A 3 
#define RH_ENCODER_B 5
#define LH_ENCODER_A 2
#define LH_ENCODER_B 4

// variables to store the number of encoder pulses
// for each motor
volatile unsigned long leftCount = 0;
volatile unsigned long rightCount = 0;

void setup() {
  pinMode(LH_ENCODER_A, INPUT);
  pinMode(LH_ENCODER_B, INPUT);
  pinMode(RH_ENCODER_A, INPUT);
  pinMode(RH_ENCODER_B, INPUT);
  
  // initialize hardware interrupts
  attachInterrupt(0, leftEncoderEvent, CHANGE);
  attachInterrupt(1, rightEncoderEvent, CHANGE);
  
  Serial.begin(9600);
}

void loop() {
  Serial.print("Right Count: ");
  Serial.println(rightCount);
  Serial.print("Left Count: ");
  Serial.println(leftCount);
  Serial.println();
  delay(500);
}

// encoder event for the interrupt call
void leftEncoderEvent() {
  if (digitalRead(LH_ENCODER_A) == HIGH) {
    if (digitalRead(LH_ENCODER_B) == LOW) {
      leftCount++;
    } else {
      leftCount--;
    }
  } else {
    if (digitalRead(LH_ENCODER_B) == LOW) {
      leftCount--;
    } else {
      leftCount++;
    }
  }
}

// encoder event for the interrupt call
void rightEncoderEvent() {
  if (digitalRead(RH_ENCODER_A) == HIGH) {
    if (digitalRead(RH_ENCODER_B) == LOW) {
      rightCount++;
    } else {
      rightCount--;
    }
  } else {
    if (digitalRead(RH_ENCODER_B) == LOW) {
      rightCount--;
    } else {
      rightCount++;
    }
  }
}

That’s all for today. In my next post I’ll take what we learned about using encoders with hardware interrupts and incorporate that into a simple speed control routine. After that we can dive into real PID control!

Bitwise Obstacle Avoidance Tweak

Around the time I was rewriting the example sketch for the obstacle avoidance tutorial I stumbled across this great tutorial on bit math on the Arduino Playground. While reading it I found a good, simple use for bit math that would make my obstacle avoidance sketch a bit more efficient.

There is a popular quote, usually attributed to Don Knuth: “premature optimization is the root of all evil.” Premature optimization occurs when we make improvements to efficiency that complicate the program, reduce readability, or cause us to ignore larger, more structural improvement possibilities.

The following is definitely a premature optimization of the obstacle avoidance example sketch. However, I think it’s okay in this case because it allows us to practice a fun new technique: bit math!


Background

First thing’s first: if you haven’t read the Arduino Playground tutorial on bit math, read it now. It’s good, it’s easy to read, and it provides much more background than I’m going to.

In my obstacle avoidance example I used a multi dimensional array of bools to store the sensor states for each possible reaction, the reaction matrix. Each bool value occupies one byte, so the size of the reaction matrix in bytes is the number of sensors times the number of reactions. In our case it’s 24 bytes.

Each byte is made of 8 bits, each of which have two states. A bool value only has two states, so in theory a bool could be represented by a single bit. Further, a byte could be interpreted as an array of 8 bools. Cool, right? This means, if the number of sensors is 8 or less, the size of our reaction matrix in bytes is just the number of reactions. The size of our reaction matrix is only 8 bytes now!

Additionally, our compareCases()  can be totally eliminated by this. It formerly involved two nested for loops to compare the values in the sensor array with the reaction matrix. The number of comparisons was the equal to the number of sensors times the number of reactions. Now all we have to do is compare the thisCase  byte directly to each possible case in the switch statement. Total number of comparisons is equal to the number of cases. We just cut the number of comparisons for each sensor update down from 24 to 8! This happens ten times per second, so it’s actually kind of significant.


Example Sketch

/*
 * Obstacle avoidance bit math example
 * by Andrew Kramer
 * 8/3/2015
 * 
 * Uses 3 ultrasonic sensors to determine 
 * if nearest obstacle is too close.
 * Outlines 8 possible cases for 
 * positions of obstacles.
 */

#include <NewPing.h>

#define RH_PWM 6 // PWM pin for right hand motor
#define RH_DIR1 9 // direction control for right hand motor
                  // BIN1 pin on motor controller
#define RH_DIR2 8 // direction control for right hand motor
                    // BIN2 pin on motor controller

#define LH_PWM 11 // PWM pin for left hand motor
#define LH_DIR1 13 // direction control for right hand motor
                     // AIN1 pin on motor controller
#define LH_DIR2 12 // direction control for left hand motor
                     // AIN2 pin on motor controller
                     
#define DEFAULT_SPEED 30 // default PWM level for the motors
#define TURN_DISTANCE 20 // distance at which the bot will turn
#define MAX_DISTANCE 200 // max range of sonar sensors
#define NUM_SONAR 3 // number of sonar sensors
#define NUM_CASES 8 // number of reaction cases

NewPing sonar[NUM_SONAR] = { // array of sonar sensor objects
  NewPing(A2, A2, MAX_DISTANCE), // right
  NewPing(A1, A1, MAX_DISTANCE), // front
  NewPing(A0, A0, MAX_DISTANCE) // left
};

/* list of cases
    B0000000  0: no obstacles
    B0000001  1: obstacle to the right
    B0000010  2: obstacle in front
    B0000011  3: obstacles front and right
    B0000100  4: obstacle to the left
    B0000101  5: obstacles left and right
    B0000110  6: obstacles front and left
    B0000111  7: obstacles front, left, and right
*/

byte thisCase = 0;

void setup() {
  for (int pin = 6; pin <= 13; pin++) {
    pinMode(pin, OUTPUT); // set pins 3 through 9 to OUTPUT
  }
  Serial.begin(9600);
}

void loop() {
  updateSensor();
  switch (thisCase) {
    // no obstacles
    case B00000000:
      straightForward();
      break;
    // obstacle to the right
    case B00000001:
      turnLeft(30);
      break;
    // obstacle in front
    case B00000010:
      turnLeft(90);
      break;
    // obstacles front and right
    case B00000011:
      turnLeft(90);
      break;
    // obstacle to the left
    case B00000100:
      turnRight(30);
      break;
    // obstacles left and right
    case B00000101:
      turnLeft(180);
      break;
    // obstacles front and left
    case B00000110:
      turnRight(90);
      break;
    // obstacles front, left, and right
    case B00000111:
      turnLeft(180);
      break;
  }
  delay(100); 
}

void updateSensor() {
    thisCase = 0;
    for (int i = 0; i < NUM_SONAR; i++) {
        int distance = sonar[i].ping_cm();
        if (distance == 0) 
            distance = MAX_DISTANCE;
        if (distance < TURN_DISTANCE)
            thisCase |= (1 << i);
    }
}

void setLeftForward() {
  digitalWrite(LH_DIR1, HIGH);
  digitalWrite(LH_DIR2, LOW);
}

void setRightForward() {
  digitalWrite(RH_DIR1, HIGH);
  digitalWrite(RH_DIR2, LOW);
}

void setBothForward() {
  setLeftForward();
  setRightForward();
}

void setLeftBackward() {
  digitalWrite(LH_DIR1, LOW);
  digitalWrite(LH_DIR2, HIGH);
}

void setRightBackward() {
  digitalWrite(RH_DIR1, LOW);
  digitalWrite(RH_DIR2, HIGH);
}

void setBothBackward() {
  setRightBackward();
  setLeftBackward();
}

void setLeftSpeed(int speed) {
  analogWrite(LH_PWM, speed);
}

void setRightSpeed(int speed) {
  analogWrite(RH_PWM, speed);
}

void setBothSpeeds(int speed) {
  setLeftSpeed(speed);
  setRightSpeed(speed);
}

// sets direction of both motors to forward and sets both speeds
// to default speed
void straightForward() {
  setBothForward();
  setBothSpeeds(DEFAULT_SPEED);
}

// makes a turn by stopping one motor
// accepts an int, 0 or 1 for left or right turn respectively
void turnLeft(int deg) {
  setBothSpeeds(0);
  delay(100);
  setLeftBackward(); // set left motor to run backward
  setBothSpeeds(DEFAULT_SPEED); // set speeds to 1/2 default
  delay(10 * deg); // allow time for the bot to turn
                   // turn time is approx 5ms per degree
  straightForward(); // resume driving forward at default speed
}

void turnRight(int deg) {
  setBothSpeeds(0);
  delay(100);
  setRightBackward(); // set right motor to run backward
  setBothSpeeds(DEFAULT_SPEED); // set speeds to 1/2 default
  delay(10 * deg); // allow time for the bot to turn
                      // turn time is approx 5ms per degree
  straightForward(); // resume driving forward at default speed
}

Sketch Notes

First, the reaction matrix is stored in an array of bytes called cases. The bits in each byte represent a sensor. The right sensor is represented by the least significant bit, the front is the second, and the left is the third least significant bit. There is room in each byte for five more sensors, but we’re only using three.

I think defining the reaction matrix is pretty self explanatory. The only slightly tough thing is building the thisCase byte based on the sensor inputs. We start with a byte consisting of all zeroes: 0b00000000. Then, if the reading from sensor i is less than the reaction distance, we want to change bit i to 1 (we want to flip bit i). We start with 0b00000001 and shift it to the left by i places using the bitwise shift operator: <<. This creates a byte containing a 1 followed by i zeroes. For instance:

int a = 1 << 2; // a is equal to 0b00000100 (or 4)

Then we use the bitwise OR operator to flip the appropriate bit in the thisCase byte. thisCase |= (1 << i);  flips bit i in the thisCase byte.

Once the thisCase byte is built it only needs to be compared to every element in the reaction matrix.


Coming Soon

A post on my PID motor control library!

It makes using DC motors much  easier. It requires motor encoders to provide feedback to the speed and position control routines, however. So I’ll probably do posts on using encoders and PID control as well.

 

Obstacle Avoidance

 

At long last I’ve gotten around to doing a post on obstacle avoidance! Thanks to everyone for your patience.

When I started writing this post it had been months since I had last thought about obstacle avoidance. I opened my obstacle avoidance sketch for Colin intending to use it for this post unchanged but it looked terrible. It was a kludgy mess of nested if/else statements. So I took a few hours to totally rewrite my code and make it more efficient and readable. Revisiting old programs can be a great opportunity to reevaluate previous work.

Anyway, the basic idea behind obstacle avoidance is pretty simple. Colin, my mobile robot, can sense objects around himself. If he gets too close to an object he turns away and goes off in another direction. Easy, right?

Wiring Diagram
Program Design
Example Sketch
Video


Preliminaries

We’ll be using HC-SR04 ultrasonic sensors for this tutorial. If you’ve never used ultrasonic sensors before you should take a look at my tutorial. This tutorial also uses DC motors. If you’ve never used DC motors with an Arduino before, take a look at my motor control tutorial.

There is one problem to address right away: the configuration of our sensors. HC-SR04 sensors have a roughly 30° field of view, so with just one sensor Colin won’t be able to see anything to his sides.

Others have solved this problem by mounting a sensor on a servo so the sensor rotates and sweeps out a larger field of view. This instructable is a good example of the technique.

For my purposes it was easier to use an array of three sensors, however. With sensors facing forward, left and right Colin gets a pretty good picture of what’s around him. He still has blind spots at +45° and -45° though, so I’m planning on adding two more sensors.

top


Wiring Diagram

Wiring up the sensors and motors is pretty simple. We really just have to combine the wiring diagrams from the motor control tutorial and the ultrasonic sensor tutorial. Wire the components per the diagram below and you’ll be in good shape.

Wiring diagram for obstacle avoidanceIt’s come to my attention that, on some displays, the color of the TRIG and ECHO wires for the left sonar sensors is very similar to the color of the +5V wire. These wires SHOULD NOT connect, however.

top


Program Design

Before we get into actually writing the obstacle avoidance program we have to decide: how should Colin react to sensor inputs?

The simplest thing we could do is have Colin turn 90º to the left whenever one of his sensors sees an obstacle in front of him. But what if there is also an obstacle to his left? He would turn directly into the obstacle on his left while trying to avoid the one in front of him. What if there is an obstacle on Colin’s left or right but no obstacle in front? Clearly there are several possibilities here.

We need to identify the set of situations Colin might encounter that require him to react. Then we need to identify what those situations look like to Colin in terms of sensor inputs. Lastly, we need to specify an action for Colin to take in each situation.

Let’s assume Colin only needs to take action when an obstacle is within a certain distance. We can call this distance, dr for reaction distance. When the distance from one or more of Colin’s sensors to the nearest obstacle is less than dr Colin needs to take action to avoid the obstacle(s). The table below breaks down the situations and sensor inputs that require Colin to react and the action Colin could take.

Reaction Matrix

Obstacle Locations Left Distance Front Distance Right Distance Response
No Obstacles > dr > dr > dr Drive forward
Front > dr < dr > dr Turn left 90°
Front and right > dr < dr < dr Turn left 90°
Front and left < dr < dr > dr Turn right 90°
Front, left and right < dr < dr < dr Turn left 180°
Left and right < dr > dr < dr Turn left 180°
Right > dr > dr < dr Turn left 45°
Left < dr > dr > dr Turn right 45°

Notice that our reaction matrix requires Colin to turn by a number of degrees for most of his reactions. But Colin has no way of knowing how far his wheels have turned, which would be required for him to know how many degrees he’s turned. The best we can do for now is set Colin’s motors to run at different speeds for a certain time interval. Through trial and error we can find the speed differential and time interval required to get a specific amount of turning. This is an approximate solution but we can’t do any better until we add encoders to Colin’s motors.

top


Example Sketch

/*
 * Obstacle avoidance example
 * by Andrew Kramer
 * 8/3/2015
 *
 * Uses 3 ultrasonic sensors to determine
 * if nearest obstacle is too close.
 * Outlines 8 possible cases for
 * positions of obstacles.
 */

#include <NewPing.h>

#define RH_PWM 3 // PWM pin for right hand motor
#define RH_DIR1 4 // direction control for right hand motor
                  // BIN1 pin on motor controller
#define RH_DIR2 5 // direction control for right hand motor
                    // BIN2 pin on motor controller
#define LH_PWM 9 // PWM pin for left hand motor
#define LH_DIR1 7 // direction control for right hand motor
                     // AIN1 pin on motor controller
#define LH_DIR2 8 // direction control for left hand motor
                     // AIN2 pin on motor controller
#define DEFAULT_SPEED 25 // default PWM level for the motors
#define TURN_DIST 25 // distance at which the bot will turn
#define MAX_DISTANCE 200 // max range of sonar sensors
#define NUM_SONAR 3 // number of sonar sensors
#define NUM_CASES 8 // number of reaction cases

#define MS_PER_DEGREE 10 // milliseconds per degree of turning

NewPing sonar[NUM_SONAR] = { // array of sonar sensor objects
  NewPing(13, 13, MAX_DISTANCE), // left
  NewPing(12, 12, MAX_DISTANCE), // front
  NewPing(11, 11, MAX_DISTANCE) // right
};

/*  
 *  stores a bool for each sensor (left, front, and right respectively
 *  true if nearest obstacle is within TURN_DIST
 *  true if not
 */
bool sensor[3] = {false, false, false}; 

// stores all possible sensor states
bool reactions[NUM_CASES][NUM_SONAR] = { 
   {false, false, false}, // 0: no obstacles
   {false, true, false},  // 1: obstacle in front
   {false, true, true},   // 2: obstacles front and right
   {true, true, false},   // 3: obstacles front and left
   {true, true, true},    // 4: obstacles front, left, and right
   {true, false, true},   // 5: obstacles left and right
   {false, false, true},  // 6: obstacle to the right
   {true, false, false} }; // 7: obstacle to the left

void setup() {
  for (int pin = 3; pin <= 9; pin++) {
    pinMode(pin, OUTPUT); // set pins 3 through 9 to OUTPUT
  }
  Serial.begin(9600);
}

void loop() {
  updateSensor();
  switch (compareCases()) {    
    case 0: // no obstacles
      straightForward();
      break;
    case 1: // obstacle in front
      turnLeft(90);
      break;
    case 2: // obstacles front and right
      turnLeft(90);
      break;
    case 3: // obstacles front and left
      turnRight(90);
      break;
    case 4: // obstacles front, left, and right
      turnLeft(180);
      break;
    case 5: // obstacles left and right
      turnLeft(180);
      break;
    case 6: // obstacle to the right
      turnLeft(30);
      break;
    case 7: // obstacle to the left
      turnRight(30);
      break;
  }
  delay(100);
}

void updateSensor() {
  for (int i = 0; i < NUM_SONAR; i++) {
    int dist = sonar[i].ping_cm();
    // if sonar returns 0 nearest obstacle is out of range
    if (dist == 0) sensor[i] = false;
    else sensor[i] = dist < TURN_DIST;
  }
}

int compareCases() {
  for (int i = 0; i < NUM_CASES; i++) {
    bool flag = true;
    for (int j = 0; j < NUM_SONAR; j++) {
      if (reactions[i][j] != sensor[j]) flag = false;
    }
    if (flag) return i;
  }
}

void setLeftForward() {
  digitalWrite(LH_DIR1, HIGH);
  digitalWrite(LH_DIR2, LOW);
}

void setRightForward() {
  digitalWrite(RH_DIR1, HIGH);
  digitalWrite(RH_DIR2, LOW);
}

void setBothForward() {
  setLeftForward();
  setRightForward();
}

void setLeftBackward() {
  digitalWrite(LH_DIR1, LOW);
  digitalWrite(LH_DIR2, HIGH);
}



void setRightBackward() {
  digitalWrite(RH_DIR1, LOW);
  digitalWrite(RH_DIR2, HIGH);
}

void setBothBackward() {
  setRightBackward();
  setLeftBackward();
}

void setLeftSpeed(int speed) {
  analogWrite(LH_PWM, speed);
}

void setRightSpeed(int speed) {
  analogWrite(RH_PWM, speed);
}

void setBothSpeeds(int speed) {
  setLeftSpeed(speed);
  setRightSpeed(speed);
}

// sets direction of both motors to forward and sets both speeds
// to default speed
void straightForward() {
  setBothForward();
  setBothSpeeds(DEFAULT_SPEED);
}

void turnLeft(int deg) {
  setBothSpeeds(0);
  delay(100); // delay to allow motors to stop before direction change
  setLeftBackward();
  setBothSpeeds(DEFAULT_SPEED);
  delay(MS_PER_DEGREE * deg); // allow time for the bot to turn
  straightForward(); // resume driving forward at default speed
}

void turnRight(int deg) {
  setBothSpeeds(0);
  delay(100); // delay to allow motors to stop before direction change 
  setRightBackward(); 
  setBothSpeeds(DEFAULT_SPEED); 
  delay(MS_PER_DEGREE * deg); // allow time for the bot to turn
  straightForward(); // resume driving forward at default speed
}

You probably won’t be able to load up the code on your differential drive robot and run it, even if you have it wired properly. Depending on how you have the motors wired, one or both of them might run backward. To fix this you should just swap the values in the DIR1 and DIR2 fields for the problematic motor. Also, you may have to adjust the value of MS_PER_DEGREE to get accurate turning.

You’ll notice most of the code in the above sketch is devoted to simply controlling the two motors: setting motor directions, PWM levels, etc. In fact, very little of the above sketch codes for higher level behaviors like deciding when and how to react to obstacles. This makes the sketch more difficult to read and it will only get worse when we add encoders to the mix.

To fix this I’ve developed a motor control library. Encapsulating the motor control code in separate motor objects allows us to focus on programming high-level behaviors without worrying too much about how we’re controlling the motors. I’ll present my motor control library (and make it available for download) in my next post.

top


Video

Below you can see a video of Colin running the above example sketch.

You’ll notice that Colin always stops before making a turn. This slows him down and makes his behavior appear jerky. New methods could be added to the sketch to allow Colin to turn while still moving forward in some situations. These methods would simply slow down the wheel on the inside of the turn rather than reversing it. This could be useful when Colin approaches an obstacle obliquely. In this case only a minor course correction is required so a small, smooth turn is better than stopping, rotating, and starting forward again.

That’s all for today. Check back in a couple of weeks for posts on my motor control library and a refinement to obstacle avoidance that uses bit math!

top

STP 2015

To those of you hoping for a technical post I’ll say sorry in advance. This post is about my experience riding the STP (Seattle To Portland). Check back in a couple of weeks for a tutorial on obstacle avoidance.


My Motivation

When I decide I like doing something I tend to throw myself into it without reservation. Often I pursue my interests to extreme ends.

Four years ago I started doing a little distance running and found it to be pretty fun. In the next two years I ran two marathons, two half marathons, and an assortment of 5ks and 10ks. I’m still working on running a marathon in under four hours.

Three years ago I bought a road bike and started using it to get around the city. I quickly fell in love with biking. It allowed me to go much faster than running for a fraction of the effort, and it was often faster than driving or public transit. There was only one problem: I wasn’t very good at it. I couldn’t ride very far and I tired out quickly on steep hills. I obviously needed to become a better biker.

So I set a ridiculous, nigh impossible goal for myself. Having never ridden more than 20 miles in a day before, I signed up for a 206 mile ride from Seattle to Portland. I thought of it as ambitious, but the word “foolish” came up more than a few times in conversations with friends and family.


My Training

My training was pretty simple. Starting in March I used my bike to commute at least once per week. Since a trip to work and back is about 38 miles for me, this alone would get me over 720 miles by the day of the STP, July 11th.

Biking to work wouldn’t be enough however. The really difficult part of riding 200+ miles isn’t the effort required to keep pedaling for 12 hours, it’s simply sitting on a bike for that length of time. You need to get your butt used to sitting on a saddle for extended periods and you can only do that by going on long rides.

I had a friend, Kelly, who was also doing the STP and she invited me to join her team. Kelly told me in no uncertain terms that I should do at least one 100+ mile ride before attempting the STP. Because Kelly has done the STP a few times now, I trusted her advice. I ultimately did not follow it, however.

I attempted a 100 mile ride two weeks before the STP on a day when the high was over 100º F. I developed a serious case of heat exhaustion around mile 70 and was forced to call it quits. Exhausted and demoralized, I decided to attempt a 206 mile ride having only ever ridden a third of that before in my life.

On the night of the ride my teammates and I all crashed in my apartment, which was close to the start line. I went to sleep full of trepidation. Between that and my teammate’s snoring not even Tylenol PM could ensure me a good night’s sleep.


The Ride

We woke up at 4:00am and immediately there was a bad omen. One of my teammates, Stacy, blew out her tire while trying to fill it. The ride hadn’t even started and we were hitting problems. We managed to make it to the start line by 5:30, much later than we intended.

My team and I (Left to right: Stacy, Drew, Me, and Kelly)

My team and I
(Left to right: Stacy, Drew, Me, and Kelly)

After our early speed bump though the ride was brilliant. We got incredibly lucky with the weather, which was mostly cloudy and never got above 76º F. There were definitely some down moments. Around mile 120 I fell into an apocalyptic despair: the result of my body almost completely running out of sugar and only curable by a giant Rice Krispy Treat at the next rest stop. Mostly the ride was wonderful though.

Long bike rides are a team effort. I did most of my training rides solo, so I had very little appreciation for this at the start of the ride. It is very hard to overstate the value of a good team for both moral and aerodynamic support. For the first hundred or so miles we were in pace lines of 20 to 30 riders and we were able to ride at upwards of 20 miles per hour without putting much effort into it. Toward the end of the ride there were only four of us but it still helped immensely to draft off of each other. I would not have finished the STP without my teammates. Both because of their wind-blocking abilities and the moral support they provided.

It is also hard to overstate the value of a short break. My instinct would have been to just get on my bike and ride continuously until I was in Portland but I don’t think I would have made it more than half way if we tried to ride the whole 206 miles in one go. We stopped about every 25 miles for ten to fifteen minutes to eat, fill our water bottles, and just get off our bikes for a little while and I’m glad we did. There were several times when I rolled into a rest stop feeling completely defeated, only to ride out fifteen minutes later feeling invincible.

We finished the ride around 9:30pm having spent 12 cumulative hours in the saddle. It was exhilarating and exhausting. I experienced heights of ecstasy and depths of despair that I’d never known before and that’s just one reason I will treasure the experience. Again, I couldn’t have done it without my teammates. Those guys were incredible.

At the finish line! (left to right: Stacy, Kelly, Me, Drew)

At the finish line! (left to right: Stacy, Kelly, Me, Drew)

When my girlfriend, Alicia, picked me up later that evening I only had one thing to say about the experience: “Did you know you can drive from Seattle to Portland in, like, a fifth of the time it takes to bike?”

 

 

Ultrasonic Sensors

If you’ve been through my simple motor control tutorial you now know how to control the input voltage to DC motors using an Arduino. In a differential drive robot like Colin, this means driving in a (nearly) straight line, a circle, or some other preset path. It would be more interesting if we could make a robot that reacts to its surroundings. For this we need sensors and ultrasonic sonar sensors are a good place to start.

Ultrasonic sensors determine the distance between themselves and the nearest obstacle by emitting an ultrasound pulse and timing how long it takes for that pulse to be reflected off the nearest obstacle and back to the sensor. They are easy to use, accurate, and they can be extremely cheap.

They do have limitations, however. Sound reflects best off of hard, smooth objects, so soft or uneven surfaces are not detected very well. Most kinds of fabric are basically invisible to an ultrasonic sensor. Reflection is another problem. To work properly, the sensor needs an obstacle to reflect its emitted pulse straight back to the sensor. If the sensor isn’t aimed straight at a perpendicular surface, the reflected pulse could miss the sensor entirely. In this case the sensor would not detect the obstacle. The datasheets for most sensors specify an angle within which the sensor will be able to reliably detect obstacles.

HC-SR04 sensor

HC-SR04 sensor

For this tutorial I’ll be using an HC-SR04 sensor. The datasheet for the sensor can be found here. Note that it will only reliably detect obstacles within +/- 15 degrees from perpendicular to the face of the sensor. The HC-SR04 definitely isn’t the best ultrasonic sensor out there. More accurate, longer range sensors exist. You cannot, however, find a cheaper ultrasonic sensor than the HC-SR04. If you shop around on Amazon you can find them for $1-3 apiece.

Before I get into the actual tutorial I should mention I’m making use of Tim Eckel’s NewPing library for Arduino here. It makes using these sensors a complete snap. You should definitely take a look at the tutorial on the Arduino Playground in addition to this one. If you don’t have experience installing or using new libraries in Arduino, take a look at this tutorial.


Single Sensor Example

We’ll start out by wiring up and testing a single HC-SR04 sensor. You’ll want to wire it up as in the diagram below.

Wiring diagram for a single HC-SR04 sensor.

Wiring diagram for a single HC-SR04 sensor.

Note the Trig and Echo pins on the sensor can be connected to different pins on the Arduino. The NewPing library makes it possible to connect the Trig and Echo pins to the same Arduino pin. This is great because it means the HC-SR04 doesn’t occupy as many I/O pins.

Note also there is no power source pictured on the above diagram. For the purposes of this exercise we can power the Arduino through its USB port, so no external power source is required.

Example Sketch

#include <NewPing.h>

#define TRIGGER_PIN 2
#define ECHO_PIN 2
#define MAX_DISTANCE 200 // max distance the sensor will return

NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // declare a NewPing object

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

void loop() {
  delay(50);
  int uS = sonar.ping(); 
  Serial.print("Ping: ");
  Serial.print(uS / US_ROUNDTRIP_CM); // convert ping time to distance in cm
  Serial.println("cm");
}

Load the above sketch on your Arduino and open a terminal window. Make sure the baud rate of the window is the same as the baud rate in your Serial.begin();  statement.

When initializing a NewPing object use the following form: NewPing(triggerPin, echoPin, distanceLimit); If you’re using the same pin for the trigger and echo just set triggerPin and echoPin to the same pin.

The example sketch will display the distance from the sensor to the nearest obstacle in centimeters every 50 milliseconds. If the nearest obstacle is beyond the distanceLimit or if the nearest obstacle is not detectable then sonar.Ping() will return 0.

Play around with the single sensor for a bit. Have some fun with it. Eventually start to realize how limited a single sensor is. A robot that can only see directly in front of itself can’t see obstacles at its sides. So if it approaches an obstacle at an angle, the obstacle won’t be detected. So what should we do? Add more sensors, of course!


Three Sensor Example

Now that we’ve got a single sensor up and running it should be a simple matter to add two more sensors. It can be useful to have an array of sensors when we’re trying to detect obstacles around a mobile robot like Colin. Wire the sensors up as in the diagram below.

Wiring diagram for three HC-SR04 sensors.

Wiring diagram for three HC-SR04 sensors.

Example Code

#include <NewPing.h>

// trigger and echo pins for each sensor
#define SONAR1 2
#define SONAR2 3
#define SONAR3 4
#define MAX_DISTANCE 200 // maximum distance for sensors
#define NUM_SONAR 3 // number of sonar sensors

NewPing sonar[NUM_SONAR] = { // array of sonar sensor objects
  NewPing(SONAR1, SONAR1, MAX_DISTANCE),
  NewPing(SONAR2, SONAR2, MAX_DISTANCE),
  NewPing(SONAR3, SONAR3, MAX_DISTANCE)
};

int distance[NUM_SONAR]; // array stores distances for each
                         // sensor in cm

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

void loop() {
  delay(50);
  updateSonar(); // update the distance array
  // print all distances
  Serial.print("Sonar 1: ");
  Serial.print(distance[0]);
  Serial.print("  Sonar 2: ");
  Serial.print(distance[1]);
  Serial.print("  Sonar 3: ");
  Serial.println(distance[2]);
}

// takes a new reading from each sensor and updates the
// distance array
void updateSonar() {
  for (int i = 0; i < NUM_SONAR; i++) {
    distance[i] = sonar[i].ping_cm(); // update distance
    // sonar sensors return 0 if no obstacle is detected
    // change distance to max value if no obstacle is detected
    if (distance[i] == 0)
      distance[i] = MAX_DISTANCE;
  }
}

Note the step in the updateSonar() method that checks if the distance returned by sonar[i].ping_cm() is 0, meaning the nearest obstacle is probably out of range. Technically it’s possible there is an obstacle 0cm away from the sensor, but that is not very likely. If the value returned by sonar[i].ping_cm() is 0, we switch it to MAX_DISTANCE .

Now we can control 3 sonar sensors simultaneously. We can use this in conjunction with our ability to control motors to make a robot that has autonomous behavior! One of the simpler things we can do with these two tools is program an obstacle avoidance routine. If that interests you stay tuned for my next tutorial, because I’m planning to do in on that very topic!

ICRA 2015 days 2-5

I’ve been at the conference on robotics and automation for five days now and all I can say is: “Did you know robots can play football?!”

https://www.youtube.com/watch?v=XLKKbz2mNyo

I stopped by the Robotis booth at the exhibition during lunch one day and they showed me the above video. Robotis makes the Darwin OP robots that make up the American team in the above video. Not only are they adorable, they’re also frighteningly smart. Their goal-blocking behaviors are pretty funny too.

On Wednesday I got to hear a talk from Peter Hart on making Shakey, one of the first autonomous mobile robots. Colin shares Shakey’s basic physical design and I think I can learn a lot from how Shakey’s software was organized. Shakey used a hierarchical system where one program controls low level functions like moving and sensing and separate programs handle motion planning, map building and localization. It seems like a good way to organize Colin’s functions. That’s a long way off though. I’m still busy writing Colin’s motor control library.

On Friday there were two sessions on Simultaneous Localization and Mapping (SLaM). I made sure to attend both of them since my ultimate goal for Colin is to program him for SLaM.

It seems a lot of implementations of SLaM rely on vision via Microsoft Kinect sensors. Those are out of my league for the moment; I have no idea how to program for them and Colin does not have the power to run any such program anyway. Laser rangefinders and LiDAR are also popular and, while I could potentially add one to Colin, they are expensive as hell. I’m still getting some good ideas though. Hopefully I can implement them on a robot that uses cheap ultrasonic and touch sensors instead of fancy RGB+D cameras.

Saturday was my favorite day at the conference by far. I attended an all-day workshop on building a career in robotics research called “Becoming a Robot Guru.” The speakers and panelists got me very excited about working toward a career in robotics. I was encouraged to learn that one doesn’t have to follow the traditional academic career path to be a robotics researcher. I have been worried lately that it may be too late for me to start a PhD in computer science or robotics, but many of the speakers on Saturday took more than a decade to start their PhD after they completed their bachelor’s degree. That gives me hope that a career in research is still an option for me. I’m starting my master’s degree this fall. All I really have to do is keep working at it and I’ll get there eventually.

Simple Motor Control

I’ve finally gotten around to outlining the basics of DC motor control with the Arduino. We won’t be completely starting from scratch. I will assume you have some familiarity with the Arduino and its programming environment. If you’ve never done anything with the Arduino before I’d suggest doing some simple tutorials, like how to blink an LED, first. If you can make the blink tutorial work and wire everything up properly, you should be able to get through the examples here.

I’ll start out with a parts list, then I’ll outline how to control a single motor, and then I’ll move on to dual motor control.


Parts List

  • 1 Arduino; I’ll be using the Duemilanove but the examples should work with the newer Uno and most of the other Arduino variants as well.
  • 1 dual motor driver; I’ll be using the TB6612FNG from Pololu
  • 2 DC motors; I’m using micro metal gearmotors from Pololu
  • Batteries; I’m using a 4 AA pack that gives around 6V but I’ll discuss other options as well
  • 1 voltage regulator; this can be really handy but it’s not totally necessary
  • Hookup wire; I suggest you use ribbon cable but, at a minimum you should use several different colors of wire. It’s hard to appreciate how hard it is to wire up electronics using only one color of wire unless you’ve actually tried it.
  • Finally, some soldering skill will be necessary. There’s a good tutorial here if you’re not familiar. Do a lot of practicing before you try to solder anything you care about.

The Motor Driver

The Arduino’s digital pins cannot provide enough current to run even a small motor. So we need a power source for the motor that can supply a lot of current, but we also need a way to control that power source using the Arduino. This can be done using a simple switching transistor as in this great Adafruit tutorial but using a motor driver circuit like the TB6612FNG has some advantages:

  • We don’t have to build our own motor control circuit. As long as we wire it up correctly the TB6612FNG works right out of the box.
  • It allows us to control two motors with the same circuit, so it saves us a bit of complexity for dual motor control.
  • Most importantly, it gives us software control of the motor’s direction using the AIN1 and AIN2 pins for one motor, and the BIN1, and BIN2 pins for the other:
    • If IN1 is driven HIGH and IN2 is LOW the motor will turn forward
    • If IN1 is driven LOW and IN2 is HIGH the motor will turn backward
    • If IN1 and IN2 are LOW the motor will freewheel
    • If IN1 and IN2 are driven HIGH the motor will lock up or be in a “braking” state

The Battery / Voltage Regulator

All of the components in this setup have different power requirements. The motors require around 6V and they draw a lot of current. The Arduino needs 7-12V. It is possible to power the Arduino by connecting a 5V source to the 5V pin, but this bypasses the Arduino’s onboard voltage regulator so you need to make sure you have a regulated 5V source if you want to go that route. Lastly, the motor driver needs 5V for its logic. The motor driver can get its 5V from the 5V pin on the Arduino, but how do we satisfy the different requirements of the motors and the Arduino?

Option 1: Different power sources for the motors and Arduino

A 4 AA battery pack provides between 5.5 and 6.5V and can give a lot of current, which makes it good for the motors. But the Arduino needs 7-12V. Some people solve this by using a separate power source for the Arduino, usually a 9V battery. This is problematic for a couple of reasons. First, the Arduino’s onboard regulator isn’t very efficient when stepping 9V down to 5V and a lot of energy from the 9V battery is lost as waste heat. Second, having 2 separate battery packs adds weight and complexity. Wouldn’t it be much easier if we could power everything using just one battery pack?

Option 2: Voltage regulator

The addition of a voltage regulator like this one can make things much simpler. As in the wiring diagram above, you can power the motors directly from the battery pack and the Arduino can get power from the voltage regulator, which is tuned to output 5V. This approach is simpler, lighter weight, and more efficient than option 1.

A word of warning: do not try to power the motor with the output of the voltage regulator. They will draw more current than the regulator can supply and cause the voltage from the regulator to drop. When this happens your Arduino will reset, which can cause some pretty baffling problems if you don’t know what’s happening. Just power your motor directly from the battery as in the diagram.


Single Motor Control

The most important part of this project is wiring the physical components correctly. As I said in a previous post, I highly recommend wiring everything up on a breadboard. Do not make any soldered connections if you don’t absolutely have to. Rewiring is far easier when you’re using a breadboard. Below is a diagram of a wiring configuration that will work with my example sketch.

single motor control

breadboard layout for single motor control

Single Motor Example Sketch

It’s finally time for the example code! You should be able to copy and paste the code below into your Arduino IDE if you’ve wired your motor up exactly as I have in the diagram above.

// Single Motor Control example sketch
// by Andrew Kramer
// 5/28/2015

// makes the motor run at 1/2 power

#define PWMA 3 // PWM pin for right hand motor
#define AIN1 4 // direction control pin 1
               // AIN1 on the motor controller
#define AIN2 5 // direction control pin 2 
               // AIN2 pin on motor controller

void setup() {
  // set all pins to output
  for (int pin = 3; pin <= 5; pin++) {
    pinMode(pin, OUTPUT); // set pins 3 through 5 to OUTPUT
  }
  // set motor direction to forward 
  digitalWrite(AIN1, HIGH);
  digitalWrite(AIN2, LOW);
  // set the motor to run at 1/2 power
  analogWrite(PWMA, 128);
}

void loop() {
  // we're just making the motor run at a constant speed,
  // so there's nothing to do here
}

So now we can wire up a motor and feed it a constant amount of voltage. That’s useful, but not very interesting by itself. To make a differential drive robot like Colin, we need to be able to control two motors simultaneously.


Dual Motor Control

The basics of dual motor control are the same as for single motor control, we’re just adding an additional motor. Wire up your second motor as shown in the diagram below.

dual motor control

wiring diagram for dual motor control

Dual Motor Example Sketch

The sketch below, when used in a differential-drive robot will make the robot drive in a small circle. It’s a common problem to load up this example and find one or both of your motors is running backward. If this happens, you can either switch the states of the IN1 and IN2 pins for the backward motor in void setup() . Or, if you want a hardware solution, you can switch the positions of the wires connecting to IN1 and IN2 or OUT1 and OUT2.

// Dual Motor Control example sketch
// by Andrew Kramer
// 5/28/2015

// makes motor A run at 1/4 power and
// motor B run at 1/8 power
// in a differential drive robot this would cause the robot to
// drive in a small circle

#define PWMA 3 // PWM pin for right hand motor
#define AIN1 4 // direction control pin 1 for right motor
#define AIN2 5 // direction control pin 2 for right motor
#define PWMB 9 // PWM pin for left hand motor
#define BIN1 7 // direction control pin 1 for left motor
#define BIN2 8 // direction control pin 2 for left motor

void setup() {
  // set all pins to output
  for (int pin = 3; pin <= 5; pin++) {
    pinMode(pin, OUTPUT); // set pins 3 through 5 to OUTPUT
  }
  for (int pin = 7; pin <= 9; pin++) {
    pinMode(pin, OUTPUT); // set pins 7 through 9 to OUTPUT
  }
  // set both motors to forward 
  digitalWrite(AIN1, HIGH);
  digitalWrite(AIN2, LOW);
  digitalWrite(BIN1, HIGH);
  digitalWrite(BIN2, LOW);
  // set right motor to run at 1/4 power
  analogWrite(PWMA, 64);
  // set left motor to run at 1/8 power 
  analogWrite(PWMB, 32);
}

void loop() {
  // we're just making the motors run at constant speed,
  // so there's nothing to do here
}

Further Work

So now we can give two motors a constant amount of input power. That’s a bit boring, isn’t it? You can take things a bit further by making the motor speed respond to sensor inputs. For example, you could control the power to the motors using a potentiometer. You could make your differential drive robot follow more complicated paths or avoid obstacles using ultrasonic sensors, which I’ll discuss in a later post.

There’s a big problem here though. Just using analogWrite()  doesn’t control the motor’s speed, only the voltage to the motor. Even if you give both motors the same input voltage they won’t run at exactly the same speed. So you won’t be able to get a differential drive robot to drive in a straight line. For that we need actual speed control, and for that the motor needs to be able to tell the Arduino how fast it’s turning, and for that we need encoders. I’ll write a post about speed control using encoders like these soon.

 

First Pictures of Colin

I have an increasing number of friends and family members who use Facebook mainly as a vehicle for posting pictures of their toddlers. I try to counterbalance that by posting lots of pictures of Colin. Here’s Colin at 11 months old:

Isn't he adorable?

11 month old Colin!

Look at his cute little bluetooth radio!

Look at his cute little bluetooth radio!

I’m going to add more ultrasonic sensors soon, but this is what he looks like for now.

So far I’ve gotten him to avoid obstacles, follow walls, and I’m working on a PID motor control library. I’ll be doing a post with the technical details of motor control very soon. Stay tuned!

ICRA 2015 Day One

I got extremely lucky this week. First of all, the IEEE Conference on Robotics and Automation is in Seattle this year. Second, I managed to convince my manager at Boeing to let me attend! So, for the whole week I’m going to be attending lectures and workshops on advanced topics in robotics rather than going to my regular job. I seriously cannot articulate how thrilled I am about that. Also, just as a little bit of icing on the cake, the conference venue is a fifteen minute bike ride from my apartment. A whole week with no commutes and a lot learning and talking about robotics? Only one word applies: jackpot.


Dynamic Locomotion and Balancing of Humanoids:

I’ve only been through one day and it’s already pretty clear that most of this stuff is over my head right now. I spent the morning in a workshop titled “Dynamic Locomotion and Balancing of Humanoids.” I was able to follow along with the high-level concepts like the discussion of the differences in stability and efficiency of different movement strategies but whenever a presenter tried to explain the particulars of the extended Kalman filter they used for torque control of their ankle-joint motors my eyes glazed over.

I found the discussion of the inverted pendulum and linear inverted pendulum models for walking to be pretty interesting. Both models represent the robot as an inverted pendulum that repeatedly falls forward, switches its foot position to catch itself and then falling forward again. The main difference is, in the inverted pendulum model (IPM) the robot’s center of mass moves up and down. In the linear inverted pendulum model (LIPM) the robot’s center of mass is nearly stationary in the vertical direction. LIPM is stable but it’s not as energy efficient as IPM. ASIMO’s gait is a good example of LIPM. The video below shows it around the 0:30 mark.


Mobile Manipulators for Manufacturing:

One of my afternoon workshops, “Mobile Manipulators for Manufacturing,” gave me a much greater appreciation for the non-technical roadblocks to wide deployment of robotics. For robots to work effectively with humans, they need to be able to map and navigate their environment, recognize and manipulate objects, and interact with humans in a safe and predictable way.

Although none of these technical problems have really been solved to the extent required for large-scale use of autonomous robots in the workplace, there are other problems that present bigger difficulties. For instance, there are no unified standards for robot programming, communication, or interaction with humans. This makes plug-and-play robotics solutions impossible. Every time a workplace, be it an automobile factory or a hospital, wants to integrate robots into their workflow they need to get a custom designed system. This is not only expensive in terms of setup cost but it makes maintaining, reconfiguring, and adapting the system to new conditions needlessly difficult. You can’t just call up customer support when you have a problem, you need someone who is intimately familiar with your system.

There are other safety and regulatory hurdles for mobile industrial robots to clear before they are widely implemented but, to make a long story short, the non-technical problems make the technical ones look simple. Technical problems have definite solutions. Regulatory and business problems don’t.


On my first day at the ICRA I learned more about the existence of a whole range of topics I wasn’t previously aware of than I learned about the particulars of any one topic. I’m expanding my realm of “things I know I don’t know” more than I am expanding the things I know.

There’s so much that goes into a seemingly simple task like making a robot walk like a human or recognize and pick up an everyday object. This is what I love about computer programming in general though: to teach a robot or computer even the most simple task requires an incredible understanding of the task itself.

Designing Colin

I had wanted to build a robot for years and my friendly competition finally gave me a motivation: to complete my robot before Jacob’s and throw my success right in his face.

Our Problem:

It seems simple enough at first glance. We just need to create robots capable of autonomously mapping a small, indoor space such as a 1 bedroom apartment. The robots need to determine the outline of the room and map all of the obstacles (furniture, tool boxes, piles of clothes, etc) within the room. We even made a couple of concessions to speed things up: The robots do not have to operate on carpet and they are allowed to use fixed points (such as RF beacons) for navigation. Easy, right?

Of course, neither of us know very much about robotics or programming, but learning-on-the-fly is sort of the point of this exercise.


Physical Requirements:

An abandoned early design.

An abandoned early design.

I decided to sort out my robot’s physical design before worrying about software, which would likely be far more difficult. So I need to ask, what does my robot need to do?

  • Move around
  • Control its own movements
  • Power itself
  • Transmit map data to a remote computer
  • Sense its surroundings
  • Sense its location

I realized from the outset that I would probably need to change the design in response to unforeseen problems, so I also wanted my robot to be easy to rewire and reconfigure. Also, I’m not a rich man, so I need a robot that will fulfill these requirements without being absurdly expensive. This means I need to use cheap, off-the-shelf parts as much as possible and keep custom fabrication to a minimum.

Finally, I my robot needed a name. Referring to it as “my robot” is just too cumbersome. I named it Colin, after the happy security robot from Mostly Harmless by Douglas Adams.


Design

For movement I elected to go with a differential drive system, like you would find on a Roomba. It has two parallel wheels, each with its own motor. Colin can move forward or Differential drive diagram from wikipedia.backward by running his motors at the same speed and he can rotate by running them at different speeds. I also considered omni-wheels for added maneuverability but the extra complication in design and programming did not seem to be worth the benefit. I chose a pair of gearmotors and wheels from Pololu. I made sure to get motors with extended backshafts so they could be used with encoders. This will be important later. Also, most controllers can’t directly power a motor, so a motor driver circuit will be necessary as well.ArduinoDuemilanove

For control I decided to use an Arduino Duemilanove. It is functionally equivalent to the newer Arduino UNO and I had one lying around from a previous project. The computation requirements of this project are probably greater than the Arduino’s ATmega328 can handle, but it will suffice for awhile. I can always change it out later.

For power I’m using four AA batteries. I badly wanted to use a lithium polymer battery like one of these from SparkFun but the extra expense did not seem worth the slight convenience.

To transmit map data I’m using a bluetooth radio. It probably would have been easy enough to do this via USB but bluetooth gives more versatility. Colin can’t be tethered by a USB cable while he’s mapping a room, but bluetooth presents no such problem. He can send back error messages and status updates to let me know how his job is progressing. Also, it seemed like a good idea to learn how to use bluetooth.hcsr04_hires

For sensing I decided to start with HC-SR04 ultrasonic sensors. They’re fairly accurate and Arduino already has a good code library to handle them thanks to Tim Eckel. The main advantage though, is that they’re dirt cheap. If you shop around on amazon you can get them for 2 or 3 dollars apiece including shipping.

For localization (telling Colin where he is) I’m using shaft encoders on my motors. These can be used to record how far the motor shaft has turned or how fast it’s turning, allowing Colin to calculate his present position relative to where he started.

To hold everything together I’m using a very simple chassis made from laser cut ABS plastic. I made my own designs using Inkscape and had the plastic custom cut at Metrix Create:Space in Seattle.

Lastly, to ensure my design is easy to reconfigure and rewire I’m doing all of my connections using a breadboard. If you’ve never used a breadboard I suggest you make learning to use one your first step before starting any electronics project. They allow you to build circuits without soldering, so rewiring is a snap.

I’ll include details on how to put these elements together into a functioning robot in later posts. Also stand by for tips on programming and example code!