Project Report 2 – The work of Pardue

For the first part of this exercise, we were to build a DIP Controlled LED Array. The setup used 5 digital pins on the Arduino board that were connected to ports 4 through 8 of the DIP switch. Afterwards, 8 LED lights were connected to the digital pins of the Arduino board via resistors. The result looked like this:

The code below was compiled and uploaded to the board. Now, whenever a switch was thrown from the DIP, a corresponding LED lit up as shown below:

The purpose of this exercise was to give us the knowledge on how to control the LEDs via the  state of a pin. The code used is displayed below:

// DIP to LED
// Joe Pardue September 29, 2009

void setup()
{
// Init pins for input
pinMode(8, INPUT); // DIP 0
digitalWrite(8,HIGH); // Turn on pullup
pinMode(9, INPUT); // DIP 1
digitalWrite(9,HIGH); // Turn on pullup
pinMode(10, INPUT); // DIP 2
digitalWrite(10,HIGH); // Turn on pullup
pinMode(11, INPUT); // DIP 3
digitalWrite(11,HIGH); // Turn on pullup
pinMode(12, INPUT); // DIP 4
digitalWrite(12,HIGH); // Turn on pullup
//pinMode(13, INPUT); // DIP 5
//digitalWrite(13,HIGH); // Turn on pullup

// Init pins for output
pinMode(0, OUTPUT); // LED 0
pinMode(1, OUTPUT); // LED 1
pinMode(2, OUTPUT); // LED 2
pinMode(3, OUTPUT); // LED 3
pinMode(4, OUTPUT); // LED 4
pinMode(5, OUTPUT); // LED 5
pinMode(6, OUTPUT); // LED 5
pinMode(7, OUTPUT); // LED 6

// preset to turn LEDs off
digitalWrite(0,HIGH);
digitalWrite(1,HIGH);
digitalWrite(2,HIGH);
digitalWrite(3,HIGH);
digitalWrite(4,HIGH);
digitalWrite(5,HIGH);
digitalWrite(6,HIGH);
digitalWrite(7,HIGH);
}

void loop()
{
digitalWrite(0,digitalRead(8)); // show DIP 0 state on LED 0
digitalWrite(1,digitalRead(9)); // show DIP 1 state on LED 1
digitalWrite(2,digitalRead(10)); // show DIP 2 state on LED 2
digitalWrite(3,digitalRead(11)); // show DIP 3 state on LED 3
digitalWrite(4,digitalRead(12)); // show DIP 4 state on LED 4
}

———————————————————————————————————————–

The Arduino can also be used to play music! Using the width of a pulse, a tone can be played for all to hear. By knowing the frequency at which a musical key is related with, the duration at which a pulse should last is known. It is simply 1 / frequency = period. All is described in the Arduino Melody tutorial page: http://www.arduino.cc/en/Tutorial/Melody

A simple melody is played below.

Sound familiar? It should. It’s a childhood melody Twinkle Twinkle Little Star.

The code for this project is displayed below:

int speakerPin = 9;

int length = 15; // the number of notes
char notes[] = "ccggaagffeeddc "; // a space represents a rest
int beats[] = { 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 4 };
int tempo = 300;

void playTone(int tone, int duration) {
  for (long i = 0; i < duration * 1000L; i += tone * 2) {
    digitalWrite(speakerPin, HIGH);
    delayMicroseconds(tone);
    digitalWrite(speakerPin, LOW);
    delayMicroseconds(tone);
  }
}

void playNote(char note, int duration) {
  char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };
  int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956 };

  // play the tone corresponding to the note name
  for (int i = 0; i < 8; i++) {
    if (names[i] == note) {
      playTone(tones[i], duration);
    }
  }
}

void setup() {
  pinMode(speakerPin, OUTPUT);
}

void loop() {
  for (int i = 0; i < length; i++) {
    if (notes[i] == ' ') {
      delay(beats[i] * tempo); // rest
    } else {
      playNote(notes[i], beats[i] * tempo);
    }

    // pause between notes
    delay(tempo / 2);
  }
}

———————————————————————————————————————–

The IR sensors are devices that measure distance via the speed of light. The time it take to go from the sensor to the object and back is converted to distance and then divided by two. The following code can be used to retrieve the distance in centimeters:

#define IR 0

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

void loop()
{
Serial.println(readIR(IR));
delay(500);
}

