David's line following code from the LVBots competition, 2015.

Dependencies:   GeneralDebouncer Pacer PololuEncoder mbed

Fork of DeadReckoning by David Grayson

main.cpp

Committer:
DavidEGrayson
Date:
2015-04-16
Revision:
57:99bec7fab454
Parent:
56:55b1473f9e3b

File content as of revision 57:99bec7fab454:

#include <mbed.h>
#include <Pacer.h>
#include <GeneralDebouncer.h>
#include <math.h>

#include "main.h"
#include "motors.h"
#include "encoders.h"
#include "leds.h"
#include "pc_serial.h"
#include "test.h"
#include "reckoner.h"
#include "buttons.h"
#include "line_tracker.h"
#include "l3g.h"
#include "turn_sensor.h"

Reckoner reckoner;
LineTracker lineTracker;
TurnSensor turnSensor;
Logger logger;
Pacer loggerPacer(50000);

uint8_t lapsCompleted = 0;
uint32_t totalEncoderCounts = 0;
uint32_t nextLogEncoderCount = 0;
const uint32_t logSpacing = 100;

void setLeds(bool v1, bool v2, bool v3, bool v4)
{
   led1 = v1;
   led2 = v2;
   led3 = v3;
   led4 = v4;
}

int __attribute__((noreturn)) main()
{
    pc.baud(115200);
    
    // Enable pull-ups on encoder pins and give them a chance to settle.
    if (l3gInit())
    {
        // Error initializing the gyro.
        setLeds(0, 0, 1, 1);
        while(1);
    }

    encodersInit();
    motorsInit();
    buttonsInit();

    // Test routines
    //testMotors();
    //testEncoders();
    //testLineSensors();
    //testReckoner();
    //testButtons();
    //testDriveHome();
    //testFinalSettleIn();
    //testCalibrate();
    //testLineFollowing();
    //testAnalog();
    //testSensorGlitches();
    //testTurnInPlace();
    //testCloseness();
    //testLogger();
    //testL3g();
    //testTurnSensor();
    //testReckoningWithGyro();

    // Real routines for the contest.
    loadCalibration();
    
    setLeds(1, 0, 0, 0);
    waitForSignalToStart();
  
    setLeds(0, 1, 0, 0);    // led3 and led4 get set by followLineSmart for debugging
    //followLineFast();
    followLineSmart();
    
    setLeds(1, 1, 1, 1);
    loggerReportLoop();
}

void loggerService()
{
    // loggerPacer.pace()
    if (totalEncoderCounts > nextLogEncoderCount)
    {
        nextLogEncoderCount += logSpacing;
        
        struct LogEntry entry;
        entry.turnAngle = turnSensor.getAngle() >> 16;
        entry.x = reckoner.x >> 16;
        entry.y = reckoner.y >> 16;
        logger.log(&entry);
    }
}

void loggerReportLoop()
{
    while(1)
    {
        if(button1DefinitelyPressed())
        {
            logger.dump();
        }
    }   
}


void loadCalibration()
{
    /** QTR-3RC **/
    lineTracker.calibratedMinimum[0] =  137;
    lineTracker.calibratedMinimum[1] =  132;
    lineTracker.calibratedMinimum[2] =  154;
    lineTracker.calibratedMaximum[0] =  644;
    lineTracker.calibratedMaximum[1] =  779;
    lineTracker.calibratedMaximum[2] = 1000;    
    
    /** QTR-3A
    lineTracker.calibratedMinimum[0] = 34872;
    lineTracker.calibratedMinimum[1] = 29335;
    lineTracker.calibratedMinimum[2] = 23845;
    lineTracker.calibratedMaximum[0] = 59726;
    lineTracker.calibratedMaximum[1] = 60110;
    lineTracker.calibratedMaximum[2] = 58446;
    **/
}

void updateReckonerFromEncoders()
{
    while(encoderBuffer.hasEvents())
    {
        PololuEncoderEvent event = encoderBuffer.readEvent();
        switch(event)
        {
        case ENCODER_LEFT | POLOLU_ENCODER_EVENT_INC:
            reckoner.handleTickLeftForward();
            totalEncoderCounts++;
            break;
        case ENCODER_LEFT | POLOLU_ENCODER_EVENT_DEC:
            reckoner.handleTickLeftBackward();
            totalEncoderCounts--;
            break;
        case ENCODER_RIGHT | POLOLU_ENCODER_EVENT_INC:
            reckoner.handleTickRightForward();
            totalEncoderCounts++;
            break;
        case ENCODER_RIGHT | POLOLU_ENCODER_EVENT_DEC:
            reckoner.handleTickRightBackward();
            totalEncoderCounts--;
            break;
        }
    }
}

void updateReckoner(TurnSensor & turnSensor)
{
    if (!encoderBuffer.hasEvents())
    {
        return;
    }
    
    reckoner.setTurnAngle(turnSensor.getAngle());
    
    while(encoderBuffer.hasEvents())
    {
        PololuEncoderEvent event = encoderBuffer.readEvent();
        switch(event)
        {
        case ENCODER_LEFT | POLOLU_ENCODER_EVENT_INC:
        case ENCODER_RIGHT | POLOLU_ENCODER_EVENT_INC:
            totalEncoderCounts++;
            reckoner.handleForward();
            break;
        case ENCODER_LEFT | POLOLU_ENCODER_EVENT_DEC:
        case ENCODER_RIGHT | POLOLU_ENCODER_EVENT_DEC:
            reckoner.handleBackward();
            totalEncoderCounts--;
            break;
        }
    }
}

float magnitude()
{
    return sqrt((float)reckoner.x * reckoner.x + (float)reckoner.y * reckoner.y);   
}

