Difference between revisions of "Arduino Bluetooth Controlled Multi-modal Robotic Car (Android and iOS Compatible)"

From ArduinoInfo
Jump to: navigation, search
m
Line 204: Line 204:
  
 
== Using the Mobile Application ==
 
== Using the Mobile Application ==
In order to control the robot car, a Mobile App has been designed with bluetooth capabilities to be able able to interface with the car via its HM10 BLE 4.0 module. BLE 4.0 protocol was chosen as it is compatible with both Android and iOS devices, and it consumes low energy.
+
In order to control the robot car, a Mobile App has been designed with bluetooth capabilities to be able able to interface with the car via its HM10 BLE 4.0 module. BLE 4.0  
 +
protocol was chosen as it is compatible with both Android and iOS devices, and it consumes low energy.
 +
 
 +
= Code and Mobile Application =
 +
<syntaxhighlight>/* YourDuino / KUFFY EBONG  Robot Control
 +
  - WHAT IT DOES Controls a Keyes robot in multiple modes
 +
  - SEE the comments after "//" on each line below
 +
 
 +
  - V1.01 16 August 2019
 +
  - V1.01 18 September 2019: corrected autoDrive() function, added servo for ultrasonic, added lightFollower() function, added  edge detection
 +
  Questions: kuffykeys@gmail.com , terry@yourduino.com */
 +
 
 +
/*-----( Import needed libraries )-----*/
 +
#include <SimpleKalmanFilter.h>
 +
#include <Servo.h>
 +
 
 +
 
 +
/*-----( Declare Constants and Pin Numbers )-----*/
 +
//ultrasonic sensor parameters  PINS USED: 0,1,2,3,4,5,6,7,8,9,10,12,13,A0,A1,A2,A3,A4 remainder---->11,A5 for the 2 other photo sensors.... Not enough pins on UNO. That means we have to use just 1 photo sensor in front
 +
const byte trigPin = 9;
 +
const byte echoPin = 8;
 +
 
 +
const byte ldrPin = A4;
 +
 
 +
byte servoPin = 11;
 +
 
 +
byte thresholdDist = 35;
 +
 
 +
int buzzInterval = 600;
 +
 
 +
int lightThreshold = 600;
 +
 
 +
/*************positions of servo *****************/
 +
byte lookLeft = 175, lookRight = 30, lookAhead = 95;
 +
 
 +
/***********flags for the millis state  ********************/
 +
bool state1 = false, state2 = false, state3 = false, state4 = false, state5 = false, state6 = false;
 +
 
 +
 
 +
bool resetBuzz = true, reset1 = true, reset2 = true, reset3 = true, reset4 = true, reset5 = true, reset6 = true;
 +
 
 +
float dist1 = NULL;
 +
 
 +
 
 +
/*************maybe create flags for the state of the robots movement declare these variables when the autoDrive state is entered*******************/
 +
 
 +
unsigned long prevMillis1 = 0l, prevMillis2 = 0l, prevMillis3 = 0l, prevMillis4 = 0l, prevMillis5 = 0l, prevMillis6 = 0l, currentMillis = 0l;
 +
unsigned long prevBuzzMillis = 0l, currentBuzzMillis = 0l;
 +
 
 +
 
 +
int pauseInterval = 1000;
 +
 
 +
 
 +
const byte leftLine = A1;  // PhotoSensors in Line Follower
 +
const byte  middleLine = A2;
 +
const byte rightLine = A3;
 +
 
 +
const byte MA_dir = 4;  // Motor Control Pins Direction and PWM Speed
 +
const byte MB_dir = 7;
 +
const byte MA_spd = 5;
 +
const byte MB_spd = 6;
 +
 
 +
const byte buzzerPin = 13; //meant for obstacle alert
 +
 
 +
 
 +
/*-----( Declare objects )-----*/
 +
SimpleKalmanFilter simpleKalmanFilter(2, 2, 0.01);
 +
Servo servo;          //create servo object to control a servo, max = 8 servos
 +
 
 +
/*-----( Declare Variables )-----*/
 +
 
 +
float dist = 0.00;
 +
float obstacleThreshold = 35; //Threshold distance where the robot sees object in front of it as an obstacle
 +
float estimated_value = 0;
 +
 
 +
 
 +
/*------( flags to hold the different robot modes )-----*/
 +
 
 +
bool manMode = false;
 +
bool lineMode = false;
 +
bool autoMode = false;
 +
bool followMode = false;
 +
bool StopMode = false;
 +
bool frontMode = false;
 +
bool backMode = false;
 +
bool leftMode = false;
 +
bool rightMode = false;
 +
bool lightMode = false;
 +
 
 +
/*-----( variable to hold data from Serial )-----*/
 +
 
 +
char val = ' ';
 +
char val1 = ' ';
 +
 
 +
 
 +
 
 +
void setup()  /**************************** SETUP: RUNS ONCE *******************/
 +
{
 +
  pinMode(leftLine, INPUT);
 +
  pinMode(middleLine, INPUT);
 +
  pinMode(rightLine, INPUT);
 +
  pinMode(ldrPin, INPUT);
 +
  pinMode(buzzerPin, OUTPUT);
 +
  pinMode(servoPin, OUTPUT);
 +
  pinMode(trigPin, OUTPUT);
 +
  pinMode(echoPin, INPUT);
 +
  pinMode(MA_dir, OUTPUT);
 +
  pinMode(MB_dir, OUTPUT);
 +
  pinMode(MA_spd, OUTPUT);
 +
  pinMode(MB_spd, OUTPUT);
 +
 
 +
 
 +
  servo.attach(servoPin);
 +
  servo.write(95); //default central position
 +
 
 +
  Serial.begin(9600);
 +
 
 +
}//--(end setup )---
 +
 
 +
