BOE Bot navigation with whiskers and visible light

This is useful as navigation by visible light along will not stop your robot from hitting an obstacle. But how to we go about writing a code to allow both kinds of navigation? Consider the following:

How do we do this?

Let's have a look at the navigation by whiskers code. The full code is here. Below we look at a snippit only

BOE Bot whisker navigation code (partial)

...
void loop()                                  // Main loop auto-repeats
{

  // Corner Escape

  byte wLeft = digitalRead(5);               // Copy right result to wLeft  
  byte wRight = digitalRead(7);              // Copy left result to wRight

  if(wLeft != wRight)                        // One whisker pressed?
  {                                          // Alternate from last time?
    if ((wLeft != wLeftOld) && (wRight != wRightOld))  
    {                                       
      counter++;                             // Increase count by one
      wLeftOld = wLeft;                      // Record current for next rep
      wRightOld = wRight;
      if(counter == 4)                       // Stuck in a corner?
      {
        wLeft = 0;                           // Set up for U-turn
        wRight = 0;
        counter = 0;                         // Clear alternate corner count
      }
    }  
    else                                     // Not alternate from last time
    {
      counter = 0;                           // Clear alternate corner count
    }
  }  

  // Whisker Navigation
  if((wLeft == 0) && (wRight == 0))          // If both whiskers contact
  {
    backward(1000);                          // Back up 1 second
    turnLeft(800);                           // Turn left about 120 degrees
  }
  else if(wLeft == 0)                        // If only left whisker contact
  {
    backward(1000);                          // Back up 1 second
    turnRight(400);                          // Turn right about 60 degrees
  }
  else if(wRight == 0)                       // If only right whisker contact
  {
    backward(1000);                          // Back up 1 second
    turnLeft(400);                           // Turn left about 60 degrees
  }
  else                                       // Otherwise, no whisker contact
  {
    forward(20);                             // Forward 1/50 of a second
  }
}
...

The code can be simplified as follows:

We will retain this structure, but in the final ELSE, when no whiskers are in contact, we let the robot navigate with the photosensitive eyes. This is what I have done in the program included below. Instead of a simple //forward(20)// command when no whiskers are in contact, I have made a call to the //photo_navigate()// function. This is a function I have written based on the photo navigation code you have already used. Additionally, I have modified it to make the navigation smoother. These detials are not essential, but make for a better behaved robot.

One more thing: the code allows you to switch off photo-navigation. If you think there is a bug in your circuit or something is wrong with the code, set //photo_nav = false// and the code will use the whiskers only.

BOE Bot Navigation with whiskers and phototransistors

/*
 * A. J. Misquitta
 * Robotics with the BOE Shield - LightSeekingShieldBot
 * Roams toward light and away from shade and uses whiskers for 
 * contact navigation. 
 * The algorithm used is:
 * 1: whiskers override everything
 * 2: when no whiskers are pressed, photo navigation takes over.
 * 3: photo navigation accepts a delta_photo variable to avoid
 *    fluctuations. 20 seems a reasonable choice.
 * 4: LEDs indicate choices made. They should not fluctuate 
 *    excessively. 
 * 5: photo-navigation sensitivity is set using the photo_sensitivity
 *    variable. 3000.0 seems to be a good value.
 */

#include <Servo.h>                           // Include servo library
 
Servo servoLeft;                             // Declare left and right servos
Servo servoRight;

// Pins for whiskers and LEDs 
int pinLwskr = 5;
int pinRwskr = 7;
int pinLled = 2;
int pinRled = 11;
// Pins for light sensors:
int pinLsensor = 8;
int pinRsensor = 6;
// Pin for buzzer
int pinBuzz = 5;

// some data for whiskers algorithm:
byte whiskersOld;                           
byte counter;                                // For counting alternate corners

boolean photo_nav = true;                    // Set to false to disable photo-nav
int delta_photo = 20;                        // Used to suppress photo-fluctuations
float photo_sensitivity = 3000.0;            // small == less sensitivity

/* 
 * Motor speeds are variable in the photo-navigation section,
 * but are fixed in the whiskers-navigation part. 
 */
int forwardL = 1700;
int forwardR = 1300;
int reverseL = 1300;
int reverseR = 1700;
int neutral  = 1500;

