Code for autonomous rover for Sparkfun AVC. DataBus won 3rd in 2012 and the same code was used on Troubled Child, a 1986 Jeep Grand Wagoneer to win 1st in 2014.

Dependencies:   mbed Watchdog SDFileSystem DigoleSerialDisp

main.cpp

Committer:
shimniok
Date:
2018-11-29
Revision:
17:08f6ee55abbe
Parent:
5:e301811727f7
Child:
20:1c2067937065

File content as of revision 17:08f6ee55abbe:

/** Code for "Data Bus" UGV entry for Sparkfun AVC 2014
 *  http://www.bot-thoughts.com/
 */

///////////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES
///////////////////////////////////////////////////////////////////////////////////////////////////////

#include "mbed.h"
#include <math.h>
#include <stdint.h>
#include "devices.h"
#include "globals.h"
#include "Config.h"
#include "Buttons.h"
#include "Display.h"
#include "Menu.h"
#include "GPSStatus.h"
#include "logging.h"
#include "Telemetry.h"
#include "SystemState.h"
#include "shell.h"
#include "Steering.h"
#include "Sensors.h"
#include "kalman.h"
#include "Ublox6.h"
#include "PinDetect.h" // TODO 4 this should be broken into .h, .cpp
#include "IncrementalEncoder.h"
#include "Steering.h"
#include "Schedule.h"
#include "GeoPosition.h"
#include "Mapping.h"
#include "SimpleFilter.h"
#include "Beep.h"
#include "util.h"
#include "updater.h"

///////////////////////////////////////////////////////////////////////////////////////////////////////
// DEFINES
///////////////////////////////////////////////////////////////////////////////////////////////////////

#define absf(x) (x *= (x < 0.0) ? -1 : 1)

#define GPS_MIN_SPEED   2.0             // speed below which we won't trust GPS course
#define GPS_MAX_HDOP    2.0             // HDOP above which we won't trust GPS course/position

// Driver configuration parameters
#define SONARLEFT_CHAN   0
#define SONARRIGHT_CHAN  1
#define IRLEFT_CHAN      2
#define IRRIGHT_CHAN     3  
#define TEMP_CHAN        4
#define GYRO_CHAN        5

#define INSTRUMENT_CHECK    0
#define AHRS_VISUALIZATION  1
#define DISPLAY_PANEL       2

///////////////////////////////////////////////////////////////////////////////////////////////////////
// GLOBAL VARIABLES
///////////////////////////////////////////////////////////////////////////////////////////////////////

// OUTPUT
DigitalOut confStatus(LED1);            // Config file status LED
DigitalOut logStatus(LED2);             // Log file status LED
DigitalOut gpsStatus(LED3);             // GPS fix status LED
DigitalOut updaterStatus(LED4);         // update loop status LED
//DigitalOut sonarStart(p18);           // Sends signal to start sonar array pings
Display display;                        // UI display
//Beep speaker(p24);                      // Piezo speaker

// INPUT
Menu menu;
Buttons keypad;

// COMM
Serial pc(USBTX, USBRX);                // PC usb communications
SerialGraphicLCD lcd(LCDTX, LCDRX, SD_FW);  // Graphic LCD with summoningdark firmware
Serial tel(TELEMTX, TELEMRX);           // UART for telemetry
Telemetry telem(tel);                   // Setup telemetry system

// SENSORS
Sensors sensors;                        // Abstraction of sensor drivers
//DCM ahrs;                             // ArduPilot/MatrixPilot AHRS
Serial *dev;                            // For use with bridge

// MISC
FILE *camlog;                           // Camera log
Filesystem fs;                          // Set up filesystems
Config config;                          // Configuration utility

// Timing
Timer timer;                            // For main loop scheduling

// GPS Variables
unsigned long age = 0;                  // gps fix age

// schedule for LED warning flasher
Schedule blink;

// Estimation & Navigation Variables
GeoPosition dr_here;                    // Estimated position based on estimated heading
Mapping mapper;

///////////////////////////////////////////////////////////////////////////////////////////////////////
// FUNCTION DEFINITIONS
///////////////////////////////////////////////////////////////////////////////////////////////////////