void loop()  /******************** LOOP: RUNS CONSTANTLY *******************/
 +
{
 +
  //  buzzer();
 +
  //  delay(10);
 +
  //  buzzer();
 +
  //  delay(500);
 +
  //  Serial.println(detectLight());
 +
  autoDrive();
 +
  if (Serial.available())
 +
  {
 +
    val = Serial.read();
 +
    switch (val) {
 +
 
 +
      case 'M':
 +
        manMode = true;
 +
        lineMode = false;
 +
        autoMode = false;
 +
        followMode = false;
 +
        lightMode = false;
 +
        servo.write(lookAhead);
 +
        break;
 +
 
 +
      case 'f':
 +
        manMode = false;
 +
        lineMode = false;
 +
        autoMode = false;
 +
        followMode = true;
 +
        lightMode = false;
 +
        servo.write(lookAhead);
 +
        break;
 +
 
 +
      case 'T':
 +
        manMode = false;
 +
        lineMode = true;
 +
        autoMode = false;
 +
        followMode = false;
 +
        lightMode = false;
 +
        servo.write(lookAhead);
 +
        break;
 +
 
 +
      case 't':
 +
        manMode = false;
 +
        lineMode = false;
 +
        autoMode = false;
 +
        followMode = false;
 +
        lightMode = true;
 +
        servo.write(lookAhead);
 +
        break;
 +
 
 +
      case 'A':
 +
        manMode = false;
 +
        lineMode = false;
 +
        autoMode = true;
 +
        followMode = false;
 +
        lightMode = false;
 +
 
 +
        dist1 = obstacle();
 +
        state1 = true;
 +
        state2 = false; state3 = false; state4 = false; state5 = false; state6 = false;
 +
        reset1 = true; reset2 = true; reset3 = true; reset4 = true; reset5 = true; reset6 = true;
 +
 
 +
      case 'S':
 +
        StopMode = true;
 +
        frontMode = false;
 +
        backMode = false;
 +
        leftMode = false;
 +
        rightMode = false;
 +
        lightMode = false;
 +
        break;
 +
 
 +
      case 'F':
 +
        StopMode = false;
 +
        frontMode = true;
 +
        backMode = false;
 +
        leftMode = false;
 +
        rightMode = false;
 +
        lightMode = false;
 +
        break;
 +
 
 +
      case 'B':
 +
        StopMode = false;
 +
        frontMode = false;
 +
        backMode = true;
 +
        leftMode = false;
 +
        rightMode = false;
 +
        lightMode = false;
 +
        break;
 +
 
 +
      case 'L':
 +
        StopMode = false;
 +
        frontMode = false;
 +
        backMode = false;
 +
        leftMode = true;
 +
        rightMode = false;
 +
        lightMode = false;
 +
        break;
 +
 
 +
      case 'R':
 +
        StopMode = false;
 +
        frontMode = false;
 +
        backMode = false;
 +
        leftMode = false;
 +
        rightMode = true;
 +
        lightMode = false;
 +
        break;
 +
 
 +
      default:
 +
        StopMode = true;
 +
        frontMode = false;
 +
        backMode = false;
 +
        leftMode = false;
 +
        rightMode = false;
 +
        lightMode = false;
 +
        servo.write(lookAhead);
 +
        break;
 +
    }
 +
  }// END If Serial Vaialable
 +
 
 +
  if (manMode)
 +
  {
 +
    if (StopMode) {
 +
      Stop();
 +
    }
 +
 
 +
    if (frontMode) {
 +
      front();
 +
    }
 +
 
 +
    if (backMode) {
 +
      back();
 +
    }
 +
 
 +
    if (leftMode) {
 +
      left();
 +
    }
 +
 
 +
    if (rightMode) {
 +
      right();
 +
    }
 +
  }// END If ManMode
 +
 
 +
  if (lineMode)
 +
  {
 +
    trackLine();
 +
  }
 +
 
 +
  if (autoMode) {
 +
    autoDrive();
 +
  }
 +
 
 +
  if (followMode) {
 +
    follower();
 +
  }
 +
 
 +
  if (lightMode) {
 +
    detectLight();
 +
  }
 +
 
 +
}//--(end main loop )---
 +
 
 +
 
 +
 
 +
/*-----( Declare User-written Functions )-----*/
 +
void front()
 +
{
 +
 
 +
  if (digitalRead(middleLine) == HIGH)
 +
  {
 +
    back();
 +
  }
 +
 
 +
  if (digitalRead(middleLine) == LOW)
 +
  {
 +
    digitalWrite(MA_dir, HIGH);
 +
    digitalWrite(MB_dir, HIGH);
 +
    analogWrite(MA_spd, 0);
 +
    analogWrite(MB_spd, 0);
 +
  }
 +
}
 +
 
 +
void back()
 +
{
 +
  digitalWrite(MA_dir, LOW);
 +
  digitalWrite(MB_dir, LOW);
 +
  analogWrite(MA_spd, 0);
 +
  analogWrite(MB_spd, 0);
 +
}
 +
 
 +
void Stop()
 +
{
 +
  digitalWrite(MA_dir, HIGH);
 +
  digitalWrite(MB_dir, HIGH);
 +
  analogWrite(MA_spd, 255);
 +
  analogWrite(MB_spd, 255);
 +
}
 +
 
 +
void left()
 +
{
 +
  digitalWrite(MA_dir, LOW);
 +
  digitalWrite(MB_dir, HIGH);
 +
  analogWrite(MA_spd, 80);
 +
  analogWrite(MB_spd, 80);
 +
}
 +
 
 +
void right()
 +
{
 +
  digitalWrite(MA_dir, HIGH);
 +
  digitalWrite(MB_dir, LOW);
 +
  analogWrite(MA_spd, 80);
 +
  analogWrite(MB_spd, 80);
 +
}
 +
 
 +
void trackLine()
 +
{
 +
  if (digitalRead(middleLine))
 +
  {
 +
    if (digitalRead(rightLine) && !(digitalRead(leftLine)))
 +
    {
 +
      front();
 +
    }
 +
    else if (!(digitalRead(rightLine)) && digitalRead(leftLine))
 +
    {
 +
      front();
 +
    }
 +
    else
 +
    {
 +
      front();
 +
    }
 +
  }
 +
  else
 +
  {
 +
    if (digitalRead(rightLine) && !(digitalRead(leftLine)))
 +
    {
 +
      left();
 +
    }
 +
    else if (!(digitalRead(rightLine)) && digitalRead(leftLine))
 +
    {
 +
      right();
 +
    }
 +
    else
 +
    {
 +
      Stop();
 +
    }
 +
  }
 +
}// END TrackLine
 +
 
 +
