Line Tracking with Sonar Obstacle Detection

Overview
I recently purchased and assembled a line tracking robot car. (If it helps, it was the Elegoo UNO Project Smart Robot Car Kit V 3.0 with UNO R3.) I'm not interested in controlling the car with a remote or with a phone app, I am only interested in programming the car to work autonomously. The kit comes with a CD with various demonstration code on it. (And you can download the latest modifications from their website.)

When I try the line tracking sketch provided by the company, the results are fantastic. I am able to track a line without any problem. Here is a short video of the robot car running the default code.

And here is the default code I'm referring to.

//Line Tracking IO define
#define LT_R !digitalRead(10)
#define LT_M !digitalRead(4)
#define LT_L !digitalRead(2)

#define ENB 5
#define IN1 7
#define IN2 8
#define IN3 9
#define IN4 11
#define ENA 6

#define carSpeed 200

void forward(){
  analogWrite(ENA, carSpeed);
  analogWrite(ENB, carSpeed);
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
}

void back(){
  analogWrite(ENA, carSpeed);
  analogWrite(ENB, carSpeed);
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW);
}

void left(){
  analogWrite(ENA, carSpeed);
  analogWrite(ENB, carSpeed);
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
}

void right(){
  analogWrite(ENA, carSpeed);
  analogWrite(ENB, carSpeed);
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW); 
} 

void stop(){
   digitalWrite(ENA, LOW);
   digitalWrite(ENB, LOW);
} 

void setup(){

}

void loop() {
  if(LT_M){
    forward();
  }
  else if(LT_R) {
    right();
    while(LT_R);
  }
  else if(LT_L) {
    left();
    while(LT_L);
  }
}

Getting to my main point and question
The default line tracking sketch does not make use of the Sonar Sensor on the front. As you can see from the code, the Sonar Sensor is nowhere to be found. I want to "simply" turn on the Sonar Sensor so that the car will stop if it detects an obstacle. (Eventually I want to do more, but my immediate short term milestone is to just have the car stop if there is an obstacle on the track.)

The changes I made to the default code are as minor as I can imagine them being while still having the functionality I want programmed in. However, when I add the Sonar into the equation, the car will occasionally do a 180 or 360 on the line rather than follow it, and worse, it will run off the track completely.

Here is another short video showing the car misbehaving once I've modified the code and added in Sonar awareness.

Here is the modified code.

#include <Servo.h>
Servo myservo;

//Line Tracking IO define
#define LT_R !digitalRead(10)
#define LT_M !digitalRead(4)
#define LT_L !digitalRead(2)

// Sonar define
#define Echo A4
#define Trig A5

// Servo define
#define SERVO_PIN 3

// Motor controller define
#define ENB 5
#define IN1 7
#define IN2 8
#define IN3 9
#define IN4 11
#define ENA 6

#define carSpeed 200

int sonarDistance = 0;

const int distanceLimit = 25;

void forward(){
  analogWrite(ENA, carSpeed);
  analogWrite(ENB, carSpeed);
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
}

void back(){
  analogWrite(ENA, carSpeed);
  analogWrite(ENB, carSpeed);
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW);
}

void left(){
  analogWrite(ENA, carSpeed);
  analogWrite(ENB, carSpeed);
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
}

void right(){
  analogWrite(ENA, carSpeed);
  analogWrite(ENB, carSpeed);
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW); 
} 

void stop(){
   digitalWrite(ENA, LOW);
   digitalWrite(ENB, LOW);
} 

//Ultrasonic distance measurement function
int getDistance() {
    digitalWrite(Trig, LOW);
    delayMicroseconds(2);
    digitalWrite(Trig, HIGH);
    delayMicroseconds(10);
    digitalWrite(Trig, LOW);
    return (int)pulseIn(Echo, HIGH) / 58;
}

void setup(){
  myservo.attach(SERVO_PIN);
  pinMode(Echo, INPUT);
  pinMode(Trig, OUTPUT);
  stop();
  myservo.write(90);
}

void loop() {
  sonarDistance = getDistance();
  if(sonarDistance <= distanceLimit && !(sonarDistance < 0)) {

    // An object is blocking our path
    stop();
  } else {
    if(LT_M){
      forward();
    }
    else if(LT_R) {
      right();
      while(LT_R);
    }
    else if(LT_L) {
      left();
      while(LT_L);
    }
  }
}

I added awareness of the servo so that the sonar would center itself during setup(). Otherwise, the servo is not used.