int autonomousMode(void);
void serialBridge(Serial &gps);
int gyroSwing(void);
int setBacklight(void);
int reverseScreen(void);
float irDistance(const unsigned int adc);
extern "C" void mbed_reset();

// If we don't close the log file, when we restart, all the written data
// will be lost.  So we have to use a button to force mbed to close the
// file and preserve the data.
//

int dummy(void)
{
    return 0;
}


int resetMe()
{
    mbed_reset();
    
    return 0;
}


int main()
{
    //checkit(__FILE__, __LINE__);
    //xTaskCreate( shell, (const signed char * ) "shell", 128, NULL, (tskIDLE_PRIORITY+3), NULL );
    //checkit(__FILE__, __LINE__);
    //vTaskStartScheduler(); // should never get past this line.
    //while(1);

    // Let's try setting priorities...
    //NVIC_SetPriority(DMA_IRQn, 0);
    NVIC_SetPriority(TIMER3_IRQn, 2);   // updater running off Ticker, must be highest priority!!
    NVIC_SetPriority(EINT0_IRQn, 5);    // wheel encoders
    NVIC_SetPriority(EINT1_IRQn, 5);    // wheel encoders
    NVIC_SetPriority(EINT2_IRQn, 5);    // wheel encoders
    NVIC_SetPriority(EINT3_IRQn, 5);    // wheel encoders
    NVIC_SetPriority(SPI_IRQn, 7);      // uSD card, logging
    NVIC_SetPriority(UART0_IRQn, 10);   // USB
    NVIC_SetPriority(UART1_IRQn, 10);
    NVIC_SetPriority(UART2_IRQn, 10);
    NVIC_SetPriority(UART3_IRQn, 10);
    NVIC_SetPriority(I2C0_IRQn, 10);    // sensors?
    NVIC_SetPriority(I2C1_IRQn, 10);    // sensors?
    NVIC_SetPriority(I2C2_IRQn, 10);    // sensors?
    NVIC_SetPriority(ADC_IRQn, 10);     // Voltage/current
    NVIC_SetPriority(TIMER0_IRQn, 10);  // unused(?)
    NVIC_SetPriority(TIMER1_IRQn, 10);  // unused(?)
    NVIC_SetPriority(TIMER2_IRQn, 10);  // unused(?)

    // Something here is jacking up the I2C stuff
    // Also when initializing with ESC powered, it causes motor to run which
    // totally jacks up everything (noise?)
    initSteering();
    initThrottle();

    display.init();
    display.status("Data Bus 2014");

    // Send data back to the PC
    pc.baud(115200);
    fputs("Data Bus 2014\n", stdout);
    fflush(stdin);

    fputs("Initializing...\n", stdout);
    display.status("Initializing");
    
    // Initialize status LEDs
    updaterStatus = 0;
    gpsStatus = 0;
    logStatus = 0;
    confStatus = 0;

    if (!fifo_init()) {
        error("\n\n%% Error initializing SystemState fifo %%\n");
    }

    fputs("Loading configuration...\n", stdout);
    display.status("Load config");
    if (config.load("/etc/config.txt"))                          // Load various configurable parameters, e.g., waypoints, declination, etc.
        confStatus = 1;

    initThrottle();

    //pc.printf("Declination: %.1f\n", config.declination);
    pc.puts("Speed: escZero=");
    pc.puts(cvftos(config.escZero, 3));
    pc.puts(" escMin=");
    pc.puts(cvftos(config.escMin, 3));
    pc.puts(" escMax=");
    pc.puts(cvftos(config.escMax, 3));
    pc.puts(" top=");
    pc.puts(cvftos(config.topSpeed, 1));
    pc.puts(" turn=");
    pc.puts(cvftos(config.turnSpeed, 1));
    pc.puts(" Kp=");
    pc.puts(cvftos(config.speedKp, 4));
    pc.puts(" Ki=");
    pc.puts(cvftos(config.speedKi, 4));
    pc.puts(" Kd=");
    pc.puts(cvftos(config.speedKd, 4));
    pc.puts("\n");

    pc.puts("Steering: steerZero=");
    pc.puts(cvftos(config.steerZero, 2));
    pc.puts(" steerScale=");
    pc.puts(cvftos(config.steerScale, 1));
    pc.puts("\n");
    steering.setScale(config.steerScale);

    // Convert lat/lon waypoints to cartesian
    mapper.init(config.wptCount, config.wpt);
    for (unsigned int w = 0; w < MAXWPT && w < config.wptCount; w++) {
        mapper.geoToCart(config.wpt[w], &(config.cwpt[w]));
        pc.puts("Waypoint #");
        pc.puts(cvntos(w));
        pc.puts(" (");
        pc.puts(cvftos(config.cwpt[w].x, 4));
        pc.puts(", ");
        pc.puts(cvftos(config.cwpt[w].y, 4));
        pc.puts(") lat: ");
        pc.puts(cvftos(config.wpt[w].latitude(), 6));
        pc.puts(" lon: ");
        pc.puts(cvftos(config.wpt[w].longitude(), 6));
        pc.puts(", topspeed: ");
        pc.puts(cvftos(config.topSpeed + config.wptTopSpeedAdj[w], 1));
        pc.puts(", turnspeed: ");
        pc.puts(cvftos(config.turnSpeed + config.wptTurnSpeedAdj[w], 1));
        pc.puts("\n");
    }

    // TODO 3 print mag and gyro calibrations

    // TODO 3 remove GPS configuration, all config will be in object itself I think

    display.status("Vehicle config      ");
    pc.puts("Wheelbase: ");
    pc.puts(cvftos(config.wheelbase, 3));
    pc.puts("\n");
    pc.puts("Track Width: ");
    pc.puts(cvftos(config.track, 3));
    pc.puts("\n");
    steering.setWheelbase(config.wheelbase);
    steering.setTrack(config.track);

    display.status("Encoder config      ");
    pc.puts("Tire Circumference: ");
    pc.puts(cvftos(config.tireCirc, 5));
    pc.puts("\n");
    pc.puts("Ticks per revolution: ");
    pc.puts(cvftos(config.encStripes, 5));
    pc.puts("\n");
    sensors.configureEncoders(config.tireCirc, config.encStripes);

    display.status("Nav configuration   ");
    pc.puts("Intercept distance: ");
    pc.puts(cvftos(config.intercept, 1));
    pc.puts("\n");
    steering.setIntercept(config.intercept);
    pc.puts("Waypoint distance: ");
    pc.puts(cvftos(config.waypointDist, 1));
    pc.puts("\n");
    pc.puts("Brake distance: ");
    pc.puts(cvftos(config.brakeDist, 1));
    pc.puts("\n");
    pc.puts("Min turn radius: ");
    pc.puts(cvftos(config.minRadius, 3));
    pc.puts("\n");

    display.status("Gyro config         ");
    pc.puts("Gyro scale: ");
    pc.puts(cvftos(config.gyroScale, 5));
    pc.puts("\n");
    sensors.setGyroScale(config.gyroScale);

    display.status("GPS configuration   ");
    pc.puts("GPS valid speed: ");
    pc.puts(cvftos(config.gpsValidSpeed,1));
    pc.puts("\n");

    pc.puts("Gyro config         ");
    pc.puts("\n");
    pc.puts("Gyro scale: ");
    pc.puts(cvftos(config.gyroScale, 5));
    pc.puts("\n");
    sensors.setGyroScale(config.gyroScale);

    pc.puts("Calculating offsets...\n");
    display.status("Offset calculation  ");
    wait(0.2);
    // TODO 3 Really need to give the gyro more time to settle
    sensors.gps.disable();
    // TODO 2 sensors.Calculate_Offsets();

    pc.puts("Starting GPS...\n");
    display.status("Start GPS           "); // TODO 3: would be nice not to have to pad at this level
    wait(0.2);
    sensors.gps.setUpdateRate(10);
    sensors.gps.enable();

    pc.puts("Starting Scheduler...\n");
    display.status("Start scheduler     ");
    wait(0.2);
    // Startup sensor/AHRS ticker; update every UPDATE_PERIOD
    restartNav();
    startUpdater();

    pc.puts("Starting keypad...\n");

    keypad.init();
    
    pc.puts("Adding menu items...\n");

    // Setup LCD Input Menu
    menu.add("Auto mode", &autonomousMode);
    menu.add("Gyro Calib", &gyroSwing);
    menu.add("Backlight", &setBacklight);
    menu.add("Reverse", &reverseScreen);
    menu.add("Reset", &resetMe);

    pc.puts("Starting main timer...\n");

    timer.start();
    timer.reset();

    int thisUpdate = timer.read_ms();    
    int nextDisplayUpdate = thisUpdate;
    int nextWaypointUpdate = thisUpdate;
    char cmd;
    bool printMenu = true;
    bool printLCDMenu = true;

    pc.puts("Timer done, enter loop...\n");

    while (1) {

        thisUpdate = timer.read_ms();
        if (thisUpdate > nextDisplayUpdate) {
            // Pulling out current state so we get the most current
            SystemState *s = fifo_first();
            // TODO 3 fix this so gps is already in state
            // Now populate in the current GPS data
            s->gpsHDOP = sensors.gps.hdop();
            s->gpsSats = sensors.gps.sat_count();

            telem.sendPacket(s);
            display.update(s);
            nextDisplayUpdate = thisUpdate + 200;
        }

        // every so often, send the currently configured waypoints
        if (thisUpdate > nextWaypointUpdate) {
            telem.sendPacket(config.cwpt, config.wptCount);
            nextWaypointUpdate = thisUpdate + 10000;
            // TODO 2: make this a request/response, Telemetry has to receive packets, decode, etc.
        }

        if (keypad.pressed) {
            keypad.pressed = false;
            printLCDMenu = true;
            switch (keypad.which) {
                case NEXT_BUTTON:
                    menu.next();
                    break;
                case PREV_BUTTON:
                    menu.prev();
                    break;
                case SELECT_BUTTON:
                    display.select(menu.getItemName());
                    menu.select();
                    printMenu = true;
                    break;
                default:
                    printLCDMenu = false;
                    break;
            }//switch  
            keypad.pressed = false;
        }// if (keypad.pressed)

            
        if (printLCDMenu) {
            display.menu( menu.getItemName() );
            display.status("Ready.");
            display.redraw();
            printLCDMenu = false;
        }
        
        // TODO 3 move to UI area
        if (printMenu) {
            fputs("\n==============\nData Bus Menu\n==============\n", stdout);
            fputs("0) Autonomous mode\n", stdout);
            fputs("1) Bridge serial to GPS\n", stdout);
            fputs("2) Gyro calibrate\n", stdout);
            fputs("3) Shell\n", stdout);
            fputs("R) Reset\n", stdout);
            fputs("\nSelect from the above: ", stdout);
            fflush(stdout);
            printMenu = false;
        }

        if (pc.readable()) {
            cmd = fgetc(stdin);
            fputc(cmd, stdout);
            fputc('\n', stdout);
            printMenu = true;
            printLCDMenu = true;
            
            switch (cmd) {
                case 'R' :
                    resetMe();
                    break;
                case '0' :
                    display.select(menu.getItemName(0));
                    autonomousMode();
                    break;
                case '1' :
                    display.select("Serial bridge");
                    display.status("Standby.");
                    sensors.gps.enableVerbose();
                    serialBridge( *(sensors.gps.getSerial()) );
                    sensors.gps.disableVerbose();
                    break;
                case '2' :
                    display.select("Gyro Calib");
                    display.select(menu.getItemName(2));
                    gyroSwing();
                    break;
                case '3' :
                    display.select("Shell");
                    display.status("Standby.");
                    shell(0);
                    break;
                default :
                    break;
            } // switch        

        } // if (pc.readable())

        wait(0.1);

    } // while

}



