TFC-Mentoring-Matters-Abstraction

Dependencies:   FRDM-TFC

RaceCar/TheDetails.cpp

Committer:
mrkeithcyr
Date:
2014-07-16
Revision:
0:0699eb71778e

File content as of revision 0:0699eb71778e:

#include "TFC.h"
#include "mbed.h"

/****** INCLUDES ******/

#include "mbed.h"
#include "common.h"
#include "TFC.h"

/**********************/


/****** CAMERA PARAMETERS ******/

#define NUM_LINE_SCAN 128
#define MAX_LINE_SCAN NUM_LINE_SCAN-1
#define MIN_LINE_WIDTH  0
#define MAX_LINE_WIDTH  15

    // number of pixels at end of camera data to ignore
    // set to 0 for now, later make 15
#define FILTER_ENDS 15

    // range of camera pixels to consider
#define RANGE (NUM_LINE_SCAN - (2 * FILTER_ENDS))

    // ratio of max possible error to pixels (have to measure!)
#define ERR_RATIO 0.85

    // ratio for der threshold level (was 0.5 initially, may put back)
#define DER_RATIO 0.5

/*******************************/


/****** STEERING AND SERVO PARAMETERS ******/

    // value determined by garage mode 2 measure 
    // must be adjusted for every servo horn attached
    // must be re-adjusted (or at least checked) if 
#define MAX_STEER_LEFT -0.38

    // value determined by garage mode 2 measure
#define MAX_STEER_RIGHT 0.49

    // # MS of time between intervals (doesn't really matter)
#define DT 0.02

/*******************************************/


/****** LOGGING PARAMETERS ******/

    // number of frames to log (when logging is active) ~14 sec worth!
#define NUM_LOG_FRAMES 700

/********************************/


/****** FOR DEBUG TUNING ******/

#define TUNE_KP 0.008
#define TUNE_KI 0
#define TUNE_KD 0

    // percent minimum power 
        // estimating for a 2-ft turn => 24" / (24" + 6" car) = 4/5
        // speed of inner wheel is 20% lower worst case
#define MIN_POWER 60

    // do not change
#define SPEED_ADJUST 4

    // number of pixels line position offset before changing KP value
#define ABS_ERROR_THRESH 10

    // which control method to use
#define CONTROL_METHOD 1

/******************************/


/****** DRIVE/MOTOR PARAMETERS ******/

    // maximum speed cap for slow speed setting
#define LIGHT_SPEED 0.5

    // maximum speed cap for faster speed setting
#define LUDICROUS_SPEED 0.7

    // percent max power (for speed adjustments)
#define MAX_POWER 100

/************************************/


/****** ALGORITHM PARAMETERS ******/

    // max value to allow for unknown track conditions before killing engine
#define UNKNOWN_COUNT_MAX  50

    // max value to allow for finding starting gate before killing engine
#define STARTGATEFOUNDMAX  0

    // delay before searching for starting gate to kill engine
#define STARTGATEDELAY     50

/**********************************/


/****** IMAGE PROCESSING VARIABLES ******/

    // snapshot of camera data for this 'frame'
uint16_t   GrabLineScanImage0[NUM_LINE_SCAN];

    // derivative of line scan data
float      DerivLineScanImage0[NUM_LINE_SCAN];

    // array-- set of where in line scan data negative edges found
float      NegEdges[NUM_LINE_SCAN];

    // array-- set of where in line scan data positive edges found
float      PosEdges[NUM_LINE_SCAN];

    // max value of valid neg and positive indices 
        // also serves as a count of # edges found
uint16_t   numNegEdges = 0, numPosEdges = 0;

    // maximum measured light intensity -- to account for lighting differences
uint16_t   MaxLightIntensity = 0;

    // minimum measured light intensity -- to account for lighting differences
uint16_t   MinLightIntensity = (1 << 12);

    // maximum derivative value
float      maxDerVal = 0;

    // minimum derivative value
float      minDerVal = (float) (1 << 12);

    // average derivative value
float      aveDerVal = 0;

    // default derivative threshold
float      DerivThreshold = (1 << 9);

    // default positive edge derivative threshold
float      PosDerivThreshold = (1 << 9);

    // default negative edge derivative threshold
float      NegDerivThreshold = (1 << 9);

/****************************************/


/****** STEERING CONTROL VARIABLES ******/

    // current position of track line (in pixels -- 0 to 127)
float      CurrentLinePosition;

    // last position of track line (in pixels -- 0 to 127)
float      LastLinePosition;

    // current line position error (used for derivative calculation)
float      CurrentLinePosError = 0;
float      AbsError;

    // last line position error (used for derivative calculation)
float      LastLinePosError = 0;

    // sum of line position error (used for integral calculation)
float      SumLinePosError = 0;

    // derivative of the error
float      DerivError = 0;

    // drive straight at first
float      CurrentSteerSetting = (MAX_STEER_RIGHT + MAX_STEER_LEFT) / 2;

    // left wheel drive setting 
float      CurrentLeftDriveSetting = 0;

    // right wheel drive setting
float      CurrentRightDriveSetting = 0;

/****************************************/


/****** SPEED CONTROL VARIABLES ******/

    // maximum speed allowed
float      MaxSpeed;

    // ticker at start of race
uint16_t   startRaceTicker;

/*************************************/


/****** CUSTOM DATA TYPES ******/

typedef enum TrackStatusType {Unknown,
                              LineFound,
                              StartGateFound,
                              LineJustLeft} TrackStatusType;
                              