float obstacle()
 +
{
 +
  digitalWrite(trigPin, LOW);
 +
  delayMicroseconds(2);
 +
  digitalWrite(trigPin, HIGH);
 +
  delayMicroseconds(12);
 +
  digitalWrite(trigPin, LOW);
 +
  dist = pulseIn(echoPin, HIGH);
 +
  delayMicroseconds(10000);
 +
  dist = dist / 58;
 +
 
 +
  //come up with a filtering, smoothening algorithm here for ultrasonic
 +
  estimated_value = simpleKalmanFilter.updateEstimate(dist);
 +
 
 +
  //  Serial.println(estimated_value);
 +
  return estimated_value;
 +
 
 +
}//END obstacle
 +
 
 +
void autoDrive()
 +
{
 +
  if (state1 == true)
 +
  {
 +
    servo.write(lookAhead);
 +
    front();
 +
 
 +
    dist1 = obstacle();
 +
    state2 =  false; state3 = false; state4 = false; state5 = false; state6 =  false;
 +
 
 +
    //for debugging
 +
    Serial.println("looking ahead + moving forward + read dist1_______state1");
 +
  }
 +
 
 +
 
 +
  if (dist1 < thresholdDist && !state3 && !state4)
 +
  {
 +
    state1 = false; state2 =  true; state3 = false; state4 = false; state5 = false; state6 =  false;
 +
 
 +
    if (state2 == true && !state3 && !state4)
 +
    {
 +
      Stop();
 +
      servo.write(lookLeft);
 +
      unsigned long currentMillis2 = millis();
 +
      reset2 = false;
 +
      reset1 = true; reset3 = true; reset4 = true; reset5 = true; reset6 = true;
 +
      float dist2 = 0.0;;
 +
 
 +
 
 +
 
 +
      //for debugging
 +
      Serial.println ("Stop + lookLeft___________state2");
 +
 
 +
      if (currentMillis2 - prevMillis2 >= pauseInterval)
 +
      {
 +
        dist2 = obstacle();
 +
        state2 = false;
 +
        reset2 = true;
 +
 
 +
        //for debugging
 +
        Serial.println("read left distance + leave state 2-----------------------------------------------------(dist2)" + String(dist2));
 +
 
 +
        if (dist2 > thresholdDist && !state1)
 +
        {
 +
          state1 = false; state2 =  false; state3 = true; state4 = false; state5 = false; state6 =  false;
 +
        }
 +
 
 +
        if (dist2 < thresholdDist && !state5)
 +
        {
 +
          state1 = false; state2 =  false; state3 = false; state4 = true; state5 = false; state6 =  false;
 +
        }
 +
      }
 +
    }
 +
  }
 +
 
 +
 
 +
  if (state3 == true)
 +
  {
 +
    servo.write(lookAhead);
 +
    left();
 +
    unsigned long currentMillis3 = millis();
 +
    reset3 = false;
 +
    reset1 = true; reset2 = true; reset4 = true; reset5 = true; reset6 = true;
 +
 
 +
    //for debugging
 +
    Serial.println("lookAhead + move left __________state3");
 +
 
 +
    if (currentMillis3 - prevMillis3 >= pauseInterval)
 +
    {
 +
      state1 = true;
 +
      state2 =  false; state3 = false; state4 = false; state5 = false; state6 =  false;
 +
      reset1 = false; reset2 = true; reset3 = true; reset4 = true; reset5 = true; reset6 = true;
 +
 
 +
      //for debugging
 +
      Serial.println("leave state3 and proceed to state 1");
 +
    }
 +
  }
 +
 
 +
  if (state4 == true)
 +
  {
 +
    Stop();
 +
    servo.write(lookRight);
 +
    unsigned long currentMillis4 = millis();
 +
    reset4 = false;
 +
 
 +
    float dist3 = 0.0;
 +
 
 +
    //for debugging
 +
    Serial.println("Stop + lookRight __________state4");
 +
 
 +
    if (currentMillis4 - prevMillis4 >= pauseInterval)
 +
    {
 +
      dist3  = obstacle();
 +
      state4 = false;
 +
      reset4 = true;
 +
 
 +
      //for debugging
 +
      Serial.println("read right distance and left state 4-----------------------------------------------------(dist3)" + String(dist3));
 +
 
 +
      if (dist3 > thresholdDist && !state1)
 +
      {
 +
        state1 = false; state2 =  false; state3 = false; state4 = false; state5 = true; state6 =  false;
 +
      }
 +
 
 +
      if (dist3  < thresholdDist && !state1)
 +
      {
 +
        state1 = false; state2 =  false; state3 = false; state4 = false; state5 = false; state6 =  true;
 +
      }
 +
    }
 +
  }
 +
 
 +
 
 +
  if (state5 == true)
 +
  {
 +
    servo.write(lookAhead);
 +
    right();
 +
    unsigned long currentMillis5 = millis();
 +
    reset5 = false;
 +
 
 +
    //for debugging
 +
    Serial.println("lookAhead + move right____________________state5");
 +
 
 +
    if (currentMillis - prevMillis5 >= pauseInterval)
 +
    {
 +
      state5 = false;
 +
      reset5 = true;
 +
      state1 = true;
 +
 
 +
      //for debugging
 +
      Serial.println("left state 5 to state 1");
 +
    }
 +
  }
 +
 
 +
 
 +
  if (state6 == true)
 +
  {
 +
    back();
 +
    servo.write(lookAhead);
 +
    unsigned long currentMillis6 = millis();
 +
    reset6 = false;
 +
 
 +
    //for debugging
 +
    Serial.println("moveBackward + lookAhead  ___________________state6");
 +
 
 +
    if (currentMillis6 - prevMillis6 >= pauseInterval)
 +
    {
 +
      state6 = false;
 +
      reset6 = true;
 +
      state2 = true;
 +
 
 +
      //for debugging
 +
      Serial.println("left  state 6 to state 2 to scan again and see where to go");
 +
    }
 +
  }
 +
 
 +
 
 +
 
 +
  if (reset1)
 +
    prevMillis1 = millis();
 +
 
 +
  if (reset2)
 +
    prevMillis2 = millis();
 +
 
 +
  if (reset3)
 +
    prevMillis3 = millis();
 +
 
 +
  if (reset4)
 +
    prevMillis4 = millis();
 +
 
 +
  if (reset5)
 +
    prevMillis5 = millis();
 +
 
 +
  if (reset6)
 +
    prevMillis6 = millis();
 +
 
 +
 
 +
 
 +
 
 +
  else
 +
  {
 +
    state1 = true; state2 =  false; state3 = false; state4 = false; state5 = false; state6 =  false;
 +
    dist1 = obstacle();
 +
    Serial.println("the else statement has happened");
 +
  }
 +
 
 +
 
 +
  //for debugging
 +
  Serial.println("nothing happening here-----------------------------------------------------(dist1)" + String(dist1));
 +
}//END Autodrive
 +
 
 +
 
 +