///////////////////////////////////////////////////////////////////////////////////////////////////////
// OPERATIONAL MODE FUNCTIONS
///////////////////////////////////////////////////////////////////////////////////////////////////////

int autonomousMode()
{
    bool goGoGo = false;                // signal to start moving
    bool navDone;                       // signal that we're done navigating
    int nextTelemUpdate;                // keeps track of teletry update periods
    //extern int tSensor, tGPS, tAHRS, tLog;

    sensors.gps.reset_available();

    // TODO: 3 move to main?
    // Navigation

    goGoGo = false;
    navDone = false;
    keypad.pressed = false;
    //bool started = false;  // flag to indicate robot has exceeded min speed.
    
    if (initLogfile()) logStatus = 1; // Open the log file in sprintf format string style; numbers go in %d
    wait(0.2);

    sensors.gps.disableVerbose();
    sensors.gps.enable();
    //gps2.enable();

    fputs("Press select button to start.\n", stdout);
    display.status("Select starts.");
    wait(1.0);
    
    timer.reset();
    timer.start();
    nextTelemUpdate = timer.read_ms();
    wait(0.1);
    
    // Tell the navigation / position estimation stuff to reset to starting waypoint
    // Disable 05/27/2013 to try and fix initial heading estimate
    //restartNav();

    // Main loop
    //
    while(navDone == false) {
        //////////////////////////////////////////////////////////////////////////////
        // USER INPUT
        //////////////////////////////////////////////////////////////////////////////

        // Button state machine
        // if we've not started going, button starts us
        // if we have started going, button stops us
        // but only if we've released it first
        //
        // set throttle only if goGoGo set
        if (goGoGo) {
            // TODO: 2 Add additional condition of travel for N meters before the HALT button is armed
            
            if (keypad.pressed == true) { // && started
                fputs(">>>>>>>>>>>>>>>>>>>>>>> HALT\n", stdout);
                display.status("HALT.");
                navDone = true;
                goGoGo = false;
                keypad.pressed = false;
                endRun();
            }
        } else {
            if (keypad.pressed == true) {
                fputs(">>>>>>>>>>>>>>>>>>>>>>> GO GO GO\n", stdout);
                display.status("GO GO GO!");
                goGoGo = true;
                keypad.pressed = false;
                beginRun();
            }
        }        

        // Are we at the last waypoint?
        // 
        if (fifo_first()->nextWaypoint == config.wptCount) {
            fputs("Arrived at final destination.\n", stdout);
            display.status("Arrived at end.");
            navDone = true;
            endRun();
        }

        //////////////////////////////////////////////////////////////////////////////
        // TELEMETRY
        //////////////////////////////////////////////////////////////////////////////
        if (timer.read_ms() > nextTelemUpdate) {
            SystemState *s = fifo_first();
            telem.sendPacket(s); // TODO 4 run this out of timer interrupt
            nextTelemUpdate += 200; // TODO 3 increase update speed
        }

        //////////////////////////////////////////////////////////////////////////////
        // LOGGING
        //////////////////////////////////////////////////////////////////////////////
        // sensor reads are happening in the schedHandler();
        // Are there more items to come out of the log fifo?
        // Since this could take anywhere from a few hundred usec to
        // 150ms, we run it opportunistically and use a buffer. That way
        // the sensor updates, calculation, and control can continue to happen
        if (fifo_available()) {
            logStatus = !logStatus;         // log indicator LED
            logData( fifo_pull() );         // log state data to file
            logStatus = !logStatus;         // log indicator LED
        }

    } // while
    closeLogfile();
    wait(2.0);
    logStatus = 0;
    display.status("Completed. Saved.");
    wait(2.0);

    updaterStatus = 0;
    gpsStatus = 0;
    //confStatus = 0;
    //flasher = 0;

    sensors.gps.disableVerbose();

    return 0;
} // autonomousMode