TrackStatusType CurrentTrackStatus;                // current track status
TrackStatusType LastTrackStatus;                   // last track status

/* typedef enum TrackType {NotSure,
                        Straight,
                        Curve,
                        Wiggle,
                        Bumps,
                        StartGate,
                        UpHill,
                        DownHill} TrackType;

TrackType CurrentTrack; */

struct LogData {
  float linepos;
  float steersetting;
  float leftdrivesetting;
  float rightdrivesetting;
};

    // array of log data to store
LogData    frameLogs[NUM_LOG_FRAMES];

    // index for log data
int        logDataIndex;

    // how many times start gate has been found
int        StartGateFoundCount = 0;

    // how many times nothing has been found
        // help5 with kill switch implementation
int        UnknownCount = 0;

    // Car can go!  Should be set to false to start.
bool       go = false;

/*******************************/

/****** EXTRA CONTROL PARAMETERS ******/

    // if true, ignores real camera and uses fake camera input instead
        // used for data processing debug
extern bool debugFakeMode = false;

    // whether output terminal data
bool terminalOutput = false;

    // whether to capture log data to output later on
bool doLogData = false;

    // whether to enable Kill Switch 
        // allows engine to stop after not finding track if true
bool killSwitch = false;

    // whether to stop or not depending on starting gate reading
bool startGateStop = false;

    // race style -- whether conservative or risky
bool doRisky = false;

/**************************************/

/****** terminalMode() *********************************************************
Purpose: Checks the current setting of dip switch 4.
Parameters: None
Returns: True if dip switch 4 is set.
*******************************************************************************/
bool terminalMode()
{
    return ((TFC_GetDIP_Switch()>>3)&0x01 == 1);
}

/****** GiveDriveSettingsToMotors() ********************************************
Purpose: Simply passes parameters to TFC_SetMotorPWM().
Parameters: The desired settings for each of the two motors
Returns: None
*******************************************************************************/
void GiveDriveSettingsToMotors(float right_drive_setting,
                               float left_drive_setting)
{
    TFC_SetMotorPWM(right_drive_setting, left_drive_setting);
}

/****** GiveSteeringSettingTo() ************************************************
Purpose: Simply passes parameters to TFC_SetServo().
Parameters: The servo number and the steering setting.
Returns: None
*******************************************************************************/
void GiveSteeringSettingTo(uint8_t ServoNumber, float Position)
{
    TFC_SetServo(ServoNumber, Position);
}

/****** ResetCamera() **********************************************************
Purpose: This function resets the camera after an image has been used.
Parameters: None
Returns: None
*******************************************************************************/
void ResetCamera()
{
    TFC_LineScanImageReady = false;
}

/****** ResetMaximumLightReading() *********************************************
Purpose: Resets the maximum light intensity variable
Parameters: None
Returns: None
*******************************************************************************/
void ResetMaximumLightReading()
{
    MaxLightIntensity = 0;
}

/****** ResetMinimumLightReading() *********************************************
Purpose: Resets the minimum light intensity variable
Parameters: None
Returns: None
*******************************************************************************/
void ResetMinimumLightReading()
{
    MinLightIntensity = (1 << 12);
}

/****** grabCameraFrame() ******************************************************
Purpose: This function gets a scan from the camera or fakes a scan from the
    camera. There are different types of fake scans that can be used to test
    other parts of the car to see how the car reacts to certain types of line
    conditions.
Parameters: None
Returns: None
*******************************************************************************/
void grabCameraFrame()
{
    uint32_t i = 0;
    uint8_t fake_type = 4;                   // type of fake data if used
    
    for(i=0;i<NUM_LINE_SCAN;i++) // print one line worth of data 
                                 // (128 characters) from Camera 0
    {
        if (debugFakeMode)
        {                   // use fake camera data
            switch (fake_type)
            {
                // ideal track -- line in center
                case 0:
                    if (i<57 || i > 70)
                        GrabLineScanImage0[i] = 0xFFF;  // no line
                    else
                        GrabLineScanImage0[i] = 0x4B0;  // line
                    break;
                // ideal track -- line to the left
                case 1:
                    if (i<27 || i > 40)
                        GrabLineScanImage0[i] = 0xFFF;  // no line
                    else
                        GrabLineScanImage0[i] = 0x4B0;  // line
                    break;
                // ideal track -- line to the right
                case 2:
                    if (i<87 || i > 100)
                        GrabLineScanImage0[i] = 0xFFF;  // no line
                    else
                        GrabLineScanImage0[i] = 0x4B0;  // line
                    break;
                // ideal track -- starting gate!
                case 3:
                    // TBD
                    break;
                // less than ideal track -- debug multi-edge issue!
                case 4:
                    if (i<54)
                        GrabLineScanImage0[i] = 4000;   // no line
                    if (i == 54)
                        GrabLineScanImage0[i] = 3370;   // neg edge
                    if (i == 55)
                        GrabLineScanImage0[i] = 3309;   // neg edge
                    if (i == 56)
                        GrabLineScanImage0[i] = 2016;   // neg edge
                    if (i == 57)
                        GrabLineScanImage0[i] = 711;    // neg edge
                    if (i == 58)
                        GrabLineScanImage0[i] = 696;    // neg edge
                    if ((i>58) && (i<69))
                        GrabLineScanImage0[i] = 500;    // line
                    if (i == 69)
                        GrabLineScanImage0[i] = 1800;   // pos edge
                    if (i > 69)
                        GrabLineScanImage0[i] = 4000;   // no line
                default:
                    break;
            }
        } else  // use real camera data
        {
            GrabLineScanImage0[i] = TFC_LineScanImage0[i];
        }
    }
}