float readIR(byte pin)
{
int tmp =0;
for(int i=0; i<5 ; i++)
{
tmp = tmp + analogRead(pin);
}
tmp = tmp / 5;
if (tmp < 3) return -1; // invalid value
return (6787.0 /((float)tmp – 3.0)) – 4.0;
}

The setup of the IR sensor is simply 5V, Ground, and a Data pin. That’s all that was used in the setup below.

The sensor (on the right) is reading the distance of the breadboard (on the left). I have placed a ruler starting from the IR sensor and placing the bread board 15cm away. Using the serial monitor and reading values every half a second, the results varied only a little.

———————————————————————————————————————–

H-Bridges are Integrated Chips (IC) that enable us to control the speed and direction of a motor. This diagram shows our robot’s implementation of the IC’s. We decided to use one H-Bridge (which controls two motors) to control one side of the robot. The Not-Gates helped us reduce the number of pins for a more efficient outlook. The capacitors are used as to filter out any noise or voltage spikes that would cause undesired operation.

To make the robots move forward and backwards, the following code was used:

// Most Arduino boards provide six 8-bit PWM (Pulse Width Modulated) signal outputs: pins 3, 5, 6, 9, 10, and 11
#define motor_left_speed 9        // Pin for the Left Motor Speed connected to PWM
#define motor_left_dir 6          // Pin for the Left Motor Direction connected in conjunction w/ NOT-Gate
#define motor_right_speed 10      // Pin for the Right Motor Speed connected to PWM
#define motor_right_dir 4         // Pin for the Right Motor Direction connected in conjunction w/ NOT-Gate

// PWM Signal – Range: 0 – 255
int velocity = 200;

void setup()
{

}

void loop()
{
// Forwards
analogWrite(motor_left_speed, velocity);
analogWrite(motor_right_speed, velocity);
digitalWrite(motor_left_dir, HIGH);
digitalWrite(motor_right_dir, HIGH);

delay(2000);

// Stop
analogWrite(motor_left_speed, 0);
analogWrite(motor_right_speed, 0);

delay(2000);

// Backwards
analogWrite(motor_left_speed, velocity);
analogWrite(motor_right_speed, velocity);
digitalWrite(motor_left_dir, LOW);
digitalWrite(motor_right_dir, LOW);

delay(2000);

// Stop
analogWrite(motor_left_speed, 0);
analogWrite(motor_right_speed, 0);

delay(2000);
}

I did not implement exact degree rotation because this involves the optical encoders. This task still poses a challenge to complete accurately.

Advertisements

Final Robot Code

There’s always room for improvement and flexibility, but nonetheless, here is the current robot code:

// Embedded Robotics – Fall 2011
// Professor Ravi Shankar
// Group 1
// Jordan Lohr & Alberto Avalos

#define motor_left_speed 6      // Pin for the Left Motor Speed connected to PWM
#define motor_left_dir 4        // Pin for the Left Motor Direction connected in conjunction w/ NOT-Gate
#define motor_right_speed 5     // Pin for the Right Motor Speed connected to PWM
#define motor_right_dir 7       // Pin for the Right Motor Direction connected in conjunction w/ NOT-Gate
/******************************************************/
const int encoderLeftPin = 11;
const int encoderRightPin = 12;
const int IRLeftPin = 5;
const int IRRightPin = 4;
const int URMPin1 = 8;
const int URMPin2 = 9;

const int FORWARD = 0;          //Variable used for direction
const int BACKWARD = 1;         //Variable used for direction
const int LEFT = 2;             //Variable used for direction
const int RIGHT = 3;            //Variable used for direction
const double PULSEDIST = 1.05 ;   //Ratio of distance travelled per slot of the encoders (21cm/10slots/2voltageChangesPerSlot +- error)
const int STDVEL = 200;
volatile unsigned int pulseLeft = 0;
volatile unsigned int pulseRight = 0;
int state = LOW;
int encoderLeft = 0;
int encoderRight = 0;
int oldRight = 0;
int oldLeft = 0;

int distance;
int left_distance;
int right_distance;

#include “URMSerial.h”
//3,5,10,11 = pwm

// The measurement we’re taking
#define DISTANCE 1
#define TEMPERATURE 2
#define ERROR 3
#define NOTREADY 4
#define TIMEOUT 5

URMSerial urm;