///////////////////////////////////////////////////////////////////////////////////////////////////////
// UTILITY FUNCTIONS
///////////////////////////////////////////////////////////////////////////////////////////////////////

// Gather gyro data using turntable equipped with dual channel
// encoder. Use onboard wheel encoder system. Left channel
// is the index (0 degree) mark, while the right channel
// is the incremental encoder.  Can then compare gyro integrated
// heading with machine-reported heading
//
int gyroSwing()
{
    FILE *fp;
    int now;
    int next;
    int g[3];
    int leftTotal=0;
    int rightTotal=0;

    // Timing is pretty critical so just in case, disable serial processing from GPS
    sensors.gps.disable();
    stopUpdater();

    fputs("Starting gyro swing...\n", stdout);
    display.status("Starting...");
//    fp = openlog("gy");
    fp = stdout;

    display.status("Rotate clockwise.");
    fputs("Begin clockwise rotation, varying rpm\n", stdout);
    wait(1);

    display.status("Select exits.");
    fputs("Press select to exit\n", stdout);
    wait(1);


    timer.reset();
    timer.start();

    next = now = timer.read_ms();

    sensors._right.read();  // easiest way to reset the heading counter
    sensors._left.read();
    
    while (1) {
        now = timer.read_ms();

        if (keypad.pressed) {
            keypad.pressed = false;
            break;
        }

        if (now >= next) {
            leftTotal += sensors._left.read();
            rightTotal += sensors._right.read();
            sensors._gyro.read(g);
            fputs(cvitos(now), fp);
            fputs(" ", fp);
            fputs(cvntos(leftTotal), fp);
            fputs(" ", fp);
            fputs(cvntos(rightTotal), fp);
            fputs(" ", fp);
            fputs(cvitos(g[_z_]), fp);
            fputs("\n", fp);
            next = now + 50;
        }
    }    
    if (fp && fp != stdout) {
        fclose(fp);
        display.status("Done. Saved.");
        fputs("Data collection complete.\n", stdout);
        wait(2);
    }

    sensors.gps.enable();
    restartNav();
    startUpdater();
    
    keypad.pressed = false;

    return 0;
}



