Obstacle Avoiding Arduino Robot


This was worked on as a group project. We discussed the requirements of this project and how we planned to meet the requirements. We also discussed other things that we could work on to boost our marks.

The assignment required the robot to drive down a corridor, avoiding obstacles using at least two ultrasonic sensor and it had to display data on an LCD display. We decided that the following would be displayed on the 20x4 I2C LCD Display:

  • Temperature/Humidity
  • Light level
  • Ultrasonic sensor distances
  • Movement status/direction

The brains of the robot is an Arduino Uno. Two ultrasonic sensors were used for obstacle avoidance, and the robot will be powered using a Poundland Power Bank.

We were given two stepper motors to drive the robot..

We were given some of the parts to start testing the robot:

  • Laser cut base frame
  • Laser cut wheels
  • 3D printed motor mounts

The things we needed were:

  • DHT11 Temperature/Humidity sensor (from my personal collection)
  • Light dependant resistor (we have been given a couple by the lab technician)

First things first was to get the basics working, like the motors working together and at least 2 ultrasonic sensors working together. We got these working separately and then worked on combining the two systems together once we had them functioning separately.

Assembly and motor control

We started to assemble the robot. Here are some pictures:

I started working on getting the two motors working together. I stripped down the code from the Stepper Motor Project, and added the functionality of running a second motor. I chose to operate the motors using Full Step Drive as this provides more torque than Wave Drive, but doesn’t reduce the speed like Half Step Drive does.

Here is the code to get the two motors working together:




/* Stepper Motor Project for EO122
 * Joseph Taylor
 *
 * Designed to drive two unipolar stepper motors (5-wire)
 *
 * ----- Left Motor -----
 * Blue (1) - Pin 8
 * Pink (2) - Pin 9
 * Yellow (3) - Pin 10
 * Orange (4) - Pin 11
 * Red (5) - 5V
 *
 * ----- Right Motor -----
 * Blue (1) - Pin 4
 * Pink (2) - Pin 5
 * Yellow (3) - Pin 6
 * Orange (4) - Pin 7
 * Red (5) - 5V
 *
 */
 
// Assign each motor pin to an Arduino pin
int motorLPin1 = 8;
int motorLPin2 = 9;
int motorLPin3 = 10;
int motorLPin4 = 11;
 
int motorRPin1 = 4;
int motorRPin2 = 5;
int motorRPin3 = 6;
int motorRPin4 = 7;
 
// Set other variables
int delayTime = 0;
int robotDirection = 0;
 
void setup() {
 
 // Initiate all the motor pins
 pinMode(motorLPin1, OUTPUT);
 pinMode(motorLPin2, OUTPUT);
 pinMode(motorLPin3, OUTPUT);
 pinMode(motorLPin4, OUTPUT);
 pinMode(motorRPin1, OUTPUT);
 pinMode(motorRPin2, OUTPUT);
 pinMode(motorRPin3, OUTPUT);
 pinMode(motorRPin4, OUTPUT);
 
}
 
void loop() {
 
 // Run the forward function
 forward();
 
 // Set the delay time between motor pulses (I found 2ms to be the best)
 delayTime = 2;
}
 
 
// The forward function
void forward() {
 if (robotDirection == 0) {
 digitalWrite(motorLPin1, HIGH);
 digitalWrite(motorLPin2, LOW);
 digitalWrite(motorLPin3, LOW);
 digitalWrite(motorLPin4, HIGH);
 
 digitalWrite(motorRPin1, LOW);
 digitalWrite(motorRPin2, LOW);
 digitalWrite(motorRPin3, HIGH);
 digitalWrite(motorRPin4, HIGH);
 
 delay(delayTime);
 
 digitalWrite(motorLPin1, HIGH);
 digitalWrite(motorLPin2, HIGH);
 digitalWrite(motorLPin3, LOW);
 digitalWrite(motorLPin4, LOW);
 
 digitalWrite(motorRPin1, LOW);
 digitalWrite(motorRPin2, HIGH);
 digitalWrite(motorRPin3, HIGH);
 digitalWrite(motorRPin4, LOW);
 
 delay(delayTime);
 
 digitalWrite(motorLPin1, LOW);
 digitalWrite(motorLPin2, HIGH);
 digitalWrite(motorLPin3, HIGH);
 digitalWrite(motorLPin4, LOW);
 
 digitalWrite(motorRPin1, HIGH);
 digitalWrite(motorRPin2, HIGH);
 digitalWrite(motorRPin3, LOW);
 digitalWrite(motorRPin4, LOW);
 
 delay(delayTime);
 
 digitalWrite(motorLPin1, LOW);
 digitalWrite(motorLPin2, LOW);
 digitalWrite(motorLPin3, HIGH);
 digitalWrite(motorLPin4, HIGH);
 
 digitalWrite(motorRPin1, HIGH);
 digitalWrite(motorRPin2, LOW);
 digitalWrite(motorRPin3, LOW);
 digitalWrite(motorRPin4, HIGH);
 
 delay(delayTime);
 
 }
}
 