void detectLight()
 +
{
 +
  int lightIntensity = analogRead(ldrPin);
 +
  currentBuzzMillis = millis();
 +
 
 +
  if (resetBuzz)
 +
  {
 +
    prevBuzzMillis = millis();
 +
  }
 +
 
 +
 
 +
  if (lightIntensity > lightThreshold)
 +
  {
 +
    front();
 +
    resetBuzz = false;
 +
 
 +
    if (currentBuzzMillis - prevBuzzMillis >= buzzInterval)
 +
    {
 +
      digitalWrite (buzzerPin, LOW);
 +
      resetBuzz = true;
 +
 
 +
      //for debugging
 +
      Serial.println("resetBUZZZZZ_________________________________________________________________________");
 +
    }
 +
 
 +
    else
 +
    {
 +
      digitalWrite (buzzerPin, HIGH);
 +
    }
 +
 
 +
    //for debugging
 +
    Serial.println("detected light ahead_____________________________________________________moving forward");
 +
  }
 +
 
 +
  else {
 +
    Stop();
 +
    digitalWrite (buzzerPin, LOW);
 +
 
 +
    //for debugging
 +
    Serial.println("Stopped because light is not up to threshold");
 +
  }
 +
}
 +
 
 +
 
 +
void follower()
 +
{
 +
  dist = obstacle();
 +
 
 +
  //come up with a filtering, smoothening algorithm here for ultrasonic
 +
  estimated_value = simpleKalmanFilter.updateEstimate(dist);
 +
 
 +
  if (estimated_value < 100 && estimated_value > 30) {
 +
    //This condition is to move forward the robot when the object is in the range of 100 to 30 centimeters.
 +
    front();
 +
  }
 +
 
 +
  if (estimated_value < 30 && estimated_value > 20) {
 +
    //This condition is to make the robot stable when the object is in the range of 20 to 30 centimeters.
 +
    Stop();
 +
  }
 +
 
 +
  if (estimated_value < 20 && estimated_value > 2) {
 +
    //This condition is to move backward the robot when the object is in the range of 2 to 20 centimeters.
 +
    back();
 +
  }
 +
 
 +
  if (estimated_value > 100) {
 +
    //This condition is to stop the robot when the object is in the out of range i.e greater than 100 centimeters.
 +
    Stop();
 +
  }
 +
}// END Follower
 +
 
 +
//*********( THE END )***********</syntaxhighlight>

Revision as of 12:24, 26 September 2019

Introduction

The Arduino micro-controller is capable of running only one sketch at a time. Therefore, it is generally thought that the Arduino can only perform one task or achieve one objective per-sketch. This project shows the implementation of a multi-modal bluetooth controlled robotic car, capable of achieving different tasks and objectives, and is controlled with an Android or iOS mobile application. Everything happens in real-time, with little to no delay.

Specifications

The communication with the robot is made possible via Serial-over-Bluetooth protocol. A bluetooth module is connected to the Arduino compatible board, and the mobile phone which the application has been installed on can connect to the bluetooth module.

This robot has been built to have 5 modes:

  1. Line-follower Mode
  2. Object-follower Mode
  3. Light-follower Mode
  4. Ramble Mode
  5. Manual-control Mode

Hardware Components and Setup

  1. HM10 Bluetooth Module x 1
  2. Yourduino RoboRed Board x 1
  3. IR Edge Detection/Line Tracker Sensor x 1
  4. Photo-resistor x 1
  5. Buzzer x 1
  6. Ultrasonic sensor x 1
  7. L9110S Motor driver x 1
  8. LM393 Opto-interrupters x 2
  9. Geared DC motors x 2
  10. Micro servo motor kit x 1
  11. Robot car chassis with 2 wheels x 1

Configuring the HM10 BLE 4 Module