float dotProduct()
{
    float s = (float)reckoner.sinv / (1 << 30);
    float c = (float)reckoner.cosv / (1 << 30);
    float magn = magnitude();
    if (magn == 0){ return 0; }    
    return ((float)reckoner.x * c + (float)reckoner.y * s) / magn;
}

// The closer this is to zero, the closer we are to pointing towards the home position.
// It is basically a cross product of the two vectors (x, y) and (cos, sin).
float determinant()
{
    // TODO: get rid of the magic numbers here (i.e. 30)
    float s = (float)reckoner.sinv / (1 << 30);
    float c = (float)reckoner.cosv / (1 << 30);
    return (reckoner.x * s - reckoner.y * c) / magnitude();
}

int16_t reduceSpeed(int16_t speed, int32_t reduction)
{
    if (reduction > speed)
    {
        return 0;   
    }
    else
    {
        return speed - reduction;
    }
}

void waitForSignalToStart()
{
    while(!button1DefinitelyPressed())
    {
        updateReckonerFromEncoders();
    }
    reckoner.reset();
    while(button1DefinitelyPressed())
    {
        updateReckonerFromEncoders();
    }
    wait(0.2);
}

void updateMotorsToFollowLineSlow()
{
    const int16_t drivingSpeed = 400;
    const int32_t followLineStrength = drivingSpeed * 5 / 4;

    int16_t speedLeft = drivingSpeed;
    int16_t speedRight = drivingSpeed;
    int16_t reduction = (lineTracker.getLinePosition() - 1000) * followLineStrength / 1000;
    if(reduction < 0)
    {
        speedLeft = reduceSpeed(speedLeft, -reduction);   
    }
    else
    {
        speedRight = reduceSpeed(speedRight, reduction);
    }
    
    motorsSpeedSet(speedLeft, speedRight);
}

void updateMotorsToFollowLineFast(int16_t drivingSpeed)
{
    const int32_t followLineStrength = drivingSpeed * 5 / 4;
    static int16_t lastPosition = 1000;

    int16_t position = lineTracker.getLinePosition();

    int16_t speedLeft = drivingSpeed;
    int16_t speedRight = drivingSpeed;
    int32_t veer = (position - 1000) * followLineStrength / 1000 + (position - lastPosition) * 200;
    if(veer > 0)
    {
        speedRight = reduceSpeed(speedRight, veer);
    }
    else
    {
        speedLeft = reduceSpeed(speedLeft, -veer);   
    }
    
    motorsSpeedSet(speedLeft, speedRight);
    
    lastPosition = position;
}

void followLineFast()
{
    totalEncoderCounts = 0;
    Pacer reportPacer(200000);
    
    loadCalibration();
    Timer timer;
    timer.start();
    turnSensor.start();
    while(1)
    {
        turnSensor.update();
        updateReckoner(turnSensor);
        loggerService();
        
        lineTracker.read();
        updateMotorsToFollowLineFast(1000);
        
        if (button1DefinitelyPressed())
        {
            break;
        }
    }
    motorsSpeedSet(0, 0);
}

bool foundStart()
{
    static int32_t lastX = 0;
    bool result = lastX < 0 && reckoner.x >= 0 && abs(reckoner.y) < (115 << 16) &&
      totalEncoderCounts > 10000 && abs(turnSensor.getAngle()) < turnAngle1 * 30;
    lastX = reckoner.x;
    return result;
}

bool onLongStraightPart()
{
    if (lapsCompleted == 0) { return false; }
    
    // Figure out what part of the log corresponds to our current situation.
    uint32_t logIndex = totalEncoderCounts / logSpacing;
    
    if (logIndex >= logger.getSize())
    {
        // Should not happen.
        return false;
    }
    
    // To improve this, we could check that turnSensor.getAngle() matches what is in the log.

    uint32_t angle1 = turnSensor.getAngleUnsigned();

    // 2000 encoder ticks
    const uint32_t lookAheadAmount = 3000 / logSpacing;
    
    // Figure out how far away the next turn is.
    uint32_t i = logIndex;
    while(1)
    {
        i++;
        
        if (i >= logger.getSize())
        {
            // reached the end the log
            return false;
        }

        if (i > logIndex + lookAheadAmount)
        {
            // looked far enough ahead that we don't think there is a turn coming up soon
            return true;
        }
        
        
        uint32_t angle2 = (uint16_t)logger.entries[i].turnAngle << 16;
        if (abs((int32_t)(angle2 - angle1)) > turnAngle45)
        {
            // detected a turn
            return false;
        }
    }
}

void followLineSmart()
{
    lapsCompleted = 0;   
    totalEncoderCounts = 0;

    Pacer reportPacer(200000);
    
    loadCalibration();
    turnSensor.start();
    while(1)
    {
        turnSensor.update();
        updateReckoner(turnSensor);
        loggerService();
        
        lineTracker.read();
        
        // By default, choose a cautious speed of 1000 (out of 1200).
        int16_t speed = 1000;
        
        // Go fast if we know we are on a long straight part.
        if (onLongStraightPart())
        {
            speed = 1200;
            led4 = 1;
        }
        else
        {
            led4 = 0;
        }
        
        
        updateMotorsToFollowLineFast(speed);

        if (foundStart())
        {
            // Another lap has been completed!
            lapsCompleted += 1;
            led3 = lapsCompleted & 1;

            reckoner.reset();
            turnSensor.reset();
            totalEncoderCounts = 0;
            nextLogEncoderCount = 0;
        }
        
        if (lapsCompleted == 3 && totalEncoderCounts > 4000)
        {
            // Classy move: know when you are done with the competition and stop automatically.
            // (Of course, there is a risk that this will backfire!)
            break;
        }
        
        if (button1DefinitelyPressed())
        {
            break;
        }
    }
    motorsSpeedSet(0, 0);
}