// The reverse function
void reverse() {
 if (robotDirection == 1) {
 digitalWrite(motorLPin1, LOW);
 digitalWrite(motorLPin2, LOW);
 digitalWrite(motorLPin3, HIGH);
 digitalWrite(motorLPin4, HIGH);
 
 digitalWrite(motorRPin1, HIGH);
 digitalWrite(motorRPin2, LOW);
 digitalWrite(motorRPin3, LOW);
 digitalWrite(motorRPin4, HIGH);
 
 delay(delayTime);
 
 digitalWrite(motorLPin1, LOW);
 digitalWrite(motorLPin2, HIGH);
 digitalWrite(motorLPin3, HIGH);
 digitalWrite(motorLPin4, LOW);
 
 digitalWrite(motorRPin1, HIGH);
 digitalWrite(motorRPin2, HIGH);
 digitalWrite(motorRPin3, LOW);
 digitalWrite(motorRPin4, LOW);
 
 delay(delayTime);
 
 digitalWrite(motorLPin1, HIGH);
 digitalWrite(motorLPin2, HIGH);
 digitalWrite(motorLPin3, LOW);
 digitalWrite(motorLPin4, LOW);
 
 digitalWrite(motorRPin1, LOW);
 digitalWrite(motorRPin2, HIGH);
 digitalWrite(motorRPin3, HIGH);
 digitalWrite(motorRPin4, LOW);
 
 delay(delayTime);
 
 digitalWrite(motorLPin1, HIGH);
 digitalWrite(motorLPin2, LOW);
 digitalWrite(motorLPin3, LOW);
 digitalWrite(motorLPin4, HIGH);
 
 digitalWrite(motorRPin1, LOW);
 digitalWrite(motorRPin2, LOW);
 digitalWrite(motorRPin3, HIGH);
 digitalWrite(motorRPin4, HIGH);
 
 delay(delayTime);
 }
}
 
// The clockwise function
void clockwise() {
 if (robotDirection == 2) {
 digitalWrite(motorLPin1, HIGH);
 digitalWrite(motorLPin2, LOW);
 digitalWrite(motorLPin3, LOW);
 digitalWrite(motorLPin4, HIGH);
 
 digitalWrite(motorRPin1, HIGH);
 digitalWrite(motorRPin2, LOW);
 digitalWrite(motorRPin3, LOW);
 digitalWrite(motorRPin4, HIGH);
 
 delay(delayTime);
 
 digitalWrite(motorLPin1, HIGH);
 digitalWrite(motorLPin2, HIGH);
 digitalWrite(motorLPin3, LOW);
 digitalWrite(motorLPin4, LOW);
 
 digitalWrite(motorRPin1, HIGH);
 digitalWrite(motorRPin2, HIGH);
 digitalWrite(motorRPin3, LOW);
 digitalWrite(motorRPin4, LOW);
 
 delay(delayTime);
 
 digitalWrite(motorLPin1, LOW);
 digitalWrite(motorLPin2, HIGH);
 digitalWrite(motorLPin3, HIGH);
 digitalWrite(motorLPin4, LOW);
 
 digitalWrite(motorRPin1, LOW);
 digitalWrite(motorRPin2, HIGH);
 digitalWrite(motorRPin3, HIGH);
 digitalWrite(motorRPin4, LOW);
 
 delay(delayTime);
 
 digitalWrite(motorLPin1, LOW);
 digitalWrite(motorLPin2, LOW);
 digitalWrite(motorLPin3, HIGH);
 digitalWrite(motorLPin4, HIGH);
 
 digitalWrite(motorRPin1, LOW);
 digitalWrite(motorRPin2, LOW);
 digitalWrite(motorRPin3, HIGH);
 digitalWrite(motorRPin4, HIGH);
 
 delay(delayTime);
 
 }
}
 