void setup()
{
Serial.begin(9600);
pinMode(encoderLeftPin, INPUT);
digitalWrite(encoderLeftPin, LOW);
pinMode(encoderRightPin, INPUT);
digitalWrite(encoderRightPin, LOW);
oldLeft = digitalRead(encoderLeftPin);
urm.begin(9,8,9600);
oldRight = digitalRead(encoderRightPin);
pinMode(motor_left_speed, OUTPUT);
pinMode(motor_right_speed, OUTPUT);
pinMode(motor_left_dir, OUTPUT);
pinMode(motor_right_dir, OUTPUT);
Stop();
}

void loop()
{
delay(5000);
doSquare();
//compTesting();
//sensorTesting();
}

void doCircle(int dir, int leftVel, int rightVel)
{
setDir(dir);
setVel(leftVel, rightVel);
}

/*
goes in a square whose side is in inches
*/
void doSquare()
{
goStraight(FORWARD, getMeasurement() – 4, STDVEL – 50, 120, 70);
delay(2000);
rotate2(RIGHT, 200, 200);
delay(2000);
goStraight(FORWARD, getMeasurement() – 4, STDVEL – 50, 120, 70);
delay(2000);
rotate2(RIGHT, 200, 200);
delay(2000);
goStraight(FORWARD, getMeasurement() – 4, STDVEL -50, 120, 70);
delay(2000);
rotate2(RIGHT, 200, 200);
delay(2000);
goStraight(FORWARD, getMeasurement() – 4, STDVEL -50, 120, 70);
delay(2000);
rotate2(RIGHT, 200, 200);
delay(2000);
}

/*
Moves the car carying the speed to keep it even.
dir = direction (FORWARD, BACKWARD, RIGHT, LEFT).
distance = distance to travel in inches.
vel = velocity value (0 – 255).
*/
void goStraight(int dir, int distance, int vel, int midVel, int slowVel)
{
pulseLeft=0;     //resets encoder value
pulseRight =0;   //resets encoder value
setDir(dir);
int pulsesToTravel = (int)(distance/PULSEDIST);   //converts distance in inches to the amount of slots that it takes
Serial.print(“slots to travel:      “);
Serial.println(pulsesToTravel);
while ((pulseRight <= pulsesToTravel) && (pulseLeft <= pulsesToTravel))
{
if((pulsesToTravel – pulseRight) < 40)
{
vel = midVel;
if((pulsesToTravel – pulseRight) < 20)
{
vel = slowVel;
}
}
if(pulseRight != pulseLeft)
{
if(pulseLeft >= pulseRight)
{
setVel(vel – 50, vel + 50);
}
else if(pulseLeft <= pulseRight)
{
setVel(vel + 50, vel – 50);
}
}
else
{
setVel(vel, vel);
}
checkEncoderChange();
}
Stop();
}

/*
Rotates the car making each side turn in different direction
dir = direction: Use only RIGHT or LEFT.
angle = angle to turn in degrees.
vel = velocity value (0 – 255).
*/
void rotate(int dir, int angle, int vel)
{
double radian = ((double)angle * (PI/(double)180));   //converts degrees to radians.
int sector = 4 * radian + 1;   // sector of circle = radius of car * angle in radians  + error due to traction
goStraight(dir, sector, vel, 130, 130);
}

void rotate3(int dir, int angle, int velLeft, int velRight)
{
pulseLeft=0;     //resets encoder value
pulseRight =0;   //resets encoder value
setDir(dir);

double radian = ((double)angle * (PI/(double)180));   //converts degrees to radians.
int sector = 4 * radian + 1;   // sector of circle = radius of car * angle in radians  + error due to traction
int pulsesToTravel = (int)(sector/PULSEDIST);   //converts distance in inches to the amount of slots that it takes
Serial.print(“slots to travel:      “);
Serial.println(pulsesToTravel);
while (pulseRight <= pulsesToTravel)
{
setVel(velLeft, velRight);
checkEncoderChange();
}
Stop();
delay(1000);
pulseLeft=0;     //resets encoder value
pulseRight =0;   //resets encoder value
while (pulseLeft <= pulsesToTravel)
{
setVel(velRight, velLeft);
checkEncoderChange();
}
Stop();
}