The HM10 module was chosen for this project due to it's compatibility with the common BLE 4.0 (Bluetooth Low Energy) protocol, used on Apple Iphones as well as Android phones. Before the module can be used, the baudrate, device name, and preferred pin have to be set. Below are steps for setting up the HM10 Module using the RoboRED board:

  1. The schematic of Arduino shows that the RX and TX pins are connected to the USB Interface chip (on Arduino board as pin 0 and pin 1), which means that those pins can directly access the USB Interface chip itself. We generally use TX and RX pins for communication. Upload the empty sketch below to the RoboRED board in order to enable it serve as USB-TTL converter for communicating with the HM10 module:
    void setup() {
    }
    
    void loop() {
    }
    
  2. Disconnect the Arduino USB connection
  3. Connect HM10-Tx --> RoboRED-Tx(Pin1) and HM10-Rx --> RoboRED-Rx(Pin0) (i.e. Tx --> Tx and Rx --> Rx of each board), then HM10-Vcc --> RoboRED-5V line (any of the pins with a red base) on the RoboRED and HM10-GND --> RoboRED-GND (any of the pins with a blue base)
  4. Plug in the RoboRED's USB to the PC and open the Arduino IDE
  5. Open the Serial Monitor and set the baudrate to 9600
  6. Type AT and press enter. If the response is OK, then the module is ready to be configured using AT commands
  7. Type AT+VERR? command to check the firmware version that runs on your HM10 module. V5** and above is good
  8. Type AT+NAME? to see current name of the module, otherwise, type AT+NAMEpreferredName to set a preferred Bluetooth name for the module. Kindly note that there should be NO space between the AT+NAME and the preferredName chosen
  9. Next, set the passcode that would be required for connecting to the module; AT+PASSpreferredPasscode. The preferred passcode must be 6 numbers. To check the passcode, use AT+PASS?
  10. The HM10 module is configured to connect without passcode (i.e. AT+TYPE0). Set it to request a passcode on pairing by using this command: AT+TYPE2

The module is now configured and ready to be used. To save your settings, use AT+RESET in other to save and reset the module

Setting up the Edge Detection/Line Tracker Sensor

This Sensor is really a module which has an IR receiver and transmitter. IR Sensors work by using a specific light sensor to detect a select light wavelength in the Infra-Red (IR) spectrum. By using an LED which produces light at the same wavelength as what the sensor is looking for, you can look at the intensity of the received reflected light. When an object is close to the sensor, the light from the LED bounces (reflects) off the object and into the light sensor. This results in a voltage being sensed on the output pin of the module. As long as the sensor receives the reflected IR light, it sends a HIGH from the output of the IR module. When the Robot gets to an edge where the IR module no longer receives the reflected IR light, it sends a LOW from the output of the module. This is the principle used for edge detection. For line tracking, a beam of infrared light is transmitted from the IR transmitter of the module onto the object, then the receiver measures how much light is reflected back. A dark object bounces off less light than a bright object (i.e. dark objects absorb more light than bright objects). This is the fundamental principle behind a line tracking robot. Hence, in a line tracking mode, the IR sensor detects the difference between white (a surface) and black (a line). Since black reflects little or no light, the module sends a LOW signal off its output, and a HIGH output signal when it detects white. This is then used to adjust the speed of the motors to track a black line.

Setting up the Motor and Motor Driver

In order to get the desired functionality of the motors and motor drivers, it is necessary to know what pins of the motor drivers control what direction. This is a tricky, but easy process and the steps are outlined:

  • First, start with one motor (Motor A). Connect pin A-1A of the L9110 motor driver (or IN1 of L293N motor driver) to MA_dir pin on the Arduino and pin A-1B of the L9110 motor driver (or IN2 of L293N motor driver) to MA_spd pin on the Arduino which we have defined in the code.
    const byte MA_dir = 4;  // Motor Control Pins Direction and PWM Speed
    const byte MB_dir = 7;
    const byte MA_spd = 5;
    const byte MB_spd = 6;
    
    void setup()   /**************************** SETUP: RUNS ONCE *******************/
    {
      pinMode(MA_dir, OUTPUT);
      pinMode(MB_dir, OUTPUT);
      pinMode(MA_spd, OUTPUT);
      pinMode(MB_spd, OUTPUT);
    
    }//--(end setup )---
    
    void loop()   /******************** LOOP: RUNS CONSTANTLY *******************/
    {
      front(); // move the wheel in the forward direction
    }
    
    void front()
    {
      digitalWrite(MA_dir, HIGH);
      digitalWrite(MB_dir, HIGH);
      analogWrite(MA_spd, 0);
      analogWrite(MB_spd, 80);
    }
    
  • If Motor A moves in the desired front direction (i.e. in a clockwise direction if the front of the motor wheel is facing you), then motor one is all set; else, interchange the red and black wires of the motor that go into Motor A slots of the motor driver.
  • To configure Motor B, Connect pin B-1A of the L9110 motor driver (or IN3 of L293N motor driver) to MB_dir pin on the Arduino and pin B-1B of the L9110 motor driver (or IN4 of L293N motor driver) to MB_spd pin on the Arduino which we have defined in the code. If Motor B moves in the desired front direction (i.e. in an anti-clockwise direction if the front of the motor wheel is facing you), then motor one is all set; else, interchange the red and black wires of the motor that go into Motor B slots of the motor driver.

After confirming the front direction (which also confirms the back direction), it is important to also test the left direction (which would also confirm the right direction).

Upload this code to the Arduino. To turn the robot in the left direction, the left motor should rotate backwards (i.e. in a clockwise direction if the front of the motor wheel is facing you) while the right motor rotates forward (also in a clockwise direction if the front of the motor wheel is facing you). This is known as Differential Steering.
const byte MA_dir = 4;  // Motor Control Pins Direction and PWM Speed
const byte MB_dir = 7;
const byte MA_spd = 5;
const byte MB_spd = 6;

void setup()   /**************************** SETUP: RUNS ONCE *******************/
{
  pinMode(MA_dir, OUTPUT);
  pinMode(MB_dir, OUTPUT);
  pinMode(MA_spd, OUTPUT);
  pinMode(MB_spd, OUTPUT);

}//--(end setup )---

void loop()   /******************** LOOP: RUNS CONSTANTLY *******************/
{
  left(); // move the wheel in the forward direction
}

void left()
{
  digitalWrite(MA_dir, LOW);
  digitalWrite(MB_dir, HIGH);
  analogWrite(MA_spd, 80);
  analogWrite(MB_spd, 80);
}

If the Robot turns right after running this sketch in the Arduino, interchange the pins on motor A with the pins of motor B, exactly they way they are, i.e. MA_dir should now be MB_dir and vice versa, likewise MA_spd should now be MB_spd and vice versa.

NOTE: The speed of the motor increases as the PWM (analogWrite) value reduces. The range is from 0 - 255 (fastest to slowest speed)

Setting up the Servo