/****** GetTheImageToUse() *****************************************************
Purpose: Retrieves whichever image we are supposed to use.
Parameters: None
Returns: None
*******************************************************************************/
void GetTheImageToUse()
{
    grabCameraFrame();
}

/****** derivativeLineScan() ***************************************************
Purpose: Calculates derivative of line scan data.
Parameters: Pointer to current image scan data, and a pointer to where you want
    the derivative to be output.
Returns: None
*******************************************************************************/
void derivativeLineScan(uint16_t* LineScanDataIn, float* DerivLineScanDataOut)
{
    uint32_t i;
    uint32_t minCnt = 0;
    uint32_t maxCnt = 0;
    float DerVal;
    float upperDerVal;
    float lowerDerVal = 0;

    maxDerVal = 0;
    minDerVal = (float) (1 << 12);
    aveDerVal = 0;

    minCnt = FILTER_ENDS;
    maxCnt = NUM_LINE_SCAN - FILTER_ENDS;

    // TERMINAL_PRINTF("i, upperDerVal, lowerDerVal, DerVal\r\n");

    // print one line worth of data from Camera 0
    for(i=minCnt;i<maxCnt;i++)
    {
        // store max light intensity value
        if (LineScanDataIn[i] > MaxLightIntensity)
            MaxLightIntensity = LineScanDataIn[i];

        // store min light intensity value
        if (LineScanDataIn[i] < MinLightIntensity)
            MinLightIntensity = LineScanDataIn[i];

        // Central Derivative
        // start point
        if (i==minCnt)
        {
            upperDerVal = (float)(LineScanDataIn[i+1]);
            // make same as start point
            lowerDerVal = (float)(LineScanDataIn[i]);
        }
        // end point
        else if (i==maxCnt - 1)
        {
            // make same as end point
            upperDerVal = (float)(LineScanDataIn[i]);
            lowerDerVal = (float)(LineScanDataIn[i-1]);
        }
        // any other point
        else
        {
            upperDerVal = (float)(LineScanDataIn[i+1]);
            lowerDerVal = (float)(LineScanDataIn[i-1]);
        }
        DerVal = (upperDerVal - lowerDerVal) / 2;
        // TERMINAL_PRINTF("%d,%9.3f,%9.3f,%9.3f\r\n",
        //                 i, upperDerVal, lowerDerVal, DerVal);

        if (DerVal > maxDerVal)
        {
            maxDerVal = DerVal;
        }
        if (DerVal < minDerVal)
        {
            minDerVal = DerVal;
        }
        //get sum
        aveDerVal = aveDerVal + DerVal;
        DerivLineScanDataOut[i] = DerVal;
    }
    aveDerVal = (float) aveDerVal / (maxCnt - minCnt);
}

/****** GetCameraData() *****************************************************
Purpose: Processes an image to get the camera's raw data.
Parameters: None
Returns: None
*******************************************************************************/
void GetCameraData()
{
    derivativeLineScan(&GrabLineScanImage0[0], &DerivLineScanImage0[0]);
}

/****** printAdjustLightsData() ************************************************
Purpose: Prints out light intensity data to the terminal.
Parameters: None
Returns: None
*******************************************************************************/
void printAdjustLightsData()
{
    if (terminalOutput)
    {
        TERMINAL_PRINTF("Max Light Intensity: %4d\r\n", MaxLightIntensity);
        TERMINAL_PRINTF("Min Light Intensity: %4d\r\n", MinLightIntensity);
        TERMINAL_PRINTF("Deriv Threshold: %9.3f\r\n", DerivThreshold);
    }
}

/****** adjustLights() *********************************************************
Purpose: Adjusts the line found threshold for different lighting conditions.
Parameters: None
Returns: None
*******************************************************************************/
void adjustLights()
{
    // LIGHT ADJUST METHOD 1
    // threshold is 1/5 of light intensity 'range'
    if (1 == 0) {
        DerivThreshold = (float) (MaxLightIntensity - MinLightIntensity) / 5;
        NegDerivThreshold = (float) -1 * (DerivThreshold);
        PosDerivThreshold = (float) (DerivThreshold);
    }

    // LIGHT ADJUST METHOD 2 -- SEEMS TO WORK MUCH BETTER
    // pos edge threshold is half range of max derivative above ave derivative
    // neg edge threshold is half range of min derivative above ave derivative
    else
    {
        NegDerivThreshold = (float) (minDerVal - aveDerVal) * DER_RATIO;
        PosDerivThreshold = (float) (maxDerVal - aveDerVal) * DER_RATIO;
    }
    printAdjustLightsData();
}