In loop(), I've just added a couple of new lines to:

  • Get sonar reading
  • if object is detected, stop
  • else, carry on with the default code

My guess is that it's an issue of timing. In the time it takes for getDistance() to take a reading, the car is speeding past the line.

To be clear, the sonar definitely works as can be seen in this short video clip.

If anyone has any ideas how to help, I would greatly appreciate it.

How can I have the line tracking work as flawlessly as it does in the first example while still having the Sonar Sensor enabled so that the car can stop if an object is placed on the track?

I would start by printing out the value returned by your sonar sensor every time through the loop. I'm guessing your are getting some odd values in there once in a while....

blh64:
I would start by printing out the value returned by your sonar sensor every time through the loop. I'm guessing your are getting some odd values in there once in a while....

Well, I think the sonar sensor values are fine. I have checked them, but I can check them again. I already have a check to ensure the value is not negative, which I've seen happen when the sonar "sees" a reflective surface like glass. So the value is definitely positive or it's just ignored.

How can the range be negative?

Edit: like this. Oops.return (int)pulseIn(Echo, HIGH) / 58;

@blixel

Please DO NOT CROSS POST.
Other post deleted

Take a few minutes to READ THIS POST.

Bob.

After many hours of researching this problem, I finally figured out what was going on, so I thought I would come back and update this post in case anyone runs into this in the future.

In short, the solution ended up being that I had to use NewPing.h for the Sonar Sensor. I was not aware of this library before, and was just trying to use adaptations of the example code that Elegoo shipped with the product. Using the Sonar Sensor in the way I was using it was causing a blocking problem. (As I mentioned in my original post, I was pretty sure it was a timing issue.)

There's more I want to do with this, such as having the car figure out how to go around the obstacle, but for now, I'm relieved to have resolved this problem.

At any rate, here's a quick video showing the car working as desired.

And here's the version of the code I was using in the video.

boolean DEBUG = false;

// Library includes
#include <Servo.h>
#include <NewPing.h>

// Line Tracking IO setup
#define LT_R !digitalRead(10)
#define LT_M !digitalRead(4)
#define LT_L !digitalRead(2)

// Sonar setup
#define ECHO_PIN     A4
#define TRIGGER_PIN  A5
#define MAX_DISTANCE 100
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
float sonarDistance;
const int distanceLimit = 25;
boolean obstacleDetected = false;

// Servo setup
Servo myservo;
#define SERVO_PIN 3

// Motor controller define
#define ENB 5
#define IN1 7
#define IN2 8
#define IN3 9
#define IN4 11
#define ENA 6

// Speed setup
int carSpeed  = 150;
int turnSpeed = 200;

void forward(){
  analogWrite(ENA, carSpeed);
  analogWrite(ENB, carSpeed);
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
}

void back(){
  analogWrite(ENA, carSpeed);
  analogWrite(ENB, carSpeed);
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW);
}

void left(){
  analogWrite(ENA, turnSpeed);
  analogWrite(ENB, turnSpeed);
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
}

void right(){
  analogWrite(ENA, turnSpeed);
  analogWrite(ENB, turnSpeed);
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW); 
} 

void stop(){
   digitalWrite(ENA, LOW);
   digitalWrite(ENB, LOW);
} 

void back_track() {

  // Slow the car down until we find the line
  carSpeed -= 50;
  
  back();
  if (LT_M || LT_R || LT_L) {
    stop();
    obstacleDetected = false;
  }

  // Found the line, bring the carSpeed back up to normal
  carSpeed += 50;
}

void setup(){
  myservo.attach(SERVO_PIN);
  pinMode(ECHO_PIN, INPUT);
  pinMode(TRIGGER_PIN, OUTPUT);
  stop();
  myservo.write(90);
  if (DEBUG) Serial.begin(9600);
}

void loop() {

  sonarDistance = sonar.ping_cm();
  
  if (DEBUG) Serial.println(sonarDistance);

  if((sonarDistance <= distanceLimit) && (sonarDistance >= 1)) {

    // An object is blocking our path
    if (DEBUG) Serial.println("Object detected. Calling stop()");
    obstacleDetected = true;
    stop();
  } 

  // The path is clear
  else {

    if(LT_M) {
      forward();
    }
    else if(LT_R) {
      right();
      while(LT_R);
    }
    else if(LT_L) {
      left();
      while(LT_L);
    }

    // None of the sensors see the line right now
    else {
      if (obstacleDetected) {
        back_track();
      }
    }
  }
}