// The anticlockwise function
void anticlockwise() {
 if (robotDirection == 3) {
 digitalWrite(motorLPin1, LOW);
 digitalWrite(motorLPin2, LOW);
 digitalWrite(motorLPin3, HIGH);
 digitalWrite(motorLPin4, HIGH);
 
 digitalWrite(motorRPin1, LOW);
 digitalWrite(motorRPin2, LOW);
 digitalWrite(motorRPin3, HIGH);
 digitalWrite(motorRPin4, HIGH);
 
 delay(delayTime);
 
 digitalWrite(motorLPin1, LOW);
 digitalWrite(motorLPin2, HIGH);
 digitalWrite(motorLPin3, HIGH);
 digitalWrite(motorLPin4, LOW);
 
 digitalWrite(motorRPin1, LOW);
 digitalWrite(motorRPin2, HIGH);
 digitalWrite(motorRPin3, HIGH);
 digitalWrite(motorRPin4, LOW);
 
 delay(delayTime);
 
 digitalWrite(motorLPin1, HIGH);
 digitalWrite(motorLPin2, HIGH);
 digitalWrite(motorLPin3, LOW);
 digitalWrite(motorLPin4, LOW);
 
 digitalWrite(motorRPin1, HIGH);
 digitalWrite(motorRPin2, HIGH);
 digitalWrite(motorRPin3, LOW);
 digitalWrite(motorRPin4, LOW);
 
 delay(delayTime);
 
 digitalWrite(motorLPin1, HIGH);
 digitalWrite(motorLPin2, LOW);
 digitalWrite(motorLPin3, LOW);
 digitalWrite(motorLPin4, HIGH);
 
 digitalWrite(motorRPin1, HIGH);
 digitalWrite(motorRPin2, LOW);
 digitalWrite(motorRPin3, LOW);
 digitalWrite(motorRPin4, HIGH);
 
 delay(delayTime);
 
 }
}
						

The code above is working with the breadboard Blu-Tacked onto the base.

Because the orientation of one of the motors is the opposite to the other, I had to modify the code so that the second motor ran anti-clockwise. I put all of this in it’s own function called ‘forward()’. The code above also shows the ‘reverse()’ function and the ability for the robot to turn.

Here is a video of the robot moving forward. I’ve powered the Arduino using my portable USB charger (sorry about the vertical video, but I think it kind of works for this video):


PCB Design

So far we had been using breadboards to test the individual components. We were introduced to Proteus 8.5. This is the piece of software that we were expected to design our PCBs for this project with. We had to make a shield that sits on top of the Arduino to enable connectivity with all of the components.

I designed the shield PCB and it was printed. Before designing the PCB, I was told that I could design the PCB to be double sided “if I was feeling brave”. I felt brave and designed the PCB to be double sided. As it turns out, my board was only printed single sided, due to the fact that it was just easier to do everyone's singles sided, because I was the only person that "felt brave". This was slightly irritating, but luckily I’d only put the bare minimum on the second side, meaning there would only be a few bodge wires.

Here is a screenshot of my PBC in Proteus 8:

Here is a picture of the PCB (ignore the “51”, that is just a number to help the technician identify everyone’s PCBs):

It turns out I made a mistake while designing my PCB, I ended up connecting the 5V and GND rails together. Other than that, the motor drive side of things all works as expected! The pictures below show the fully soldered PCB:

This video shows the robot moving around with it’s shiny new PCB shield (again, sorry about the vertical video, but I think it kind of works for this video):


Power Supply

We used a Poundland battery bank to power it, as it supplies a pretty stable 5V line and it is rechargeable. Much better than the 3x AA battery pack we were provided, which we were expected to connect to the 5V line, even though 3x AA batteries provide an unregulated nominal voltage of 4.5V. This can cause inconsistencies when using the analogue input as the 5V rail is used as a reference for the analogue to digital converter. Something that I saw a lot of people had problems with.

LCD Display

The next thing to get working was the LCD display. Having the LCD working would make it easier to get the sensors working, as we would have a way of seeing the sensor outputs. This was done simply by using the LiquidCrystal_I2C Arduino library.




#include <Wire.h>;
#include <LiquidCrystal_I2C.h>;
 
// Set the LCD address to 0x27 for a 16 chars and 4 line display
LiquidCrystal_I2C lcd(0x27, 20, 4);
 
void setup()
{
 // initialize the LCD
 lcd.begin();
 
 // Turn on the blacklight and print a message.
 lcd.backlight();
 lcd.setCursor(0, 0);
 lcd.print("Line 1");
 lcd.setCursor(0, 1);
 lcd.print("Line 2");
 lcd.setCursor(0, 2);
 lcd.print("Line 3");
 lcd.setCursor(0, 3);
 lcd.print("Line 4");
}
 
void loop()
{
 // Do nothing here...
}
				