void setup()                                 // Built-in initialization block
{
  tone(pinBuzz, 3000, 1000);                 // Play tone for 1 second
  delay(1000);                               // Delay to finish tone

  servoLeft.attach(13);                      // Attach left signal to pin 13
  servoRight.attach(12);                     // Attach right signal to pin 12

  // Initialise the whiskers:
  counter = 0;
  whiskersOld = 2;
  
  Serial.begin(9600);                        // Output to Serial
  while (! Serial); // Wait untilSerial is ready
  Serial.println("BOE bot. Welcome!");

}  
 
void loop()
{
  // Main loop. This auto-repeats.
  // =============================
  // The idea here is to give the whiskers precedence. If there is no
  // input from the whiskers then use photo-navigation
  //
  
  //whiskers = 2 * digitalRead(pinLwskr);
  //whiskers += digitalRead(pinRwskr);
  whiskers = read_whiskers();
  if (Serial.available())                    // In general, output to Serial
  {                                          // only if input is detected.
    Serial.print("Whiskers = ");
    Serial.println(whiskers);
  }

  /* 
   * The Get-Out-Of-A-Corner code:
   * Works by saving the previous whisker states. If an alternation
   * is detected for a certain number (3 or 4) of times, BOEbot is in
   * a corner (likely!). In that case, best to try a near-reverse.
   */
  if((whiskers == 1) || (whiskers == 2))     // One whisker pressed?
  {                                          // Alternate from last time?
    if (whiskers != whiskersOld)  
    {                                       
      counter++;                             // Increase count by one
      whiskersOld = whiskers;
      if(counter == 3)                       // Stuck in a corner?
      {
        whiskers == 0;                       // Set up for U-turn
        counter = 0;                         // Clear alternate corner count
      }
    }  
    else                                     // Not alternate from last time
    {
      counter = 0;                           // Clear alternate corner count
    }
  }  

  /*
   * Basic whiskers navigation code:
   * Control is passed to the photo-navigation function only if
   * neither whisker is pressed.
   */
  if(whiskers == 0)          // If both whiskers contact
  {
    digitalWrite(pinLled, HIGH);
    digitalWrite(pinRled, HIGH);
    backward(1000);                          // Back up 1 second
    turnLeft(800);                           // Turn left about 120 degrees
  }
  else if(whiskers == 1)                        // If only left whisker contact
  {
    digitalWrite(pinLled, HIGH);
    digitalWrite(pinRled, LOW);
    backward(1000);                          // Back up 1 second
    turnRight(400);                          // Turn right about 60 degrees
  }
  else if(whiskers == 2)                       // If only right whisker contact
  {
    digitalWrite(pinRled, HIGH);
    digitalWrite(pinLled, LOW);
    backward(1000);                          // Back up 1 second
    turnLeft(400);                           // Turn left about 60 degrees
  }
  else                                       // Otherwise, no whisker contact
  {
    // No whisker contact so use photo-navigation (if allowed):
    digitalWrite(pinLled, LOW);
    digitalWrite(pinRled, LOW);
    if (photo_nav)
    {
      photo_navigate();
    }
    else
    {
      forward(20);                             // Forward 1/50 of a second
    }
  }
}

byte read_whiskers()
{
  // 0 : both touch
  // 1 : left
  // 2 : right
  // 3 : neither touches
  byte whiskers;
  whiskers = 2 * digitalRead(pinLwskr);
  whiskers += digitalRead(pinRwskr);
  //Serial.println(whiskers);
  return(whiskers);
}

void forward(int time)                       // Forward function
{
  servoLeft.writeMicroseconds(forwardL);     // Left wheel counterclockwise
  servoRight.writeMicroseconds(forwardR);    // Right wheel clockwise
  delay(time);                               // Maneuver for time ms
}

void turnLeft(int time)                      // Left turn function
{
  servoLeft.writeMicroseconds(reverseL);     // Left wheel clockwise
  servoRight.writeMicroseconds(forwardR);    // Right wheel clockwise
  delay(time);                               // Maneuver for time ms
}

void turnRight(int time)                     // Right turn function
{
  servoLeft.writeMicroseconds(forwardL);     // Left wheel counterclockwise
  servoRight.writeMicroseconds(reverseR);    // Right wheel counterclockwise
  delay(time);                               // Maneuver for time ms
}

void backward(int time)                      // Backward function
{
  servoLeft.writeMicroseconds(reverseL);     // Left wheel clockwise
  servoRight.writeMicroseconds(reverseR);    // Right wheel counterclockwise
  delay(time);                               // Maneuver for time ms
}
 