/****** findEdges_v2() *********************************************************
Purpose: This function is used to determine where the edges of the line are
    found in the camera image. The location of the line edges is printed out to
    the terminal if terminal output has been enabled.
Parameters: Pointer to the array of line scan data derivatives
Returns: None
*******************************************************************************/
void findEdges_v2(float* derivLineScanData)
{
    // search for edges in deriviative data using a threshold
    // need to store in a hash if that's possible...
    // combine edges that are a pixel apart
    
    int i;
    
    // serves as buffer to store neg edges found next to each other
    int NegEdgeBufCnt = 0, NegEdgeBufSum = 0;
    // serves as buffer to store pos edges found next to each other
    int PosEdgeBufCnt = 0, PosEdgeBufSum = 0;
        
    int minCnt = FILTER_ENDS;
    int maxCnt = NUM_LINE_SCAN - FILTER_ENDS;
    
    
    // count of neg edges found thus far
    numNegEdges = 0;
    // count of pos edges found thus far
    numPosEdges = 0;
    // print one line worth of data from Camera 0
    for(i=minCnt;i<maxCnt;i++)
    {
        // NEGATIVE EDGE FOUND!
        if (derivLineScanData[i] <= NegDerivThreshold)
        {
            if (terminalOutput)
            {
                TERMINAL_PRINTF("NEG EDGE FOUND AT INDEX "
                "%d WITH VALUE %9.3f\r\n", i, derivLineScanData[i]);
            }
            // add value to neg edge buffer
            NegEdgeBufCnt++;
            NegEdgeBufSum = NegEdgeBufSum + i;
        }
        // POSITIVE EDGE FOUND!
        else if(derivLineScanData[i] > PosDerivThreshold)
        {
            if (terminalOutput)
            {
                TERMINAL_PRINTF("POS EDGE FOUND AT INDEX "
                "%d WITH VALUE %9.3f\r\n", i, derivLineScanData[i]);
            }

            // add value to pos edge buffer
            PosEdgeBufCnt++;                                      
            PosEdgeBufSum = PosEdgeBufSum + i;

        }

        // NO EDGE FOUND
        else
        {
            // POP EDGE BUFFERS IF NON-EMPTY AND STORE TO EDGE "STACK" 
            // (i.e. edges found)
            if (NegEdgeBufCnt > 0)
            {
                // store edge value
                numNegEdges++;
                NegEdges[numNegEdges - 1] =
                    (float) NegEdgeBufSum / NegEdgeBufCnt;

                // clear edge buffer      
                NegEdgeBufSum = 0; NegEdgeBufCnt = 0;
            }

            if (PosEdgeBufCnt > 0)
            {
                // store edge value
                numPosEdges++;
                PosEdges[numPosEdges - 1] = 
                    (float) PosEdgeBufSum / PosEdgeBufCnt;

                // clear edge buffer
                PosEdgeBufSum = 0; PosEdgeBufCnt = 0;
            }
        }
    }
}

/****** FindTheLine() *********************************************************
Purpose: Simply a link to whichever version of the edge-finding functions you
    choose.
Parameters: None
Returns: None
*******************************************************************************/
void FindTheLine()
{
    findEdges_v2(&DerivLineScanImage0[0]);
}

/****** reviewEdges() **********************************************************
Purpose: This function check which edges were found and decides whether or not
    the line has been found. Its results are printed out to the terminal if
    terminal output has been enabled.
Parameters: None
Returns: None
*******************************************************************************/
void reviewEdges()
{
    LastTrackStatus = CurrentTrackStatus;

    // only one negative and positive edge found (LINE)
    if ((numPosEdges == 1) && (numNegEdges == 1))
    {
        // has proper expected width
        if (((PosEdges[0] - NegEdges[0]) >= MIN_LINE_WIDTH)
            && ((PosEdges[0] - NegEdges[0]) <= MAX_LINE_WIDTH))
        {
            // report line found!
            CurrentTrackStatus = LineFound;
            // reset unknown status count
            UnknownCount = 0;
            LastLinePosition = CurrentLinePosition;
            // update line position
            CurrentLinePosition = (PosEdges[0]+NegEdges[0]) / 2;
        }
    }
    
    // 1 pos edge found (POSSIBLE LINE)
    else if ((numPosEdges == 1) && (numNegEdges == 0))
    {
        // pos edge is within line width of edge of camera (LEFT)
        if ((PosEdges[0] <= MAX_LINE_WIDTH) && (LastLinePosError < 0))
        {
            // report line found!
            CurrentTrackStatus = LineFound;
            // reset unknown status count
            UnknownCount = 0;
            LastLinePosition = CurrentLinePosition;
            // update line position
            CurrentLinePosition = PosEdges[0] - ( MAX_LINE_WIDTH / 2);
            // TERMINAL_PRINTF("*** SINGLE POSEDGE LINE FOUND AT POSITION "
            //     "%9.3f *** \r\n", CurrentLinePosition);
        }
    }
    
    // 1 neg edge found (POSSIBLE LINE)
    else if ((numNegEdges == 1) && (numPosEdges == 0))
    {
        // neg edge is within line width of edge of camera (RIGHT)
        if ((NegEdges[0] >= (MAX_LINE_SCAN - MAX_LINE_WIDTH))
            && (LastLinePosError > 0))
        {
            // report line found!
            CurrentTrackStatus = LineFound;
            // reset unknown status count
            UnknownCount = 0;
            LastLinePosition = CurrentLinePosition;
            // update line position
            CurrentLinePosition = NegEdges[0] + ( MAX_LINE_WIDTH / 2);
            // TERMINAL_PRINTF("*** SINGLE NEGEDGE LINE FOUND AT POSITION "
            //     "%9.3f *** \r\n", CurrentLinePosition);
        } 
    }

    // 2 negative and 2 positive edges found (STARTING/FINISH GATE)
    else if ((numPosEdges == 2) && (numNegEdges == 2))
    {
        if ( // white left 'line'
            (((NegEdges[0] - PosEdges[0]) >= MIN_LINE_WIDTH) 
            && ((NegEdges[0] - PosEdges[0]) <= MAX_LINE_WIDTH))
            // white right 'line'
            && (((NegEdges[1] - PosEdges[1]) >= MIN_LINE_WIDTH)
            && ((NegEdges[1] - PosEdges[1]) <= MAX_LINE_WIDTH))
            // actual track line
            && (((PosEdges[1] - NegEdges[0]) >= MIN_LINE_WIDTH)
            && ((PosEdges[1] - NegEdges[0]) <= MAX_LINE_WIDTH)))
        {
            // only start counting for starting gate until after delay
            if (startRaceTicker > STARTGATEDELAY)
            {
                StartGateFoundCount++;
            }
        }
        CurrentTrackStatus = StartGateFound;
        // reset unknown status count
        UnknownCount = 0;
    }

    // more than 1 negative edge and positive edge found 
    // (but not 2 for both) (STARTING / FINISH GATE)
    else if ((numPosEdges > 1) && (numNegEdges > 1))
    {

    // remove edges that aren't close to center TBD DDHH
    if (terminalOutput)
    {
        TERMINAL_PRINTF("***************************************** \r\n");
        TERMINAL_PRINTF("********** NOT SURE FOUND ********** \r\n");
        TERMINAL_PRINTF("***************************************** \r\n");
    }
    CurrentTrackStatus = Unknown; 
        
    }

    // no track or starting gate found
    else
    {
        if (terminalOutput) {
            TERMINAL_PRINTF("***************************************** \r\n");
            TERMINAL_PRINTF("*** !!!!!!!!!! LINE NOT FOUND !!!!!!! *** \r\n",
                CurrentLinePosition);
            TERMINAL_PRINTF("***************************************** \r\n");
        }

        CurrentTrackStatus = Unknown;
        UnknownCount++;
    }
}