Light Sensor

The light sensor used was just a simple light dependant resistor (LDR). I was connected between an analogue input pin and 5V. As we had no way of "calibrating" the light level in to lumens (the SI derived unit of luminous flux), we simply displayed the raw sensor voltage on the LCD.

To prevent the LCD very rapidly refrshing the fluctuating LDR value, a delay was used to limit the refresh rate to 500ms.

The code for my LDR implementation is as follows. You cannot simply copy and paste this code in to the Arduino IDE and have it work. Look up how to get this working yourself. The code is based off of the Blink Without Delay example on arduino.cc:




unsigned long currentMillis = millis();
  if (currentMillis - LDRpreviousMillis >= LDRinterval) {
    LDRpreviousMillis = currentMillis;

    ldrValue = analogRead(ldrPin);
    lcd.setCursor(8, 3);
    lcd.print(char(3));
    lcd.print(" ");
    lcd.print(ldrValue);
  }
				

Temperature/Humidity Sensor

To retrieve ambient temperature and humidity readings, we used a DHT11 module. They're not the most accurate sensors, but they are cheap and I had a couple spare lying in my parts bin.

They can be connected using either the analogue pins or digital pins. I always go with using the analogue pins, because I have working code for that in my Aduino IDE Sketchbook.

Below is the code I alway use to get started with DHT11 usage in any of my projects. You will see that I did not create the code. It was made by Nick Athanasoulas on arduino.cc's forum. Also note that it uses a similar method of working without using the standard delay function, while still delaying.



						
/*  DHT11 Example Sketch Code for reading the sensor without delay on your program!
Example Code by: Nick Athanasoulas
Date: 27/5/2012
FUNCTION: It reads the sensor every 2 seconds without delays.
The user can also use the temperature and humidity values directly as an integer 
and compare it to other values without making new arrays.
*/
#define DHT11_PIN 2          // ADC0  Define the ANALOG Pin connected to DHT11 Sensor
int temp1[3];                //Temp1, temp2, hum1 & hum2 are the final integer values that you are going to use in your program. 
int temp2[3];                // They update every 2 seconds.
int hum1[3];
int hum2[3];

byte read_dht11_dat()
{
 byte i = 0;
 byte result=0;
 for(i=0; i< 8; i++){

   while(!(PINC & _BV(DHT11_PIN)));  // wait for 50us
   delayMicroseconds(30);

   if(PINC & _BV(DHT11_PIN)) 
     result |=(1<<(7-i));
   while((PINC & _BV(DHT11_PIN)));  // wait '1' finish

 }
 return result;
}

long dht11delay_previousMillis = 0;        // will store last time LED was updated
long dht11delay_interval = 1000;           // dht11delay_interval at which to blink (milliseconds)

void setup()
{
 DDRC |= _BV(DHT11_PIN);
 PORTC |= _BV(DHT11_PIN);

 Serial.begin(9600);
Serial.println("DHT11 without delay");
Serial.println("Example code by: Nick Athanasoulas");
 Serial.println("Ready");
 delay(1000);
}

void loop()
{

 unsigned long dht11delay_currentMillis = millis();

 if(dht11delay_currentMillis - dht11delay_previousMillis > dht11delay_interval) {
   dht11delay_previousMillis = dht11delay_currentMillis;   
   byte dht11_dat[5];
   byte dht11_in;
   byte i;
   // start condition
   // 1. pull-down i/o pin from 18ms
   PORTC &= ~_BV(DHT11_PIN);
   delay(18);
   PORTC |= _BV(DHT11_PIN);
   delayMicroseconds(40);

   DDRC &= ~_BV(DHT11_PIN);
   delayMicroseconds(40);

   dht11_in = PINC & _BV(DHT11_PIN);

   if(dht11_in){
     Serial.println("dht11 start condition 1 not met");
     return;
   }
   delayMicroseconds(80);

   dht11_in = PINC & _BV(DHT11_PIN);

   if(!dht11_in){
     Serial.println("dht11 start condition 2 not met");
     return;
   }
   delayMicroseconds(80);
   // now ready for data reception
   for (i=0; i<5; i++)
     dht11_dat[i] = read_dht11_dat();

   DDRC |= _BV(DHT11_PIN);
   PORTC |= _BV(DHT11_PIN);

   byte dht11_check_sum = dht11_dat[0]+dht11_dat[1]+dht11_dat[2]+dht11_dat[3];
   // check check_sum
   if(dht11_dat[4]!= dht11_check_sum)
   {
     Serial.println("DHT11 checksum error");
   }

   temp1[0]=dht11_dat[2];
   temp2[0]=dht11_dat[3];
   Serial.print("Temperature: ");
   Serial.print(temp1[0]);
   Serial.print(".");
   Serial.print(temp2[0]);
   Serial.print(" C");
   Serial.print("    ");
   hum1[0]=dht11_dat[0];
   hum2[0]=dht11_dat[1];
   Serial.print("Humidity: ");
   Serial.print(hum1[0]);
   Serial.print(".");
   Serial.print(hum2[0]);
   Serial.println("%");

 }
}
				