It is necessary to determine the actual angle of rotation of the servo motor each direction (left, right, and front) represents. Upload the code below to the Arduino and change the servo.write(<angle>) angle till the desired values for left, right and front positions of the servo which controls the ultrasonic sensor is achieved. The datatype of the variable should be a byte.

#include <Servo.h>      //Import the Servo library

Servo servo;          //create servo object to control a servo, max = 8 servos
byte servoPin = 11;

void setup()   /**************************** SETUP: RUNS ONCE *******************/
{
  pinMode(servoPin, OUTPUT);
  servo.attach(servoPin);
  servo.write(95); //change this value till you achieve the desired value for left, front, and right directions 

}//--(end setup )---

void loop()   /******************** LOOP: RUNS CONSTANTLY *******************/
{

}

Testing the Ultrasonic Range Sensor

To test if the ultrasonic range sensor has no faults, upload the code below into the Arduino and open the Serial Monitor at baudrate 9600 to see if you get correct values. Try to place an object in front of the sensor and vary the distance to see if there is a corresponding change in the values displayed on the Serial Monitor.

/*-----( Import needed library )-----*/
#include <SimpleKalmanFilter.h>


/*-----( Declare Constants and Pin Numbers )-----*/
//ultrasonic sensor parameters
const byte trigPin = 9;
const byte echoPin = 8;

SimpleKalmanFilter simpleKalmanFilter(2, 2, 0.01);        //create an object for the simpleKalmanFilter library

float dist = 0.00;
float estimated_value = 0.00;


void setup()   /**************************** SETUP: RUNS ONCE *******************/
{
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  Serial.begin(9600);

}//--(end setup )---


void loop()   /******************** LOOP: RUNS CONSTANTLY *******************/
{
  Serial.println(obstacle());
}


float obstacle()
{
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(12);
  digitalWrite(trigPin, LOW);
  dist = pulseIn(echoPin, HIGH);
  delayMicroseconds(10000);
  dist = dist / 58;
  
  //smoothing of ultrasonic sensor readings
  estimated_value = simpleKalmanFilter.updateEstimate(dist);

  return estimated_value;

}//END obstacle

Testing the Light Dependent Resistor (LDR)

This sketch is use to confirm that the ultrasonic range sensor is working fine. Use a torch light and vary its distance from the LDR to see the different readings on the Serial Monitor. This can also be used to set a desired lightThreshold in the main code.
const byte ldrPin = A4;

void setup()   /**************************** SETUP: RUNS ONCE *******************/
{
  pinMode(ldrPin, INPUT);

  Serial.begin(9600);

}//--(end setup )---

void loop()   /******************** LOOP: RUNS CONSTANTLY *******************/
{
  // display light  intensity values on the Serial Monitor 
  Serial.println(analogRead(ldrPin));
}

Using the Mobile Application

In order to control the robot car, a Mobile App has been designed with bluetooth capabilities to be able able to interface with the car via its HM10 BLE 4.0 module. BLE 4.0 protocol was chosen as it is compatible with both Android and iOS devices, and it consumes low energy.

Code and Mobile Application

/* YourDuino / KUFFY EBONG  Robot Control
  - WHAT IT DOES Controls a Keyes robot in multiple modes
  - SEE the comments after "//" on each line below

  - V1.01 16 August 2019
  - V1.01 18 September 2019: corrected autoDrive() function, added servo for ultrasonic, added lightFollower() function, added  edge detection
   Questions: kuffykeys@gmail.com , terry@yourduino.com */

/*-----( Import needed libraries )-----*/
#include <SimpleKalmanFilter.h>
#include <Servo.h>


/*-----( Declare Constants and Pin Numbers )-----*/
//ultrasonic sensor parameters  PINS USED: 0,1,2,3,4,5,6,7,8,9,10,12,13,A0,A1,A2,A3,A4 remainder---->11,A5 for the 2 other photo sensors.... Not enough pins on UNO. That means we have to use just 1 photo sensor in front
const byte trigPin = 9;
const byte echoPin = 8;

const byte ldrPin = A4;

byte servoPin = 11;

byte thresholdDist = 35;

int buzzInterval = 600;

int lightThreshold = 600;

/*************positions of servo *****************/
byte lookLeft = 175, lookRight = 30, lookAhead = 95;

/***********flags for the millis state  ********************/
bool state1 = false, state2 = false, state3 = false, state4 = false, state5 = false, state6 = false;


bool resetBuzz = true, reset1 = true, reset2 = true, reset3 = true, reset4 = true, reset5 = true, reset6 = true;

float dist1 = NULL;


/*************maybe create flags for the state of the robots movement declare these variables when the autoDrive state is entered*******************/

unsigned long prevMillis1 = 0l, prevMillis2 = 0l, prevMillis3 = 0l, prevMillis4 = 0l, prevMillis5 = 0l, prevMillis6 = 0l, currentMillis = 0l;
unsigned long prevBuzzMillis = 0l, currentBuzzMillis = 0l;


int pauseInterval = 1000;


const byte leftLine = A1;   // PhotoSensors in Line Follower
const byte  middleLine = A2;
const byte rightLine = A3;

const byte MA_dir = 4;  // Motor Control Pins Direction and PWM Speed
const byte MB_dir = 7;
const byte MA_spd = 5;
const byte MB_spd = 6;

const byte buzzerPin = 13; //meant for obstacle alert


/*-----( Declare objects )-----*/
SimpleKalmanFilter simpleKalmanFilter(2, 2, 0.01);
Servo servo;          //create servo object to control a servo, max = 8 servos

/*-----( Declare Variables )-----*/

float dist = 0.00;
float obstacleThreshold = 35; //Threshold distance where the robot sees object in front of it as an obstacle
float estimated_value = 0;


/*------( flags to hold the different robot modes )-----*/

bool manMode = false;
bool lineMode = false;
bool autoMode = false;
bool followMode = false;
bool StopMode = false;
bool frontMode = false;
bool backMode = false;
bool leftMode = false;
bool rightMode = false;
bool lightMode = false;

/*-----( variable to hold data from Serial )-----*/

char val = ' ';
char val1 = ' ';