/****** printLineScanData() ****************************************************
Purpose: This function prints out the camera data to the terminal for use in
    testing and debugging.
Parameters: Takes the most recent scan returned by the camera.
Returns: None
*******************************************************************************/
void printLineScanData(uint16_t* LineScanData)
{
    uint32_t i = 0;
    float Val;

    TERMINAL_PRINTF("LINE SCAN DATA:,");

    // print one line worth of data (128) from Camera 0
    for(i=0;i<NUM_LINE_SCAN;i++)
    { 
        if (1 == 1) { // use float to print
            Val = (float) LineScanData[i];
            TERMINAL_PRINTF("%9.3f",Val);
            if(i==MAX_LINE_SCAN)  // when last data reached put in line return
                TERMINAL_PRINTF("\r\n");
            else
                TERMINAL_PRINTF(",");
        } else
        {
            TERMINAL_PRINTF("0x%X",LineScanData[i]);
            if(i==MAX_LINE_SCAN)  // when last data reached put in line return
                TERMINAL_PRINTF("\r\n",LineScanData[i]);
            else
                TERMINAL_PRINTF(",",LineScanData[i]);
        }
    }
}

/****** SteeringControl() ******************************************************
Purpose: This function decides how much to turn the front wheels to keep the car
    on course.
    The CurrentSteerSetting variable is set here.
    Its results are printed out to the terminal if terminal output has been
    enabled.
Parameters: None
Returns: None
*******************************************************************************/
void SteeringControl()
{
    float ReadPot1 = 1;
    
    // target to achieve for line position
    float targetPosition = (float)( (NUM_LINE_SCAN / 2) - 0.5);
    
    float KP;                         // proportional control factor
    float KI;                         // integral control factor
    float KD;                         // derivative control factor
    
    // PID terms
    float Pout;
    float Iout;
    float Dout;
    
    // Calculate error
    // make error to the right positive
    // i.e. if LINE to the right-- then CurrentLinePosError > 0
    //      if LINE to the left -- then CurrentLinePosError < 0
    CurrentLinePosError = CurrentLinePosition - targetPosition;
    
    // Get absolute error
    if (CurrentLinePosError >= 0) 
        AbsError = CurrentLinePosError;
    else
        AbsError = -1 * CurrentLinePosError;
    
    // CHOOSE SET OF PID CONTROL PARAMETERS
    switch (CONTROL_METHOD)
    {
        // Pure proportional control based on 
        // range of steering values vs. range of error values
        case 0:
            KP = (float) ( MAX_STEER_RIGHT - MAX_STEER_LEFT )
                / ( NUM_LINE_SCAN - (2*FILTER_ENDS) - MIN_LINE_WIDTH );
            KD = 0;
            KI = 0;
            break;
        // Proportional control but 50% more aggressive around the bends
        case 1:
            ReadPot1 = TFC_ReadPot(1)+1; // pot range 0-2
            KP = (float) ( MAX_STEER_RIGHT - MAX_STEER_LEFT )
                / ( NUM_LINE_SCAN - (2*FILTER_ENDS) - MIN_LINE_WIDTH );
            KP = KP * ReadPot1;
            KD = 0;
            KI = 0;
            break;
        // MANUAL TUNING CASE 1 (use pot to help determine tuning parameters)
        case 2:
            KP = TUNE_KP;
            KI = TUNE_KI;
            KD = TUNE_KD;
        case 3:
            if (AbsError < ABS_ERROR_THRESH)
            {
                KP = 0.003;  // when relatively straight, keep KP gain low
            }
            else
            {
                KP = 0.010;  // when curve begins or off track, increase KP gain
            }
            KI = 0;
            KD = 0;
        default:
            break;
    }

    /* Pseudocode
     previous_error = 0
     integral = 0 
     start:
       error = setpoint - measured_value
       integral = integral + error*dt
       derivative = (error - previous_error)/dt
       output = Kp*error + Ki*integral + Kd*derivative
       previous_error = error
       wait(dt)
       goto start 
    */

    if (terminalOutput)
    {
        TERMINAL_PRINTF("KP = %6.4f\r\n", KP);
        TERMINAL_PRINTF("TARGET %6.3f\r\n", targetPosition);
    }

    // Update integral of error
    // i.e. if LINE stays to the right, then SumLinePosError increases
    // i.e. if LINE stays to the left, then SumLinePosError decreases
    SumLinePosError = SumLinePosError + ( CurrentLinePosError * DT );
    
    DerivError = (CurrentLinePosError - LastLinePosError) / DT;
    
    if (terminalOutput) {
        TERMINAL_PRINTF("CURRENT LINE POSITION %9.3f\r\n",
            CurrentLinePosition);
        TERMINAL_PRINTF("CURRENT LINE POSITION ERROR %9.3f\r\n",
            CurrentLinePosError);
    }
    
    // SECOND- calculate new servo position
    
    // proportional control term
    Pout = KP * CurrentLinePosError;
    
    // integral control term
    Iout = KI * SumLinePosError;
    
    // Derivative control term
    Dout = KD * DerivError;
    
    if (terminalOutput) {
        TERMINAL_PRINTF("KP = %6.4f\r\n", KP);
        TERMINAL_PRINTF("KI = %6.4f\r\n", KI);
        TERMINAL_PRINTF("KD = %6.4f\r\n", KD);
        TERMINAL_PRINTF("Pout = %6.4f\r\n", Pout);
        TERMINAL_PRINTF("Iout = %6.4f\r\n", Iout);
        TERMINAL_PRINTF("Dout = %6.4f\r\n", Dout);
    }
    
    // add offset to steering to account for non-centered servo mounting
    // CurrentSteerSetting = Pout + Iout + Dout
    //     + ((float) (MAX_STEER_LEFT + MAX_STEER_RIGHT) / 2);
    CurrentSteerSetting = Pout
        + ((float) (MAX_STEER_LEFT + MAX_STEER_RIGHT) / 2 );
    
    // store for next cycle deriv calculation
    LastLinePosError = CurrentLinePosError;
    
    // for tuning control algo only
    if (1 == 0)
    {
        TERMINAL_PRINTF("*** ******************************** \r\n");
        TERMINAL_PRINTF("*** LINE FOUND AT POSITION %9.3f *** \r\n",
            CurrentLinePosition);
        TERMINAL_PRINTF("*** ERROR %9.3f *** \r\n", CurrentLinePosError);
        TERMINAL_PRINTF("*** INTEGRAL ERROR %9.3f *** \r\n",
            SumLinePosError);
        TERMINAL_PRINTF("*** DERIVATIVE ERROR %9.3f *** \r\n", DerivError);
        TERMINAL_PRINTF("*** P STEER SETTING %9.3f *** \r\n",
            CurrentSteerSetting);
        TERMINAL_PRINTF("*** PI STEER SETTING  %9.3f *** \r\n",
            (CurrentSteerSetting + Iout));
        TERMINAL_PRINTF("*** ******************************** \r\n");
        wait_ms(1000);
    }
}