/*
Rotates the car making each side turn in different direction
dir = direction: Use only RIGHT or LEFT.
angle = angle to turn in degrees.
vel = velocity value (0 – 255).
*/
void rotate2(int dir, int angle, int vel)
{
boolean finish_flag = false;
int tempIR = 0;
int tempUS = 0;
int oldIR =0;
int oldUS=0;
int startingIR = readIR(IRLeftPin);
int startingUS = getMeasurement();
int targetIR = startingUS;
int targetUS = 100;
delay(1000);
boolean corner_flag = false;
boolean before_flag = false;
int maxDist = 0;
int minDist = 0;
int minUSDist = 0;
setDir(RIGHT);
setVel(vel,vel);
while (!corner_flag)
{
tempIR = readIR(IRLeftPin);
if(tempIR > oldIR)
{
maxDist = tempIR;
Serial.print(“MaxDist:     “);
Serial.println(maxDist);
}
else if(tempIR < (maxDist – 20))
{
Stop();
corner_flag = true;
Serial.println(“Corner Found”);
Serial.print(“Current IR Reading:     “);
Serial.println(tempIR);
Serial.print(“Old IR Reading:     “);
Serial.println(oldIR);
delay(1000);
setVel(170,170);
delay(150);
Stop();
delay(150);
oldIR = readIR(IRLeftPin);
oldUS = getMeasurement();
minDist = oldIR;
setVel(150,150);
delay(150);
Stop();
delay(150);
}
Serial.print(“Current Right IR Reading:     “);
Serial.println(tempIR);
oldIR = tempIR;
}

while(!finish_flag)
{
setVel(170,170);
delay(120);
Stop();
delay(100);
tempIR = readIR(IRLeftPin);
if (tempIR < oldIR)
{
minDist = tempIR;
Serial.print(“MinDist:     “);
Serial.println(minDist);
}
else if (tempIR > minDist)
{
finish_flag = true;
Serial.println(“90 degree Found”);
Serial.print(“Old IR Reading:     “);
Serial.println(oldIR);
delay(150);
if(readIR(IRLeftPin) > tempIR){
setDir(LEFT);
setVel(150, 150);
delay(120);
Stop();
}
}
oldIR = tempIR;
}
Stop();
}

/*
Checks the values of the encoders
*/
boolean checkEncoderChange()
{
encoderRight = digitalRead(encoderRightPin);
encoderLeft = digitalRead(encoderLeftPin);
if(encoderRight != oldRight)
{
pulseRight++;
oldRight = encoderRight;
Serial.print(“Right:     “);
Serial.println(pulseRight);
}
if(encoderLeft != oldLeft)
{
pulseLeft++;
oldLeft = encoderLeft;
Serial.print(“Left:     “);
Serial.println(pulseLeft);
}
}

/*
Turns motors off
*/
void Stop()
{
setVel(0,0);
}

/*
Sets the PWN (0-255) of the left and right motors, giving speed to individual sides of the car
velLeft = PWM for left motors.
velRight = PWM for right motors
*/
void setVel(int velLeft, int velRight)
{
analogWrite(motor_left_speed, velLeft);
analogWrite(motor_right_speed, velRight);
}

/*
Sets the direction
dir = direction (FORWARD, BACKWARD, RIGHT, LEFT)
*/
void setDir(int dir)
{
switch(dir)
{
case FORWARD:
digitalWrite(motor_left_dir, LOW);
digitalWrite(motor_right_dir, LOW);
break;
case BACKWARD:
digitalWrite(motor_left_dir, HIGH);
digitalWrite(motor_right_dir, HIGH);
break;
case LEFT:
digitalWrite(motor_left_dir, HIGH);
digitalWrite(motor_right_dir, LOW);
break;
case RIGHT:
digitalWrite(motor_left_dir, LOW);
digitalWrite(motor_right_dir, HIGH);
}

}

float readIR(byte pin)
{
int tmp =0;
for(int i=0; i<5 ; i++)
{
tmp = tmp + analogRead(pin);
}
tmp = tmp / 5;
if (tmp < 3) return -1; // invalid value
return (6787.0 /((float)tmp – 3.0)) – 7.0;
}

// gets meassurement from ultrasound
int value; // This value will be populated
int getMeasurement()
{
// Request a distance reading from the URM37
switch(urm.requestMeasurementOrTimeout(DISTANCE, value)) // Find out the type of request
{
case DISTANCE: // Double check the reading we recieve is of DISTANCE type
//    Serial.println(value); // Fetch the distance in centimeters from the URM37
return value;
break;
case TEMPERATURE:
return value;
break;
case ERROR:
Serial.println(“Error”);
break;
case NOTREADY:
Serial.println(“Not Ready”);
break;
case TIMEOUT:
Serial.println(“Timeout”);
break;
}

return -1;
}