Ultrasonic Sensors

For reading the values off of the ultrasonic sensors, I used the NewPing library by Tim Eckel. This library was used instead of method in the standard Ping example, to eliminate timing issues caused by the code "waiting" for a ping to bounce back to the sensor. This library is also well equipped for dealing with multiple ultrasonic sensors.




#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <NewPing.h>

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address

#define SONAR_NUM     2 // Number or sensors.
#define MAX_DISTANCE 200 // Maximum distance (in cm) to ping.
#define PING_INTERVAL 33 // Milliseconds between sensor pings (29ms is about the min to avoid cross-sensor echo).

unsigned long pingTimer[SONAR_NUM]; // Holds the times when the next ping should happen for each sensor.
unsigned int cm[SONAR_NUM];         // Where the ping distances are stored.
uint8_t currentSensor = 0;          // Keeps track of which sensor is active.

NewPing sonar[SONAR_NUM] = {     // Sensor object array.
  NewPing(13, 12, MAX_DISTANCE), // Each sensor's trigger pin, echo pin, and max distance to ping.
  NewPing(1, 0, MAX_DISTANCE)
};

void setup() {
  lcd.begin(20, 4);
  pingTimer[0] = millis() + 75;           // First ping starts at 75ms, gives time for the Arduino to chill before starting.
  for (uint8_t i = 1; i < SONAR_NUM; i++) // Set the starting time for each sensor.
    pingTimer[i] = pingTimer[i - 1] + PING_INTERVAL;
}

void loop() {
  for (uint8_t i = 0; i < SONAR_NUM; i++) { // Loop through all the sensors.
    if (millis() >= pingTimer[i]) {         // Is it this sensor's time to ping?
      pingTimer[i] += PING_INTERVAL * SONAR_NUM;  // Set next time this sensor will be pinged.
      if (i == 0 && currentSensor == SONAR_NUM - 1) oneSensorCycle(); // Sensor ping cycle complete, do something with the results.
      sonar[currentSensor].timer_stop();          // Make sure previous timer is canceled before starting a new ping (insurance).
      currentSensor = i;                          // Sensor being accessed.
      cm[currentSensor] = 0;                      // Make distance zero in case there's no ping echo for this sensor.
      sonar[currentSensor].ping_timer(echoCheck); // Do the ping (processing continues, interrupt will call echoCheck to look for echo).
    }
  }
  // The rest of your code would go here.
}

void echoCheck() { // If ping received, set the sensor distance to array.
  if (sonar[currentSensor].check_timer())
    cm[currentSensor] = sonar[currentSensor].ping_result / US_ROUNDTRIP_CM;
}

void oneSensorCycle() { // Sensor ping cycle complete, do something with the results.
  for (uint8_t i = 0; i < SONAR_NUM; i++) {
    lcd.print(i);
    lcd.print("=");
    lcd.print(cm[i]);
    lcd.print("cm ");
  }
}
			

Final Comments

This was a very challenging project to work on. Many hours were put into completing this project; most of which could have been saved by realising that some of the problems that we encountered were hardware and not software.

  • One of the ultrasonic sensors was not functioning similarly to the other, so we had to swap that.
  • The connections to both ultrasonic sensors were not making adequate contact, causing juddery movement. This was solved by hot gluing the connectors.

Trying to incorporate so many different sub-systems within code was also a great challenge. Dealing with the timings of all of the sensors, while still running the stepper motors smoothly was a big learning curve.

Overall, this project allowed my to improve my understanding of how to program an Arduino and, required me to understand some of the more "advanced" features that the Arduino is capable of achieving.

Given the time, adding extra features to the robot would have been interesting. For example, a battery level indicator, preset movements (dance mode) and experementation with a gyroscope would have been fun to play with, with the intention of making the robot self-balancing.

Over 10 months on, the Arduino Robot can still be seen today. It lives on a shelf in Ian Watts' office, and it can be seen on display during the university open days occasionally.


If you have any questions about this project, or any others on my site get in touch!