/****** Steer() ****************************************************************
Purpose: Sets the steering servo output to the value necessary for the current
    steering needs. It also checks the value needed to make sure the board isn't
    trying to steer harder than the wheels can turn.
Parameters: None
Returns: None
*******************************************************************************/
void Steer()
{
    // make sure doesn't go beyond steering limits
    if (CurrentSteerSetting > MAX_STEER_RIGHT)
    {
        CurrentSteerSetting = MAX_STEER_RIGHT;
    }
    else if(CurrentSteerSetting < MAX_STEER_LEFT)
    {
        CurrentSteerSetting = MAX_STEER_LEFT;
    }
    if(terminalOutput)
    {
        TERMINAL_PRINTF("APPLYING SERVO SETTING %5.3f\r\n",
            CurrentSteerSetting);
    }
    TFC_SetServo(0,CurrentSteerSetting);  
}

/****** printDerivLineScanData() ***********************************************
Purpose: This function prints out the line scan derivative data to the terminal
    for use in testing and debugging.
Parameters: Takes the array of line scan data derivatives.
Returns: None
*******************************************************************************/
void printDerivLineScanData(float* derivLineScanData)
{
    uint32_t i;
    uint32_t minCnt = 0;
    uint32_t maxCnt = 0;

    minCnt = FILTER_ENDS;
    maxCnt = NUM_LINE_SCAN - FILTER_ENDS;

    TERMINAL_PRINTF("DERIVATIVE DATA:,");

    // print one line worth of data (128) from Camera 0
    for(i=minCnt;i<maxCnt;i++)
    {
        TERMINAL_PRINTF("%9.3f",derivLineScanData[i]);
        if(i==maxCnt-1)          // when last data reached put in line return
            TERMINAL_PRINTF("\r\n",derivLineScanData[i]);
        else
            TERMINAL_PRINTF(", ",derivLineScanData[i]);
    }
}

/****** printEdgesFound() ******************************************************
Purpose: This function prints the line edge data to the terminal for line use in
    debugging.
Parameters: None
Returns: None
*******************************************************************************/
void printEdgesFound()
{
    int i;

    // Check that neg edges captured ok
    TERMINAL_PRINTF("NEGATIVE EDGES FOUND:,");
    for(i=0;i<=numNegEdges-1;i++)
    {
        TERMINAL_PRINTF("%9.3f",NegEdges[i]);
        // when last data reached put in line return
        if(i==numNegEdges-1)
            TERMINAL_PRINTF("\r\n");
        else
            TERMINAL_PRINTF(", ");
    }

    // Check that pos edges captured ok
    TERMINAL_PRINTF("POSITIVE EDGES FOUND:,");
    for(i=0;i<=numPosEdges-1;i++)
    {
        TERMINAL_PRINTF("%9.3f",PosEdges[i]);
        // when last data reached put in line return
        if(i==numPosEdges-1)
            TERMINAL_PRINTF("\r\n");
        else
            TERMINAL_PRINTF(", ");
    }
}