void bridgeRecv()
{
#if 0
    while (dev && dev->readable()) {
        pc.putc(dev->getc());
    }
#endif
}


void serialBridge(Serial &serial)
{
#if 0
    char x;
    int count = 0;
    bool done=false;

    fputs("\nEntering serial bridge in 2 seconds, +++ to escape\n\n", stdout);
    sensors.gps.enableVerbose();
    wait(2.0);
    //dev = &gps;
    sensors.gps.disable();
    serial.baud(38400);
    while (!done) {
        if (pc.readable()) {
            x = pc.getc();
            serial.putc(x);
            // escape sequence
            if (x == '+') {
                if (++count >= 3) done=true;
            } else {
                count = 0;
            }
        }
        if (serial.readable()) {
            fputc(serial.getc(), stdout);
        }
    }
#endif
}


int setBacklight(void) {
    Menu bmenu;
    bool done = false;
    bool printUpdate = false;
    static int backlight=100;
    
    display.select(">> Backlight");

    while (!done) {
        if (keypad.pressed) {
            keypad.pressed = false;
            printUpdate = true;
            switch (keypad.which) {
                case NEXT_BUTTON:
                    backlight+=5;
                    if (backlight > 100) backlight = 100;
                    lcd.backlight(backlight);
                    break;
                case PREV_BUTTON:
                    backlight-=5;
                    if (backlight < 0) backlight = 0;
                    lcd.backlight(backlight);
                    break;
                case SELECT_BUTTON:
                    done = true;
                    break;    
            }
        }
        if (printUpdate) {
            printUpdate = false;
            lcd.pos(0,1);
            // TODO 3 lcd.printf("%3d%%%-16s", backlight, "");
        }
    }
    
    return 0;
}

int reverseScreen(void) {
    lcd.reverseMode();
    
    return 0;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
// ADC CONVERSION FUNCTIONS
///////////////////////////////////////////////////////////////////////////////////////////////////////

// returns distance in m for Sharp GP2YOA710K0F
// to get m and b, I wrote down volt vs. dist by eyeballin the
// datasheet chart plot. Then used Excel to do linear regression
//
float irDistance(const unsigned int adc)
{
    float b = 1.0934; // Intercept from Excel
    float m = 1.4088; // Slope from Excel

    return m / (((float) adc) * 4.95/4096 - b);
}