void photo_navigate() 
{
  // Photo navigation function
  // 
  float tLeft = float(rcTime(pinLsensor));    // Get left light & make float
  float tRight = float(rcTime(pinRsensor));   // Get right light & make float
 
  float ndShade;                             // Normalized differential shade
  ndShade = tRight / (tLeft+tRight) - 0.5;   // Calculate it and subtract 0.5

  int speedLeft, speedRight;                 // Declare speed variables
 
  if (ndShade > 0.0)                         // Shade on right?
  {                                          // Slow down left wheel
    speedLeft = int(200.0 - (ndShade * photo_sensitivity));
    speedLeft = constrain(speedLeft, -200, 200);
    speedRight = 200;                        // Full speed right wheel
  }
  else                                       // Shade on Left?
  {                                          // Slow down right wheel
    speedRight = int(200.0 + (ndShade * photo_sensitivity));
    speedRight = constrain(speedRight, -200, 200);
    speedLeft = 200;                         // Full speed left wheel
  }  
  /*
   * Stutter Suppression
   * ===================
   * If the left and right servo speeds are too close (i.e. not much
   * difference in light on the two sides) then we may encounter a 
   * stutter: that is, the motors can rapidly alternate in speed and 
   * direction. To suppress this we use the following code. If the 
   * two speeds are less than delta_photo apart, the two are assigned
   * the same speed, taken as the average of the two.
   */
  if (abs(speedLeft-speedRight) < delta_photo)
  {
    int avg = (speedLeft + speedRight)/2;
    speedLeft = avg;
    speedRight = avg;
  }
  /*
   * Write out the speeds if input is detected on the serial port
   */
  if (Serial.available())
  {
    Serial.print("Speeds L= ");
    Serial.print(speedLeft);
    Serial.print("  R= ");
    Serial.println(speedRight);
  }

  /*
   * Get rolling!!!
   */
  maneuver(speedLeft, speedRight, 50);       // Set wheel speeds
}

long rcTime(int pin)                         // rcTime measures decay at pin
{
  /*
   * This is an RC-time operation. Because we need to measure the decay
   * time fairly rapidly, the capacitance in parallel with the photo-transistor
   * is quite small (in this case, 0.1 microFarad). 
   * Time constant = R * C
   *               = (V/I) * C
   * Here, V = 5 V (set by digitalWrite(pin, HIGH)
   * I varies depending on incident light. 
   *    = 1.75 mA (bright) => R = V/I =  3 kOhm  => RC = 3e-4 sec  =  300 msec
   *    = 0.25 ma (dim)    => R = V/I = 20 kOhm  => RC = 0.002 sec = 2000 msec
   * This is rough. In practice you will see a greater variation.
   */
  pinMode(pin, OUTPUT);                      // Charge capacitor
  digitalWrite(pin, HIGH);                   // ..by setting pin ouput-high
  delay(5);                                  // ..for 5 ms
  pinMode(pin, INPUT);                       // Set pin to input
  digitalWrite(pin, LOW);                    // ..with no pullup
  long time  = micros();                     // Mark the time
  while(digitalRead(pin));                   // Wait for voltage < threshold
  time = micros() - time;                    // Calculate decay time
  return time;                               // Returns decay time
}

// maneuver function
void maneuver(int speedLeft, int speedRight, int msTime)
{
  if (speedLeft < speedRight)
  {
    digitalWrite(pinRled, HIGH);
    digitalWrite(pinLled, LOW);
  } 
  else if (speedLeft > speedRight)
  {
    digitalWrite(pinRled, LOW);
    digitalWrite(pinLled, HIGH);
  }
  else
  {
    digitalWrite(pinRled, LOW);
    digitalWrite(pinLled, LOW);
  }
  servoLeft.writeMicroseconds(neutral + speedLeft);   // Set left servo speed
  servoRight.writeMicroseconds(neutral - speedRight); // Set right servo speed
  if(msTime==-1)                                   // if msTime = -1
  {                                  
    servoLeft.detach();                            // Stop servo signals
    servoRight.detach();   
  }
  delay(msTime);                                   // Delay for msTime
}

AJMPublic/teaching/arduino-pi/projects/arduino/boe-bot/whiskers-visible-light-code (last edited 2021-04-14 13:12:41 by apw109)