/****** PrintOutSomeData() *****************************************************
Purpose: Prints out a selection of Track mode data after image processing.
Parameters: None
Returns: None
*******************************************************************************/
void PrintOutSomeData()
{
    printLineScanData(&GrabLineScanImage0[0]);
    printDerivLineScanData(&DerivLineScanImage0[0]);
    printAdjustLightsData();
    printEdgesFound();
}

/****** ActOnTrackStatus() *****************************************************
Purpose: This function decides what to do next based on the current track
    status. Its results are printed out to the terminal if terminal output has
    been enabled.
Parameters: None
Returns: None
*******************************************************************************/
void ActOnTrackStatus()
{
    // Decide what to do next based on current track status

    // LINE FOUND!
    if (CurrentTrackStatus == LineFound)
    {
        if (terminalOutput) {
            TERMINAL_PRINTF("***************************************** \r\n");
            TERMINAL_PRINTF("*** LINE FOUND AT POSITION %9.3f *** \r\n",
                CurrentLinePosition);
            TERMINAL_PRINTF("***************************************** \r\n");
        }

        // Update steering position 
        SteeringControl();

        // Apply to servo    
        Steer();
    } 

    // STARTING GATE FOUND
    else if (CurrentTrackStatus == StartGateFound)
    {
        if (terminalOutput)
        {
            TERMINAL_PRINTF("***************************************** \r\n");
            TERMINAL_PRINTF("********** STARTING GATE FOUND ********** \r\n");
            TERMINAL_PRINTF("**********     count = %d      ********** \r\n",
                StartGateFoundCount);
            TERMINAL_PRINTF("***************************************** \r\n");
        }

        // END RACE!
        if (startGateStop)
        {
            if (StartGateFoundCount > STARTGATEFOUNDMAX)
            {
               go = false;   // STOP!!
            } 
        }
    }
}

/****** feedbackLights() *******************************************************
Purpose: Turns on board LED's to indicate specific status for debugging
    purposes.
Parameters: None
Returns: None
*******************************************************************************/
void feedbackLights()
{
    switch (CurrentTrackStatus)
    {
        case LineFound:
            TFC_BAT_LED0_OFF;
            TFC_BAT_LED1_ON;
            TFC_BAT_LED2_ON;
            TFC_BAT_LED3_OFF;
            break;
        case StartGateFound:
            TFC_BAT_LED0_ON;
            TFC_BAT_LED1_OFF;
            TFC_BAT_LED2_OFF;
            TFC_BAT_LED3_ON;
            break;
        default:
            TFC_BAT_LED0_OFF;
            TFC_BAT_LED1_OFF;
            TFC_BAT_LED2_OFF;
            TFC_BAT_LED3_OFF;
    }
}