void setup()   /**************************** SETUP: RUNS ONCE *******************/
{
  pinMode(leftLine, INPUT);
  pinMode(middleLine, INPUT);
  pinMode(rightLine, INPUT);
  pinMode(ldrPin, INPUT);
  pinMode(buzzerPin, OUTPUT);
  pinMode(servoPin, OUTPUT);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  pinMode(MA_dir, OUTPUT);
  pinMode(MB_dir, OUTPUT);
  pinMode(MA_spd, OUTPUT);
  pinMode(MB_spd, OUTPUT);


  servo.attach(servoPin);
  servo.write(95); //default central position

  Serial.begin(9600);

}//--(end setup )---

void loop()   /******************** LOOP: RUNS CONSTANTLY *******************/
{
  //  buzzer();
  //  delay(10);
  //  buzzer();
  //  delay(500);
  //  Serial.println(detectLight());
  autoDrive();
  if (Serial.available())
  {
    val = Serial.read();
    switch (val) {

      case 'M':
        manMode = true;
        lineMode = false;
        autoMode = false;
        followMode = false;
        lightMode = false;
        servo.write(lookAhead);
        break;

      case 'f':
        manMode = false;
        lineMode = false;
        autoMode = false;
        followMode = true;
        lightMode = false;
        servo.write(lookAhead);
        break;

      case 'T':
        manMode = false;
        lineMode = true;
        autoMode = false;
        followMode = false;
        lightMode = false;
        servo.write(lookAhead);
        break;

      case 't':
        manMode = false;
        lineMode = false;
        autoMode = false;
        followMode = false;
        lightMode = true;
        servo.write(lookAhead);
        break;

      case 'A':
        manMode = false;
        lineMode = false;
        autoMode = true;
        followMode = false;
        lightMode = false;

        dist1 = obstacle();
        state1 = true;
        state2 = false; state3 = false; state4 = false; state5 = false; state6 = false;
        reset1 = true; reset2 = true; reset3 = true; reset4 = true; reset5 = true; reset6 = true;

      case 'S':
        StopMode = true;
        frontMode = false;
        backMode = false;
        leftMode = false;
        rightMode = false;
        lightMode = false;
        break;

      case 'F':
        StopMode = false;
        frontMode = true;
        backMode = false;
        leftMode = false;
        rightMode = false;
        lightMode = false;
        break;

      case 'B':
        StopMode = false;
        frontMode = false;
        backMode = true;
        leftMode = false;
        rightMode = false;
        lightMode = false;
        break;

      case 'L':
        StopMode = false;
        frontMode = false;
        backMode = false;
        leftMode = true;
        rightMode = false;
        lightMode = false;
        break;

      case 'R':
        StopMode = false;
        frontMode = false;
        backMode = false;
        leftMode = false;
        rightMode = true;
        lightMode = false;
        break;

      default:
        StopMode = true;
        frontMode = false;
        backMode = false;
        leftMode = false;
        rightMode = false;
        lightMode = false;
        servo.write(lookAhead);
        break;
    }
  }// END If Serial Vaialable

  if (manMode)
  {
    if (StopMode) {
      Stop();
    }

    if (frontMode) {
      front();
    }

    if (backMode) {
      back();
    }

    if (leftMode) {
      left();
    }

    if (rightMode) {
      right();
    }
  }// END If ManMode

  if (lineMode)
  {
    trackLine();
  }

  if (autoMode) {
    autoDrive();
  }

  if (followMode) {
    follower();
  }

  if (lightMode) {
    detectLight();
  }

}//--(end main loop )---



/*-----( Declare User-written Functions )-----*/
void front()
{

  if (digitalRead(middleLine) == HIGH)
  {
    back();
  }
  
  if (digitalRead(middleLine) == LOW)
  {
    digitalWrite(MA_dir, HIGH);
    digitalWrite(MB_dir, HIGH);
    analogWrite(MA_spd, 0);
    analogWrite(MB_spd, 0);
  }
}

void back()
{
  digitalWrite(MA_dir, LOW);
  digitalWrite(MB_dir, LOW);
  analogWrite(MA_spd, 0);
  analogWrite(MB_spd, 0);
}

void Stop()
{
  digitalWrite(MA_dir, HIGH);
  digitalWrite(MB_dir, HIGH);
  analogWrite(MA_spd, 255);
  analogWrite(MB_spd, 255);
}

void left()
{
  digitalWrite(MA_dir, LOW);
  digitalWrite(MB_dir, HIGH);
  analogWrite(MA_spd, 80);
  analogWrite(MB_spd, 80);
}

void right()
{
  digitalWrite(MA_dir, HIGH);
  digitalWrite(MB_dir, LOW);
  analogWrite(MA_spd, 80);
  analogWrite(MB_spd, 80);
}

void trackLine()
{
  if (digitalRead(middleLine))
  {
    if (digitalRead(rightLine) && !(digitalRead(leftLine)))
    {
      front();
    }
    else if (!(digitalRead(rightLine)) && digitalRead(leftLine))
    {
      front();
    }
    else
    {
      front();
    }
  }
  else
  {
    if (digitalRead(rightLine) && !(digitalRead(leftLine)))
    {
      left();
    }
    else if (!(digitalRead(rightLine)) && digitalRead(leftLine))
    {
      right();
    }
    else
    {
      Stop();
    }
  }
}// END TrackLine

float obstacle()
{
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(12);
  digitalWrite(trigPin, LOW);
  dist = pulseIn(echoPin, HIGH);
  delayMicroseconds(10000);
  dist = dist / 58;

  //come up with a filtering, smoothening algorithm here for ultrasonic
  estimated_value = simpleKalmanFilter.updateEstimate(dist);

  //  Serial.println(estimated_value);
  return estimated_value;

}//END obstacle