void compTesting()
{
checkEncoderChange();
char letter;
if(Serial.available())
{
letter = Serial.read();
if(letter == ‘f’)
{
goStraight(FORWARD, getMeasurement() – 5, STDVEL-50, 110, 65);
}
else if(letter == ‘s’)
{
Stop();
}
else if(letter == ‘r’)
{
rotate(RIGHT, 160, 255);
}
else if(letter == ‘p’)
{
rotate3(RIGHT, 90, 0, 180);
}
else if(letter == ‘l’)
{
rotate(LEFT, 160, 255);
}
else if(letter == ‘b’)
{
goStraight(BACKWARD, 24, STDVEL, 100, 60);
}
else if(letter == ‘m’)
{
rotate2(RIGHT, 200, 200);
}
else if(letter == ‘c’)
{
doCircle(FORWARD, 00, 250);
delay(17000);
Stop();
}
else if(letter == ‘t’)
{
sensorTesting();
}
}
}

void sensorTesting()
{
Serial.print(“Left:   “);
int leftIR = readIR(IRLeftPin);
Serial.print(leftIR);
Serial.print(”               “);
Serial.print(“Right:   “);
int rightIR = readIR(IRRightPin);
Serial.print(rightIR);
Serial.print(”               “);
Serial.print(“Front:   “);
int urm = getMeasurement();
Serial.println(urm);
Serial.println(”    “);
delay(500);
}

Test – With Marker

At last comes the ultimate test, an actual drawing with the marker in place.

One thing to note, the marker created a bit of a drag force on the robot which caused the robot to not be able to fully adjust itself. The turns on the corners were not even because of the inability to pick up the marker and then place it back down when needed. After acquiring the marker holder, we realized that the axis of rotation needed to be about the marker and not the center of gravity of the robot. These were a couple limitations of the robot. A major improvement for the robot would be to use a 2-wheeled platform. This would make it easier to turn.

Test – Without Marker

As the development of our robot progressed, it was time to do some field testing. The enclosures that we were using were composed of MDF (Medium Density Fibre) and painted with an oil based high gloss white enamel paint. More details on this setup can be found at our fellow classmate, Luiz Dias’s blog site: http://lhdrobotics.wordpress.com/2011/11/29/walls/ This is what we tested in:

The picture below shows the drawing paper and card stock used to make the surface area

In order to make the square of our drawing, here is the logic behind our code so that you may be able to follow with the video below. The robot starts off by retrieving a reading from the Ultra Sonic Sensor from the posed obstacle. This reading is used to travel that exact distance. After coming the a complete stop, it turns left until the reading from the IR sensor to the obstacle spikes. A little function is used to compensate the robot if it turns too far. Then it repeats the process.

Progress – Corner Finder

After extensive testing of the Infrared Sensors. We have determined that their accuracy in determining distance is inhibited greatly. Initially we wanted to use the sensor to scan the wall as we turned in order to make a 90 degree turn. When turning, in theory, the sensor would reach the corner boundary of the square enclosure we were in and then continue turning until the shortest distance was found. This would mean that the 90 degree turn was complete. Or test results proved most disturbing. The following two pictures show the Infrared Sensor pointing at an obstacle at slight distance variations. We used a laser pointer to show where the IR sensor was pointing.

The values reported back to the Serial Monitor were roughly the same values that were reported from the two pictures below.

This was a major issue because this meant that the IR sensors were useless in our current implementation. Then a light bulb went off and an idea came to us. We noticed that when the IR sensor hit the corner of the enclosure, the reading spiked tremendously. So, we used this to our advantage in our code. Here is a video of the robot finding the corner of the enclosure.

Progress – Optical Encoder Compensation

So after implementing the H-Bridges into our robot to get it mobile, we receive these optical encoders that are supposed to count the revolutions of our wheels in order to account for some of the traction issues we encountered. Upon our research as to how these devices specifically worked, we stumbled upon a very insightful website that explained things quite clearly: http://abrobotics.tripod.com/Ebot/using_encoder.htm

As part of the functionality of our robot, we coded a function that would compensate for any deviation the wheels would make from the intended path of movement. Let’s say for example, that our robot was to travel in a straight line from point A to point B. Seems simple enough… until for some unforeseen reason (aka the crooked shaped wheels) the robot starts to stray to the left.

A crooked wheel, causes the robot to have unforeseen movement.

This is where the compensation function comes into play. This is demonstrated in the video below.

In order for the robot to stray to the left, then that means that the two wheels on the right hand side are moving faster than the left hand side. To compensate, the right wheels must slow down and stop and the left wheels must speed up. This is what the video demonstrates.