/****** SpeedControl() *********************************************************
Purpose: This function determines the speed settings in the code and set on the
    board itself. It then offers multiple ways to apply those settings to the
    actual speed output to the wheels.
    Its results are printed out to the terminal if terminal output has been
    enabled.
Parameters: None
Returns: None
*******************************************************************************/
void SpeedControl()
{
    // Get max speed setting from reading pot1 and then adjust
    float ErrLimit;
    float LeftDriveRatio;
    float RightDriveRatio;

    // set maximum speed
    if (doRisky)
        MaxSpeed = LUDICROUS_SPEED * ((TFC_ReadPot(0)+1)/2.0); // faster
    else
        MaxSpeed = LIGHT_SPEED * ((TFC_ReadPot(0)+1)/2.0);     // slower

    switch (SPEED_ADJUST)
    {
        // SPEED ADJUST METHOD 0
        // no speed adjust
        case 0:
            LeftDriveRatio = MAX_POWER;
            RightDriveRatio = LeftDriveRatio;

        // SPEED ADJUST METHOD 1
        // High speed when error is low, low speed when error is high
        // lower speed when more than third outside of center
        case 1:
            ErrLimit = ((float) RANGE ) * 0.5 * ERR_RATIO * 0.33;
            if (AbsError > ErrLimit) {
              LeftDriveRatio = MIN_POWER;
            } else {
              LeftDriveRatio = MAX_POWER;
            }
            RightDriveRatio = LeftDriveRatio;
            break;

        // SPEED ADJUST METHOD 2
        // max/min speed adjusts proportional to absolute value of line error
        case 2:
            ErrLimit = ((float) RANGE )  * 0.5 * ERR_RATIO; 
            LeftDriveRatio =
                MAX_POWER - ((MAX_POWER - MIN_POWER) * (AbsError / ErrLimit));
            RightDriveRatio = LeftDriveRatio;
            break;

        // SPEED ADJUST METHOD 3
        // wheel relative speed proportional to absolute value of line error
        case 3:
            // heading right, slow right wheel down
            if (CurrentLinePosError > 0)
            {
                LeftDriveRatio = MAX_POWER;
                RightDriveRatio = MAX_POWER - (MAX_POWER - MIN_POWER)
                    * (CurrentLinePosError * 2 / ( (float) RANGE ) );
            }
            // heading left, slow left wheel down
            else if (CurrentLinePosError < 0)
            {
                // note sign change due to error being negative
                LeftDriveRatio = MAX_POWER - (MIN_POWER - MAX_POWER)
                    * (CurrentLinePosError * 2 / ( (float) RANGE ) );
                RightDriveRatio = MAX_POWER;
            }
            else
            {
                LeftDriveRatio = MAX_POWER;
                RightDriveRatio = MAX_POWER;
            }
            break;
        case 4:
            // SPEED ADJUST METHOD 4
            // wheel relative speed proportional to absolute value of line error
            // only when above a certain error
            ErrLimit = ((float) RANGE )  * 0.5 * ERR_RATIO * 0.1;

            // right turn-- slow right wheel down a bit
            if (CurrentLinePosError > ErrLimit)
            {
              LeftDriveRatio = MAX_POWER;
              RightDriveRatio = MAX_POWER - (MAX_POWER - MIN_POWER)
                * (CurrentLinePosError * 2 / ( (float) RANGE ) );
            }

            // left turn-- slow left wheel down a bit
            else if(CurrentLinePosError < (-1 * ErrLimit))
            {
                // note sign change due to error being negative
                LeftDriveRatio = MAX_POWER - (MIN_POWER - MAX_POWER)
                    * (CurrentLinePosError * 2 / ( (float) RANGE ) );
                RightDriveRatio = MAX_POWER;
            }

            // when in center drive full speed
            else
            {
              LeftDriveRatio = MAX_POWER;
              RightDriveRatio = MAX_POWER;
            }
            break;

        // SPEED ADJUST METHOD 5
        // High speed when error is low, low speed when error is high
        // lower speed when more than third outside of center
        case 5:
            ErrLimit = ((float) RANGE ) * 0.5 * ERR_RATIO * 0.2;
            if (AbsError > ErrLimit)
            {
                LeftDriveRatio = MIN_POWER;
            }
            
            else
            {
                LeftDriveRatio = MAX_POWER;
            }
            
            RightDriveRatio = LeftDriveRatio;
            break;   
        
        // SPEED ADJUST METHOD 6
        // High speed when error is low, low speed when error is high
        // lower speed when more than third outside of center
        case 6:
            if (AbsError > ABS_ERROR_THRESH)
            {
                LeftDriveRatio = MIN_POWER;
            }
            
            else
            {
              LeftDriveRatio = MAX_POWER;
            }
            
            RightDriveRatio = LeftDriveRatio;
            break;                 
        
        default:
            break;
    }
    // TBD-- add speed adjust based on Xaccel sensor!

    // currently no control mechanism as don't have speed sensor  
    CurrentLeftDriveSetting = (float) (LeftDriveRatio / 100) * MaxSpeed;
    CurrentRightDriveSetting = (float) (RightDriveRatio / 100) * MaxSpeed;

    if (terminalOutput) {
        TERMINAL_PRINTF("Abs Error: %4.2f\r\n", AbsError);
        TERMINAL_PRINTF("Error Limit: %4.2f\r\n", ErrLimit);
        TERMINAL_PRINTF("MAX SPEED = %5.2f\r\n", MaxSpeed);
        TERMINAL_PRINTF("Current Left Drive Setting: %5.2f\r\n",
            CurrentLeftDriveSetting);
        TERMINAL_PRINTF("Current Right Drive Setting: %5.2f\r\n",
            CurrentRightDriveSetting);
    }
}

/****** Drive() ****************************************************************
Purpose: This is the function that enables the motors to run.  It monitors the
    pressing of buttons A and B to start and stop the car. It also monitors for
    emergency stop conditions and will turn off the motors to stop the car if
    necessary.
Parameters: None
Returns: None
*******************************************************************************/
void Drive()
{
    // START!
    // if not going, go when button A is pressed
    if (!go)
    {
        if(TFC_PUSH_BUTTON_0_PRESSED)
        {
            go = true;
            UnknownCount = 0;
            StartGateFoundCount = 0;
            startRaceTicker = TFC_Ticker[0];  // keep track of start of race
            logDataIndex = 0;                 // reset log data index
        }
    }

    // STOP!
    // if going, stop when button B is pressed
    if (go) {              
     if(TFC_PUSH_BUTTON_1_PRESSED) {
        go = false;
        StartGateFoundCount = 0;
      }
    }

    // EMERGENCY STOP!
    // 'kill switch' to prevent crashes off-track
    if (killSwitch)
    {
        // if track not found after certain time
        if (UnknownCount > UNKNOWN_COUNT_MAX)
        {
            // kill engine
            go = false;
            StartGateFoundCount = 0;
        }
    }

    // stop!
    if (!go)
    {
        // make sure motors are off 
        TFC_SetMotorPWM(0,0);
        TFC_HBRIDGE_DISABLE;
    }

    // go!
    if (go)
    {
        TFC_HBRIDGE_ENABLE;
        // motor A = right, motor B = left based on way it is mounted
        TFC_SetMotorPWM(CurrentRightDriveSetting,CurrentLeftDriveSetting);
    }
}

/****** GoSlow() ***************************************************************
Purpose: Sets the doRisky variable to false
Parameters: None
Returns: None
*******************************************************************************/
void GoSlow()
{
    doRisky = false;
}

/****** GoFast() ***************************************************************
Purpose: Sets the doRisky variable to true
Parameters: None
Returns: None
*******************************************************************************/
void GoFast()
{
    doRisky = true;
}