void autoDrive()
{
  if (state1 == true)
  {
    servo.write(lookAhead);
    front();

    dist1 = obstacle();
    state2 =  false; state3 = false; state4 = false; state5 = false; state6 =  false;

    //for debugging
    Serial.println("looking ahead + moving forward + read dist1_______state1");
  }


  if (dist1 < thresholdDist && !state3 && !state4)
  {
    state1 = false; state2 =  true; state3 = false; state4 = false; state5 = false; state6 =  false;

    if (state2 == true && !state3 && !state4)
    {
      Stop();
      servo.write(lookLeft);
      unsigned long currentMillis2 = millis();
      reset2 = false;
      reset1 = true; reset3 = true; reset4 = true; reset5 = true; reset6 = true;
      float dist2 = 0.0;;



      //for debugging
      Serial.println ("Stop + lookLeft___________state2");

      if (currentMillis2 - prevMillis2 >= pauseInterval)
      {
        dist2 = obstacle();
        state2 = false;
        reset2 = true;

        //for debugging
        Serial.println("read left distance + leave state 2-----------------------------------------------------(dist2)" + String(dist2));

        if (dist2 > thresholdDist && !state1)
        {
          state1 = false; state2 =  false; state3 = true; state4 = false; state5 = false; state6 =  false;
        }

        if (dist2 < thresholdDist && !state5)
        {
          state1 = false; state2 =  false; state3 = false; state4 = true; state5 = false; state6 =  false;
        }
      }
    }
  }


  if (state3 == true)
  {
    servo.write(lookAhead);
    left();
    unsigned long currentMillis3 = millis();
    reset3 = false;
    reset1 = true; reset2 = true; reset4 = true; reset5 = true; reset6 = true;

    //for debugging
    Serial.println("lookAhead + move left __________state3");

    if (currentMillis3 - prevMillis3 >= pauseInterval)
    {
      state1 = true;
      state2 =  false; state3 = false; state4 = false; state5 = false; state6 =  false;
      reset1 = false; reset2 = true; reset3 = true; reset4 = true; reset5 = true; reset6 = true;

      //for debugging
      Serial.println("leave state3 and proceed to state 1");
    }
  }

  if (state4 == true)
  {
    Stop();
    servo.write(lookRight);
    unsigned long currentMillis4 = millis();
    reset4 = false;

    float dist3 = 0.0;

    //for debugging
    Serial.println("Stop + lookRight __________state4");

    if (currentMillis4 - prevMillis4 >= pauseInterval)
    {
      dist3  = obstacle();
      state4 = false;
      reset4 = true;

      //for debugging
      Serial.println("read right distance and left state 4-----------------------------------------------------(dist3)" + String(dist3));

      if (dist3 > thresholdDist && !state1)
      {
        state1 = false; state2 =  false; state3 = false; state4 = false; state5 = true; state6 =  false;
      }

      if (dist3  < thresholdDist && !state1)
      {
        state1 = false; state2 =  false; state3 = false; state4 = false; state5 = false; state6 =  true;
      }
    }
  }


  if (state5 == true)
  {
    servo.write(lookAhead);
    right();
    unsigned long currentMillis5 = millis();
    reset5 = false;

    //for debugging
    Serial.println("lookAhead + move right____________________state5");

    if (currentMillis - prevMillis5 >= pauseInterval)
    {
      state5 = false;
      reset5 = true;
      state1 = true;

      //for debugging
      Serial.println("left state 5 to state 1");
    }
  }


  if (state6 == true)
  {
    back();
    servo.write(lookAhead);
    unsigned long currentMillis6 = millis();
    reset6 = false;

    //for debugging
    Serial.println("moveBackward + lookAhead  ___________________state6");

    if (currentMillis6 - prevMillis6 >= pauseInterval)
    {
      state6 = false;
      reset6 = true;
      state2 = true;

      //for debugging
      Serial.println("left  state 6 to state 2 to scan again and see where to go");
    }
  }



  if (reset1)
    prevMillis1 = millis();

  if (reset2)
    prevMillis2 = millis();

  if (reset3)
    prevMillis3 = millis();

  if (reset4)
    prevMillis4 = millis();

  if (reset5)
    prevMillis5 = millis();

  if (reset6)
    prevMillis6 = millis();




  else
  {
    state1 = true; state2 =  false; state3 = false; state4 = false; state5 = false; state6 =  false;
    dist1 = obstacle();
    Serial.println("the else statement has happened");
  }


  //for debugging
  Serial.println("nothing happening here-----------------------------------------------------(dist1)" + String(dist1));
}//END Autodrive


void detectLight()
{
  int lightIntensity = analogRead(ldrPin);
  currentBuzzMillis = millis();

  if (resetBuzz)
  {
    prevBuzzMillis = millis();
  }


  if (lightIntensity > lightThreshold)
  {
    front();
    resetBuzz = false;

    if (currentBuzzMillis - prevBuzzMillis >= buzzInterval)
    {
      digitalWrite (buzzerPin, LOW);
      resetBuzz = true;

      //for debugging
      Serial.println("resetBUZZZZZ_________________________________________________________________________");
    }

    else
    {
      digitalWrite (buzzerPin, HIGH);
    }

    //for debugging
    Serial.println("detected light ahead_____________________________________________________moving forward");
  }

  else {
    Stop();
    digitalWrite (buzzerPin, LOW);

    //for debugging
    Serial.println("Stopped because light is not up to threshold");
  }
}


void follower()
{
  dist = obstacle();

  //come up with a filtering, smoothening algorithm here for ultrasonic
  estimated_value = simpleKalmanFilter.updateEstimate(dist);

  if (estimated_value < 100 && estimated_value > 30) {
    //This condition is to move forward the robot when the object is in the range of 100 to 30 centimeters.
    front();
  }

  if (estimated_value < 30 && estimated_value > 20) {
    //This condition is to make the robot stable when the object is in the range of 20 to 30 centimeters.
    Stop();
  }

  if (estimated_value < 20 && estimated_value > 2) {
    //This condition is to move backward the robot when the object is in the range of 2 to 20 centimeters.
    back();
  }

  if (estimated_value > 100) {
    //This condition is to stop the robot when the object is in the out of range i.e greater than 100 centimeters.
    Stop();
  }
}// END Follower

//*********( THE END )***********