ECE 4180 Embedded Systems: Discover Bot

Project Description

Rover type bots are the automated voyagers into the unknown. To gain appreciation for these wheeled adventurers, Fulford and Sons came up with a basic rover type bot implementing the bare bones fundemental features a rover of the moon exploration caliber might possess. The discover bot implements PID control in an effort to optimize straight-line travel. It houses an on board IMU that features a magnetometer for compass headings. In addition, it sports the Adafruit BLE module to communicate with androids or iphones in an easy to use interface in a companion phone app. Furthermore, the mounted servo and IR attachment are used for object detection and obstacle avoidance.

The Discover Bot initializes on power up awaiting user input using the companion app. To use this, one only needs to select the UART feature when using the app and begin with typing a 1 (the binary ON!!) or any character and send it via UART (boring use the 1. It's coo) The Discover Bot uses this communication link with the mbed to send two commands after the user initializes the intuitive prompt (see screen capture below). The user can put in the heading (degrees) and a distance (this is less intuitive since it is based on rotations detected by the encoders and system specific). For the Discover system used in this example, 600 rotational turns roughly equate to 1 foot. Several measurements are likely necessary to determine this in another system.

Once the user enters this information, the bot immediately initializes calibration. This is used for the magnetometer on the IMU primarily as well as the servo's initial calibration. Following calibration, the Discover Bot will find its heading and aim in line with that direction and begin traveling. A routine object detection call is made each loop iteration in case of unexpected boulders or space invaders that pop up along the way. Discover will course correct and resume its original heading to arrive slightly off from its intent, but that is better than the alternative: destruction. Upon arrival to its destination, Discover will scan its horizon and find any interesting objects and aim itself towards it.

At this point, Discover is finished in its task, but it could be further updated to collect said object, or a laser cannon could be mounted to destroy the object. The choice is yours. It could then be called to return home the way in whence it came bringing loot or wearing a shiny grin :J /media/uploads/jfulford6/screen_capture.png

Quote:

The various videos can be found here that showcase our system. Link to video demonstration of system: /media/uploads/jfulford6/4180_discover_bot.mp4 Link to video demonstration of object avoidance: /media/uploads/jfulford6/demo_obstacle_avoidance.mp4

/media/uploads/jfulford6/top_down.jpg /media/uploads/jfulford6/front.jpg

System Code

main.cpp

//====================================================//
/*
            Project: Discover Bot
            Authors: Daniel Fulford
                     James Fulford
*/
//====================================================//
// PID code was found at: https://developer.mbed.org/cookbook/mbed-Rover


#include "mbed.h"
#include "Motor.h"
#include "PID.h"
#include "HALLFX_ENCODER.h"
#include "LSM9DS1.h"
#include "rtos.h"
#include "Servo.h"

#define PI 3.14159
#define DECLINATION -4.94 // Declination (degrees) in Atlanta,GA.

// Various objects:
Motor rightMotor(p26, p18, p15);  //pwm, AIN2, AIN1
Motor leftMotor(p21, p16, p17); //pwm, BIN1, BIN2
HALLFX_ENCODER encR(p28);
HALLFX_ENCODER encL(p27);
LSM9DS1 imu(p9, p10, 0xD6, 0x3C); // Sensor object
AnalogIn IRout(p20);//analog out from sensor to analog in on mbed
Servo myservo(p22);

Serial pc(USBTX, USBRX); //RawSerial for UART communication
RawSerial BLE(p13,p14); // BLE's rx, tx pins

// Leds for communcation notification
DigitalOut led1(LED1);
DigitalOut led4(LED4);

//Tuning parameters calculated from step tests;
//see http://mbed.org/cookbook/PID for examples.
PID leftPid(1.7, 0.45, 2, 0.1);  //Kc, Ti, Td
PID rightPid(1.7, 0.45, 2, 0.1); //Kc, Ti, Td


//=====================================================//
/*
                    Various Variables
*/
//=====================================================//

int desiredHeading = 300; // Heading for the bot to travel along
int leftPulses      = 0; //How far the left wheel has travelled.
int leftPrevPulses  = 0; //The previous reading of how far the left wheel
                         //has travelled.
float leftVelocity  = 0.0; //The velocity of the left wheel in pulses per
                            //second.
int rightPulses     = 0; //How far the right wheel has travelled.
int rightPrevPulses = 0; //The previous reading of how far the right wheel
                        //has travelled.
float rightVelocity = 0.0; //The velocity of the right wheel in pulses per
                            //second.
int length     = 1800; //Number of pulses to travel.

bool active = false; // bot sleeps until this changes to true;
bool started = false; // used to check if the user has started the system

//=====================================================//
/*                     
                Calibration/Setup Functions
*/
//=====================================================//

void thread2(void const *argument)
{
    // This thread spins the bot for calibrations
    leftMotor.speed(0.4);
    rightMotor.speed(-0.4);
}

void calibrateIMU()
{
    imu.calibrate(1);
    Thread thread1(thread2); // start spinning bot for calibration
    imu.calibrateMag(0);
    thread1.terminate(); // stop thread
    leftMotor.speed(0.0);
    rightMotor.speed(0.0);   
    encR.reset();
    encL.reset();
    wait(1);    
}

void PIDsetup()
{
    leftMotor.period(0.00005);  //Set motor PWM periods to 20KHz.
    rightMotor.period(0.00005);
    
    //Input and output limits have been determined
    //empirically with the specific motors being used.
    //Change appropriately for different motors.
    //Input  units: counts per second.
    //Output units: PwmOut duty cycle as %.
    //Default limits are for moving forward.
    leftPid.setInputLimits(0, 3200);
    leftPid.setOutputLimits(0.0, 1.0);
    leftPid.setMode(AUTO_MODE);
    rightPid.setInputLimits(0, 3200);
    rightPid.setOutputLimits(0.0, 1.0);
    rightPid.setMode(AUTO_MODE);

    //Velocity to mantain in pulses per second
    leftPid.setSetPoint(200);
    rightPid.setSetPoint(325);
}

//=====================================================//
/*                     
           Bot Setup for Traveling Functions
*/
//=====================================================//

float calculateHeading()
{
    while(!imu.magAvailable(X_AXIS));
    imu.readMag();
    float mx = imu.calcMag(imu.mx);
    mx = -mx;
    float my = imu.calcMag(imu.my);
    float heading;
    if (my == 0.0)
        heading = (mx < 0.0) ? 180.0 : 0.0;
    else
        heading = atan2(mx, my)*360.0/(2.0*PI);
            
    heading -= DECLINATION; //correct for geo location
    if(heading>180.0) heading = heading - 360.0;
    else if(heading<-180.0) heading = 360.0 + heading;
    else if(heading<0.0) heading = 360.0  + heading;
    
    return heading;
}

void CW(float spd) //clockwise rotation
{   
    leftMotor.speed(spd);
    rightMotor.speed(-spd);
}
void CCW(float spd)
{
    leftMotor.speed(-spd);
    rightMotor.speed(spd);
}
    
    
void rotateToDesired(float spd)
{
    float currHeading = calculateHeading();
    float offset = desiredHeading - currHeading;
    
 if(offset <= 0)//if offset is negative add 360 shift back to correct terminal angle 
    {
        offset = offset + 360;
        if(abs(offset) - 180 < 0 )
            {
            while(abs(calculateHeading()-desiredHeading) >= 2) //turn cw
            {
                CW(spd);
            }
        }
        else //turn cw
        {
           while(abs(calculateHeading()-desiredHeading) >= 2) //turn cw
            {
                CCW(spd);
            } 
        }
    }
    else //if absolute distance is less than 180
    {
        if(abs(offset) - 180 < 0)//and is negative
        {
            while(abs(calculateHeading()-desiredHeading) >= 2) //turn cw
            {
                CW(spd);
            }
        }
        else //turn ccw
        {
           while(abs(calculateHeading()-desiredHeading) >= 2) //turn ccw
            {
                CCW(spd);
            } 
        }
        
    }
    
    leftMotor.brake();
    rightMotor.brake();
    encR.reset();
    encL.reset();
    wait(1);
    
}

void BLE_recv()
{
    
    if(!started)
    {
        
        char trash; // Used to clear any possible garbage data on the buffer
    
        while(!BLE.readable()); // wait for an input to start the system
        started = true;
        while(BLE.readable())
        {
            trash = BLE.getc(); // clear the BLE UART buffer
            wait_ms(100);
        }
    }
    
    
    if(started)
    {
        BLE.printf("Please Enter the Heading.");
        while(!BLE.readable());
        char input1[] = ""; 
        int count = 0;
        
        while(BLE.readable())
        {
            input1[count] = BLE.getc();
            count++;
            led1 = 1;
            wait_ms(50); // to make sure aa data has arrived in buffer before 
                        // accidentally leaving the loop
            led1 = 0;
        }
        
        sscanf(input1, "%d", &desiredHeading); //convert to integer
        
        BLE.printf("Please Enter the Distance.");
        while(!BLE.readable());
        char input2[] = ""; 
        count = 0;
        
        while(BLE.readable())
        {
            input2[count] = BLE.getc();
            count++;
            led1 = 1;
            wait_ms(50); // to make sure aa data has arrived in buffer before 
                        // accidentally leaving the loop
            led1 = 0;
        }
        
        sscanf(input2, "%d", &length);
  
        active = true;
    }   
}

//=====================================================//
/*                     
                Robot Movement Functions
*/
//=====================================================//

void objectAdvoidIR()
{
    float IRvolts = 0.0;//variable to store read voltages from IR sensor 
    float distance;

        IRvolts=IRout.read();//read voltage from analog in (0-1 scale)
        distance = .95/IRvolts*10;
        if(distance < 15)
        {
            
                CCW(0.25);
                wait(1.5);
                leftMotor.speed(0.0);
                rightMotor.speed(0.0);
                wait(0.1);
                leftMotor.speed(0.25);
                rightMotor.speed(0.25);
                wait(2);
                leftMotor.speed(0.0);
                rightMotor.speed(0.0);
                wait(0.1);
                CW(0.25);
                wait(1);
                rotateToDesired(0.3); 
                
        }
       // pc.printf("Current Min Value is: %f\r\n", curMin);
     
}   

void headOnward()
{
    while ((leftPulses < length) || (rightPulses < length)) {

        //Get the current pulse count and subtract the previous one to
        //calculate the current velocity in counts per second.
        leftPulses = encL.read();
        leftVelocity = (leftPulses - leftPrevPulses) / 0.01;
        leftPrevPulses = leftPulses;
        //Use the absolute value of velocity as the PID controller works
        //in the % (unsigned) domain and will get confused with -ve values.
        leftPid.setProcessValue(fabs(leftVelocity));
        leftMotor.speed(leftPid.compute());

        rightPulses = encR.read();
        rightVelocity = (rightPulses - rightPrevPulses) / 0.01;
        rightPrevPulses = rightPulses;
        rightPid.setProcessValue(fabs(rightVelocity));
        rightMotor.speed(rightPid.compute());

        wait(0.01);
        
        //float heading = calculateHeading(); // get the heading from IMU
        //printf("Heading (degrees): %f2.2\r", heading);
        objectAdvoidIR();//use IR for obstacle avoidance and use basic evasion

    }
    
    leftMotor.brake();
    rightMotor.brake();
    encR.reset();
    encL.reset();
    wait(1);
    
}
void scanHorizon()
{
    float IRvolts = 0.0;//variable to store read voltages from IR sensor 
    float distance;
    float oldMin = 100;//set large to be max distance capable  
    float curMin; 
    float p = 0.0;
    float finalPos = 0.0; 

    while(p < 1.0){
        
        p+=0.02;
        myservo = p;
        IRvolts=IRout.read();//read voltage from analog in (0-1 scale)
        distance = .95/IRvolts*10;
        curMin = distance;
        if(curMin < oldMin)
        {
            oldMin = curMin; //set old min value to current one measured
            finalPos = p;
        }
       // pc.printf("Current Min Value is: %f\r\n", curMin);
        //pc.printf("Old Min Value is: %f\r\n", oldMin);
        wait(0.1);
    }
    
       //final position for when IR detected min value
       myservo = finalPos;
       float deg = finalPos*180; //used to indicate final pos (degrees of servo) 
       //display min value
       pc.printf("The Min Value was: %f\r\n", oldMin);
       pc.printf("The heading was: %f\r\n", finalPos*180.0);
       wait(2); 
       myservo = 0.5;
       wait(1);
       
/*  Note:     
    determine the new heading in line with object of interest based off the
    the finalPos of servo 
    the outer conditional statements conceptualizes the front end of the bot as 
    having the desiredheading pointing straight ahead. This means that the 
    approx. 180 deg span infront of bot is the work space of the servo and
    ultimately the IR sensor. Using this and basic mathematical manipulation
    the inner statements were formed. (A picture is much more helpful here)
*/
       int x = 90 - deg;
       int y = abs(deg-90);
       float currHeading = calculateHeading();
       if(x > 0)
       {
           if(desiredHeading - x < 0)
           {
               desiredHeading = currHeading - x + 360;//add 360 for 
               rotateToDesired(0.4);
           }
           else
           {
               desiredHeading = currHeading - x;
               rotateToDesired(0.4);
           }
       }
       else
       {
           if(desiredHeading + y > 360)
           {
               desiredHeading = currHeading + y - 360;
               rotateToDesired(0.4);
           }
           else
           {
               desiredHeading = currHeading + y;
               rotateToDesired(0.4);
           }
       }               
       
}

int main() 
{

    pc.baud(9600);
    BLE.baud(9600);
    BLE.attach(&BLE_recv, Serial::RxIrq);
    
    while(1)
    { 
        if(active)
        {
            pc.printf("%d\r\n",desiredHeading);
            myservo.calibrate(0.001, 180); // needed to ensure the servo rotates the full 180deg
            wait(1);
            myservo = 0.5; // sets IR sensor to front of bot
            
            // Perform IMU steps
            imu.begin();
            calibrateIMU(); // calibrate the IMU sensor
            
            PIDsetup();
        
            rotateToDesired(0.2); // Spin robot to correct heading
            headOnward(); // Move robot in direction of heading with setpoint speed
            myservo.calibrate(0.001, 180); // calibrate again to set up PWM period for 
                                           // servo. When the period is set for DC motors
                                           // this will interfere with how the servo
                                           // operates.
            
            wait(1);
            scanHorizon(); // scan for object in area
            active = false;
        }
    }
}

Published Libraries Used

Import libraryHALLFX_ENCODER

Basic Encoder Library for Sparkfun's Hall- Effect Encoder Kit Part# ROB-12629

Import libraryLSM9DS1_Library_cal

library for IMU demo with mag calibration code

Import librarymbed-rtos

Official mbed Real Time Operating System based on the RTX implementation of the CMSIS-RTOS API open standard.

Import libraryMotor

Control a DC motor via a standard H-bridge using a PwmOut pin to control speed and two DigitalOut pins to control direction. Can change pwm period on the PwmOut pin, and also brake high or low.

Import libraryPID

Proportional, integral, derivative controller library. Ported from the Arduino PID library by Brett Beauregard.

Import libraryServo

A class to control a model R/C servo, using a PwmOut


Please log in to post comments.