The software I used to run my Sparkfun AVC 2011 entry, a 1:10 scale RC truck called \"Data Bus\". This is the final revision of the code as-run on April 23, 2011, the date of the contest, on the 3rd heat.
Dependencies: TinyCHR6dm PinDetect mbed IncrementalEncoder Servo Schedule DebounceIn SimpleFilter LSM303DLH Steering
main.cpp
- Committer:
- shimniok
- Date:
- 2011-04-27
- Revision:
- 0:9b27ebe1ce17
File content as of revision 0:9b27ebe1ce17:
/** Code for "Data Bus" UGV entry for Sparkfun AVC 2011 * http://bot-thoughts.com/ */ /////////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////////////////////// #include "mbed.h" #include "TinyGPS.h" #include "SDFileSystem.h" #include "ADC128S.h" #include "PinDetect.h" #include "LSM303DLH.h" #include "Servo.h" #include "IncrementalEncoder.h" #include "Steering.h" #include "Schedule.h" #include "GeoPosition.h" #include "TinyCHR6dm.h" #include "SimpleFilter.h" /////////////////////////////////////////////////////////////////////////////////////////////////////// // DEFINES /////////////////////////////////////////////////////////////////////////////////////////////////////// #define WHERE(x) debug->printf("%d\n", __LINE__); #define clamp360(x) \ while ((x) >= 360.0) (x) -= 360.0; \ while ((x) < 0) (x) += 360.0; #define clamp180(x) ((x) - floor((x)/360.0) * 360.0 - 180.0); #define absf(x) (x *= (x < 0.0) ? -1 : 1) #define UPDATE_PERIOD 50 // update period in ms #define GYRO_UPDATE UPDATE_PERIOD/5 // gyro update period in ms #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 // Error correction gains #define COMPASS_GAIN 0.25 #define YAW_GAIN 0.25 // 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 // Waypoint queue parameters #define MAXWPT 10 #define ENDWPT 9999.0 // Chassis specific parameters #define WHEEL_CIRC 0.321537 // m; calibrated with 4 12.236m runs. Measured 13.125" or 0.333375 wheel dia #define WHEELBASE 0.290 #define TRACK 0.280 /////////////////////////////////////////////////////////////////////////////////////////////////////// // GLOBAL OBJECTS /////////////////////////////////////////////////////////////////////////////////////////////////////// // OUTPUT DigitalOut confStatus(LED1); // Config file status LED DigitalOut logStatus(LED2); // Log file status LED DigitalOut gps2Status(LED3); // GPS fix status LED DigitalOut ahrsStatus(LED4); // GPS fix status LED DigitalOut sonarStart(p18); // Sends signal to start sonar array pings DigitalOut flasher(p10); // Autonomous mode warning flasher DigitalOut steerServo(p21); // After shutdown, pass pulses through to servo //Serial lcd(p9, NC); // LCD module, TX only // INPUT InterruptIn receiver(p23); // RC Receiver steering channel DigitalOut buttonPower(p19); // Power the button(s) //PinDetect upButton(p17); PinDetect selectButton(p20); // Input selectButton //PinDetect downButton(p18); // VEHICLE Servo steering(p21); // Steering Servo Servo throttle(p22); // ESC Schedule go; // Throttle profile, dead stop to full speed Schedule stop; // Throttle profile, full speed to dead stop Steering steerCalc(TRACK, WHEELBASE); // steering calculator int maxSpeed=520; // Servo setting for max speed // SENSORS //HMC6352 compass(p28, p27); // Driver for compass LSM303DLH compass3d(p28, p27); // Driver for compass //I2C cam(p28, p27); // CMUcam I2C bridge //ADC128S adc(p5, p6, p7, p15); // ADC128S102 8ch ADC driver; mosi, miso, sclk, cs ADC128S adc(p11, p12, p13, p14); // ADC128S102 8ch ADC driver; mosi, miso, sclk, cs IncrementalEncoder left(p30); // Left wheel encoder IncrementalEncoder right(p29); // Right wheel encoder //Serial gps1(p26, p25); // GPS1, Locosys LS20031 Serial ahrs(p26, p25); // CHR-6dm AHRS Serial gps2(p9, p10); // GPS2, iGPS-500 Serial *dev; // For use with bridge //TinyGPS gps1Parse; // GPS NMEA parser TinyCHR6dm ahrsParser; // CHR-6dm AHRS parser TinyGPS gps2Parse; // GPS NMEA parser // COMM Serial pc(USBTX, USBRX); // PC usb communications Serial *debug = &pc; // MISC Timer timer; // For main loop scheduling SDFileSystem sd(p5, p6, p7, p8, "log"); // mosi, miso, sclk, cs LocalFileSystem local("etc"); // Create the local filesystem under the name "local" /////////////////////////////////////////////////////////////////////////////////////////////////////// // GLOBAL VARIABLES /////////////////////////////////////////////////////////////////////////////////////////////////////// // GPS Variables double gps1_lat; // latitude double gps1_lon; // longitude double gps2_lat; // latitude double gps2_lon; // longitude unsigned long age = 0; // gps fix age float gps1_hdop = 0.0; // gps horizontal dilution of precision float gps2_hdop = 0.0; // gps horizontal dilution of precision int year; // gps date variables byte month; byte day; byte hour; byte minute; byte second; byte hundredths; // schedule for LED warning flasher Schedule blink; // Useful globals float declination; // compass declination for local correction bool goGoGo = false; // signal to start moving bool done = false; // signal that we're done navigating // Gyro Variables float gyro = 0; // gyro reading float gyroBias = 2027.0; // gyro bias in raw 12-bit ADC value unsigned int temp = 0; // gyro temp reading float gyroSens = 4.89; // in LSB/deg/sec since we're using ratiometric ADC and gyro float gyroSum[UPDATE_PERIOD/GYRO_UPDATE]; // for summijng/averaging gyro between DR / compass updates int gyroCount = 0; // counter for gyroSum array // Navigation Variables GeoPosition wpt[MAXWPT]; // course waypoints unsigned int wptCount = 0; // number of waypoints configured int wptCurrent = 0; // current waypoint GeoPosition here1; // current gps position GeoPosition here2; // current gps position GeoPosition dr_here; // current dead reckoning position GeoPosition dr_here_last; // DR position at last GPS packet // Misc FILE *fp = 0; bool buttonPressed = false; bool buttonReleased = false; /////////////////////////////////////////////////////////////////////////////////////////////////////// // FUNCTION DEFINITIONS /////////////////////////////////////////////////////////////////////////////////////////////////////// FILE *initLogfile(void); void initButtons(void); void initCompass(void); void initGPS(void); void initAHRS(void); void initFlasher(void); void initSteering(void); void initThrottle(void); void initDR(void); void loadConfig(float &interceptDist, float &declination, float &compassGain, float &yawGain); void doGPS(TinyGPS &parse, GeoPosition &here, DigitalOut status, char *date, char *time); void autonomousMode(void); void findGPSBias(double *brg, double *dist, int n, GeoPosition place); void idleMode(void); void compassCalibrate(void); void servoCalibrate(void); void serialBridge(Serial &gps); void instrumentCheck(void); float compassHeading(void); float gyroRate(unsigned int adc); float sonarDistance(unsigned int adc); float irDistance(unsigned int adc); // 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. // void assertButton() { buttonPressed = true; buttonReleased = false; } void deassertButton() { buttonPressed = false; buttonReleased = true; } // Use interrupt handler to receive AHRS serial comm and parse // it using TinyCHR6dm library // void recv1() { while (ahrs.readable()) ahrsParser.parse(ahrs.getc()); } void recv2() { while (gps2.readable()) gps2Parse.encode(gps2.getc()); } char getInput(const char *prompt) { char c; if (prompt) pc.printf("%s ", prompt); while (!pc.readable()) wait(0.001); c = pc.getc(); pc.printf("%c\n", c); return c; } int main() { // Send data back to the PC pc.baud(115200); //receiver.rise(&killSwitch); // Detects remote kill switch / rc takeover initButtons(); // Initialize input buttons initCompass(); initSteering(); initThrottle(); // initFlasher(); // Initialize autonomous mode flasher initGPS(); // Insert menu system here w/ timeout bool autoBoot=true; /* pc.printf("Booting to autonomous mode in 10 seconds, any key to abort.\n "); for (int i=1; i > 0; i--) { pc.printf("%d...", i); if (pc.readable()) { autoBoot = false; while (pc.readable()) pc.getc(); // clear buffer break; } wait(1.0); } pc.printf("\n"); */ char cmd; while (1) { if (autoBoot) { autoBoot = false; cmd = 'a'; } else { pc.printf("==============\nData Bus Menu\n==============\n"); pc.printf("(a)utonomous mode\n"); pc.printf("(s)erial bridge\n (1) Port 1 (2) Port 2 (GPS)\n"); pc.printf("(c)ompass calibration\n"); pc.printf("(i)nstrument check\n"); //pc.printf("(s)ervo calibration\n"); cmd = getInput("\nSelect from the above:"); } switch (cmd) { case 'a' : autonomousMode(); break; case 's' : // which GPS cmd = getInput("GPS2 (2) or AHRS(1)?"); switch (cmd) { case '1' : serialBridge(ahrs); break; case '2' : serialBridge(gps2); break; default : break; } break; case 'i' : instrumentCheck(); break; case 'c' : compassCalibrate(); break; default : break; } } } // Handle data from a GPS (there may be two GPS's so needed to put the code in one routine // void doGPS(TinyGPS &parse, GeoPosition &here, DigitalOut status, char *date, char *time) { double lat, lon; unsigned long age; parse.reset_ready(); // reset the flags //pc.printf("%d GPS RMC are ready\n", millis); parse.f_get_position(&lat, &lon, &age); parse.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age); sprintf(date, "%02d/%02d/%4d", month, day, year); sprintf(time, "%02d:%02d:%02d.%03d", hour, minute, second, hundredths); float hdop = parse.f_hdop(); // Want to blink the LED when GPS update arrives // must toggle opposite //status = (hdop > 0.0 && hdop < 10.0) ? 0 : 1; // Bearing and distance to waypoint here.set(lat, lon); //pc.printf("HDOP: %.1f gyro: %d\n", gps1_hdop, gyro); return; } // convert character to an int // int ctoi(char c) { int i=-1; if (c >= '0' && c <= '9') { i = c - '0'; } //printf("char: %c int %d\n", c, i); return i; } // convert string to floating point // double cvstof(char *s) { double f=0.0; double mult = 0.1; bool neg = false; //char dec = 1; // leading spaces while (*s == ' ' || *s == '\t') { s++; if (*s == 0) break; } // What about negative numbers? if (*s == '-') { neg = true; s++; } // before the decimal // while (*s != 0) { if (*s == '.') { s++; break; } f = (f * 10.0) + (double) ctoi(*s); s++; } // after the decimal while (*s != 0 && *s >= '0' && *s <= '9') { f += (double) ctoi(*s) * mult; mult /= 10; s++; } // if we were negative... if (neg) f = -f; return f; } // copy t to s until delimiter is reached // return location of delimiter+1 in t char *split(char *s, char *t, int max, char delim) { int i = 0; if (s == 0 || t == 0) return 0; while (*t != 0 && *t != delim && i < max) { *s++ = *t++; i++; } *s = 0; return t+1; } #define MAXBUF 64 // load configuration from filesystem void loadConfig(float &interceptDist, float &declination, float &compassGain, float &yawGain) { // FILE *fp; char buf[MAXBUF]; // buffer to read in data char tmp[MAXBUF]; // temp buffer char *p; double lat, lon; bool declFound = false; // Just to be safe let's wait //wait(2.0); pc.printf("opening config file...\n"); fp = fopen("/etc/config.txt", "r"); if (fp == 0) { pc.printf("Could not open config.txt\n"); } else { wptCount = 0; declination = 0.0; while (!feof(fp)) { fgets(buf, MAXBUF-1, fp); p = split(tmp, buf, MAXBUF, ','); // split off the first field switch (tmp[0]) { case 'W' : // Waypoint p = split(tmp, p, MAXBUF, ','); // split off the latitude to tmp lat = cvstof(tmp); p = split(tmp, p, MAXBUF, ','); // split off the longitude to tmp lon = cvstof(tmp); if (wptCount < MAXWPT) { wpt[wptCount].set(lat, lon); wptCount++; } break; case 'G' : // Gyro Bias p = split(tmp, p, MAXBUF, ','); // split off the declination to tmp gyroBias = (float) cvstof(tmp); break; case 'D' : // Compass Declination p = split(tmp, p, MAXBUF, ','); // split off the declination to tmp declination = (float) cvstof(tmp); declFound = true; break; case 'I' : // Intercept distance p = split(tmp, p, MAXBUF, ','); // split off the number to tmp interceptDist = (float) cvstof(tmp); break; case 'S' : // Speed maximum p = split(tmp, p, MAXBUF, ','); // split off the number to tmp maxSpeed = atoi(tmp); //pc.printf("tmp:%s maxSpeed:%d\n", tmp, maxSpeed); break; case 'E' : p = split(tmp, p, MAXBUF, ','); // split off the number to tmp compassGain = (float) cvstof(tmp); p = split(tmp, p, MAXBUF, ','); // split off the number to tmp yawGain = (float) cvstof(tmp); default : break; } // switch } // while // Did we get the values we were looking for? if (wptCount > 0 && declFound) { confStatus = 1; } } // if fp if (fp != 0) fclose(fp); pc.printf("Intercept Dist: %.1f\n", interceptDist); pc.printf("Declination: %.1f\n", declination); pc.printf("MaxSpeed: %d\n", maxSpeed); pc.printf("CompassGain; %.3f YawGain: %.3f\n", compassGain, yawGain); for (int w = 0; w < MAXWPT && w < wptCount; w++) { pc.printf("Waypoint #%d lat: %.6f lon: %.6f\n", w, wpt[w].latitude(), wpt[w].longitude()); } } /////////////////////////////////////////////////////////////////////////////////////////////////////// // INITIALIZATION ROUTINES /////////////////////////////////////////////////////////////////////////////////////////////////////// // Find the next unused filename of the form logger##.csv where # is 0-9 // FILE *initLogfile() { FILE *fp = 0; char myname[64]; pc.printf("Opening log file...\n"); while (fp == 0) { if ((fp = fopen("/log/test.txt", "r")) == 0) { pc.printf("Waiting for filesystem to come online..."); wait(0.200); } } fclose(fp); for (int i = 0; i < 1000; i++) { sprintf(myname, "/log/log%04d.csv", i); //pc.printf("Try file: <%s>\n", myname); if ((fp = fopen(myname, "r")) == 0) { //pc.printf("File not found: <%s>\n", myname); break; } else { //pc.printf("File exists: <%s>\n", myname); fclose(fp); } } fp = fopen(myname, "w"); if (fp == 0) { pc.printf("file write failed: %s\n", myname); } else { //status = true; pc.printf("opened %s for writing\n", myname); fprintf(fp, "Millis, Gyro, GyroHdg, CompassHeading, Latitude, Longitude, Age, Date, Time, Altitude, Course, Speed, HDOP, Bearing, Distance, Left Enc, Right Enc, Gyro Temp, OdoHdg\n"); //fclose(fp); } return fp; } void initButtons() { // Set up button (plugs into two GPIOs, active low buttonPower = 0; selectButton.mode(PullUp); selectButton.setSamplesTillAssert(20); selectButton.setAssertValue(0); // active low logic selectButton.setSampleFrequency(20); // 20us selectButton.attach_asserted( &assertButton ); selectButton.attach_deasserted( &deassertButton ); } void initCompass() { // Initialize compass; continuous mode, periodic set/reset, 20Hz measurement rate. //compass.setOpMode(HMC6352_CONTINUOUS, 1, 20); // Set calibration parameters for this particular LSM303DLH //compass3d.setOffset(29.50, -0.50, 4.00); //compass3d.setScale(1.00, 1.03, 1.21); compass3d.setOffset(44.50, 5.00, -0.50); // Apr 11 testing compass3d.setScale(1.00, 1.04, 1.29); // Apr 11 testing //compass3d._debug = &pc; } void initGPS() { // Initialize the GPS comm and handler //gps1.baud(57600); // LOCOSYS LS20031 // Set LCD baud rate ; has to be 4800 // because we share with 4800 bps GPS\ // send chr(124) and CTRL-L //gps2.baud(9600); //gps2.printf("Data Bus"); //gps2.printf("%c%c", 124, 12); gps2.baud(4800); // Pharos iGPS-500 // Synchronize with GPS //gps1Parse.reset_ready(); gps2Parse.reset_ready(); } void initAHRS() { char data[MAX_BYTES]; int status; int ok = 0; // counts number of command_complete messages int c = 10; // timeout for status ahrs.baud(115200); ahrs.attach(recv1, Serial::RxIrq); wait(0.5); // Configure AHRS to use only acceleromters and gyro, no magnetometer data[0] = Accel_EN; ahrsParser.send_packet(&ahrs, SET_EKF_CONFIG, 1, data); c = 10; while (!ahrsParser.statusReady() && c-- > 0) wait(0.1); status = ahrsParser.status(); if (status == PT_COMMAND_COMPLETE) ok++; pc.printf("SET_EKF_CONFIG: %02x %s\n", status, ahrsParser.statusString(status)); ahrsParser.send_packet(&ahrs, ZERO_RATE_GYROS, 0, 0); c = 10; while (!ahrsParser.statusReady() && c-- > 0) wait(0.1); status = ahrsParser.status(); if (status == PT_COMMAND_COMPLETE) ok++; pc.printf("ZERO_RATE_GYROS: %02x %s\n", status, ahrsParser.statusString(status)); data[0] = 0; // 20Hz ahrsParser.send_packet(&ahrs, SET_BROADCAST_MODE, 1, data); c = 10; while (!ahrsParser.statusReady() && c-- > 0) wait(0.1); status = ahrsParser.status(); if (status == PT_COMMAND_COMPLETE) ok++; pc.printf("SET_BROADCAST_MODE: %02x %s\n", status, ahrsParser.statusString(status)); ahrsParser.send_packet(&ahrs, EKF_RESET, 0, 0); c = 10; while (!ahrsParser.statusReady() && c-- > 0) wait(0.1); status = ahrsParser.status(); if (status == PT_COMMAND_COMPLETE) ok++; pc.printf("EKF_RESET: %02x %s\n", status, ahrsParser.statusString(status)); //ahrs.printf("%s", ahrsParser.send_packet()) // <--mag calibration here// if (ok == 4) ahrsStatus = 1; // turn on status LED ahrsParser.resetReady(); } void initDR() { dr_here.set(wpt[0]); // Initialize Dead Reckoning to starting waypoint dr_here_last.set(wpt[0]); } void initFlasher() { // Set up flasher schedule; 3 flashes every 80ms // for 80ms total, with a 9x80ms period blink.max(9); blink.scale(80); blink.mode(Schedule::repeat); blink.set(0, 1); blink.set(2, 1); blink.set(4, 1); } void initSteering() { // Setup steering servo steering = 0.5; steering.calibrate(0.005, 45.0); } void initThrottle() { throttle = 0.5; throttle.calibrate(0.0005, 45.0); } /////////////////////////////////////////////////////////////////////////////////////////////////////// // OPERATIONAL MODE FUNCTIONS /////////////////////////////////////////////////////////////////////////////////////////////////////// void autonomousMode() { // Navigation float interceptDist = 1.5; // Course correction intercept distance ; GPS_MAX_HDOP calculates to 20* SA // Compass Variables float compassHdg = 0.0; // compass heading float compassHdg_last = -999; // heading at last GPS packet float compassGain = COMPASS_GAIN; // AHRS Variables float yaw = 0.0; // course float yawHdg = -1.0; // heading calculated from AHRS float yawRate = 0.0; // calculated rate of change in yaw float initialHdg = -999; // initial heading (course) float yawGain = YAW_GAIN; // Variables we calculate double bearing = 0.0; // bearing to next waypoint double distance = 0.0; // distance to next waypoint float heading = 0.0; // estimated heading float relativeBrg = 0.0; // relative bearing to next waypoint double gps2_bearing = 0.0; // bearing, gps2 double gps2_distance = 0.0; // distance, gps2 float gps2_heading = 0.0; // current heading, gps2 double dr_bearing = 0.0; // bearing, dead reckoning double dr_distance = 0.0; // distance, dead reckoning float dr_heading = 0.0; // dead reckoning heading float odo_heading = 0.0; // heading calc from odometry double err_bearing = 0.0; // bearing between dr and gps double err_distance = 0.0; // distance between dr and gps float err_yaw = 0.0; // error in ahrs yaw versus gps heading. float err_compass = 0.0; // error in compass versus gps heading double gps2_off_dist = 0.0; // distance from gps fix to starting point double gps2_off_brg = 0.0; // bearing from gps fix to starting point double encDistance = 0.0; // encoder average distance recorded double encSpeed = 0.0; // encoder calculated speed goGoGo = false; loadConfig(interceptDist, declination, compassGain, yawGain); // Load the waypoints, declination, and other config stuff steerCalc.setIntercept(interceptDist); // Setup steering calculator based on intercept distance go.set(UPDATE_PERIOD, 20, 650, maxSpeed, Schedule::hold); // Set throttle profile //stop.set(UPDATE_PERIOD, 10, 500, 400, Schedule::hold); // Set brake profile // TODO: Parameterize max brake // obstacle: brake, speed up // turn: brake or slowdown, speed up again // brake speed up initAHRS(); wptCurrent = 0; initDR(); // initalize dead reckoning wptCurrent++; // Point to the next waypoint; first wpt is the starting point done = false; buttonReleased = buttonPressed = false; bool started = false; // flag to indicate robot has exceeded min speed. // TODO--make sure all variables are initialized here yawHdg = -1.0; gyroCount = 0; err_bearing = 0.0; err_distance = 0.0; err_compass = 0.0; err_yaw = 0.0; fp = initLogfile(); // Open the log file in sprintf format string style; numbers go in %d wait(0.2); //gps1.attach(recv1, Serial::RxIrq); ahrs.attach(recv1, Serial::RxIrq); gps2.attach(recv2, Serial::RxIrq); // Figure out the "offset" or "bias" of the GPS reading // since we know the robot's starting point // 20110419 1052 findGPSBias(&gps2_off_brg, &gps2_off_dist, 2, wpt[0]); // Find average compass heading and use for // initializing base heading used by AHRS gyro+accel // Compass LSM303DLH runs at 15Hz = .06666 sec pc.printf("Computing initial yaw heading...\n"); initialHdg = 0.0; for (int i = 0; i < 10; i++) { float hdg = compassHeading(); initialHdg += hdg / 10.0; pc.printf("Compass: %.2f Initial Heading: %.2f\n", hdg, initialHdg); wait(0.05); } initialHdg = 91.0; // hard code due to issues pc.printf("Initial Heading: %.2f\n", initialHdg); odo_heading = initialHdg; //while (!ahrsParser.dataReady()); // sync to ahrs //ahrsParser.resetReady(); timer.reset(); // Keep track of milliseconds, elapsed timer.start(); float lastYaw = ahrsParser.readYaw(); // try to capture rate of change float initialHdgFilt = initialHdg; // initialize the initial heading filter bool gpsSync = false; // Allows us to sync closer to actual fix time int syncTime = 0; // stores next millisecond count at which to save DR data bool firstGPS = 0; // is this the first GPS packet? If so, don't calc errors // Main loop // while(done == false) { int millis = timer.read_ms(); // reset led status; allows blinking, 10ms long if ((millis % UPDATE_PERIOD) == 10) { logStatus = (fp != 0) ? 1 : 0; //ahrsStatus = (gps1Parse.f_hdop() > 0.0 && gps1Parse.f_hdop() < 10.0) ? 1 : 0; gps2Status = (gps2Parse.f_hdop() > 0.0 && gps2Parse.f_hdop() < 10.0) ? 1 : 0; } // Warning flasher //if (blink.ticked(millis)) { // flasher = blink.get(); //} // 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) { if (go.ticked(millis)) { throttle = go.get() / 1000.0; //pc.printf("throttle: %0.3f\n", throttle.read()); } if (buttonPressed && started) { pc.printf(">>>>>>>>>>>>>>>>>>>>>>> HALT\n"); done = true; goGoGo = false; } } else { if (buttonPressed == true) { pc.printf(">>>>>>>>>>>>>>>>>>>>>>> GO GO GO\n"); goGoGo = true; buttonPressed = false; } } // UPDATE EVERY UPDATE_PERIOD ms (50Hz) // The compass updates only ever 50ms // The gyro updates however fast we can read it // if ((millis % UPDATE_PERIOD) == 0) { unsigned int adcResult[8]; //WHERE(); adc.setChannel(0); for (int i=0; i < 8; i++) { adcResult[i] = adc.read(); } temp = adcResult[TEMP_CHAN]; // <== read/temp calibrate here // Convert gyro adc reading to heading // TODO: // automatically calculate null at start //gyro = gyroRate(adcResult[GYRO_CHAN]); yaw = ahrsParser.readYaw(); // <-- this may have been causing trouble before. 20110420 1249 // 20110421 yawRate = yaw - lastYaw; lastYaw = yaw; // If we're stopped, set initialHdg to compass // But do a running average / leaky integrator to reduce some noise // 20110421 if (encSpeed == 0) { findGPSBias(&gps2_off_brg, &gps2_off_dist, 1, wpt[0]); initialHdgFilt += compassHdg - (initialHdgFilt * 0.25); initialHdg = initialHdgFilt * 0.25; while (initialHdg < 0.0) initialHdg += 360.0; while (initialHdg >= 360.0) initialHdg -= 360.0; } // Substitute 6dof AHRS yaw for 1-axis gyro and 6dof compass yawHdg = initialHdg + yaw; while (yawHdg < 0) yawHdg += 360.0; while (yawHdg >= 360.0) yawHdg -= 360.0; compassHdg = compassHeading(); // pc.printf("Gyro: %d GyroHdg: %.1f\n", gyro, yaw); // Need a better filtered heading output // for now just use gyro heading as it seems to be most accurate // provided the bias is set right. // Odometry unsigned int leftCount = left.read(); unsigned int rightCount = right.read(); // TODO--> sanity check on encoders; if difference between them // is huge, what do we do? Slipping wheel? Skidding wheel? double leftDist = (WHEEL_CIRC / 32) * (double) leftCount; double rightDist = (WHEEL_CIRC / 32) * (double) rightCount; encDistance = (leftDist + rightDist) / 2.0; encSpeed = encDistance / (UPDATE_PERIOD * 0.001); odo_heading += (leftDist - rightDist) / TRACK; if ((millis % 500) == 0) { pc.printf("Odo heading: %.1f\n", odo_heading); } // 20110421 if (encSpeed < GPS_MIN_SPEED) { dr_heading = yawHdg; } else { started = true; dr_heading = compassHdg; // Yaw error // Let yaw track closely behind compass which // tracks closely behind GPS err_yaw = clamp180(compassHdg - yawHdg); // fix yaw by tweaking the initial heading // which is a kludge but... hey it's 2 days before the competition initialHdg += err_yaw * yawGain; clamp360(initialHdg); } while (dr_heading < 0) dr_heading += 360.0; while (dr_heading >= 360.0) dr_heading -= 360.0; //pc.printf("%d dr_heading: %.2f\n", __LINE__, dr_heading); // Dead Reckoning Position estimation, very simple stuff // lat: north +y, south -y ; cos(hdg) // long: west -x, east +x ; sin(hdg) // 1852m = 1 nautical mile = 1 deg lat / long dr_here.move(dr_heading, encDistance); //////////////////////////////// // Correct position here // // //////////////////////////////// dr_bearing = wpt[wptCurrent].bearing(dr_here); dr_distance = wpt[wptCurrent].distance(dr_here); //pc.printf("dr_heading: %.1f encDistance: %.3f\n", dr_heading, encDistance); // Log dead reckoning info logStatus = 0; fprintf(fp, "%d, %.1f, %.1f, %.1f, %.8f, %.8f, , , , , , %.1f, %.1f, , %.1f, %.3f, %d, %d, %d, %.1f\n", millis, yaw, yawHdg, compassHdg, dr_here.latitude(), dr_here.longitude(), dr_heading, encSpeed, dr_bearing, dr_distance, left.readTotal(), right.readTotal(), temp, odo_heading); //pc.printf("Yaw = %.1f\n", ahrsParser.readYaw()); // synchronize when RMC and GGA sentences received w/ AHRS if (gps2Parse.rmc_ready() && gps2Parse.gga_ready()) { char gps2date[32], gps2time[32]; // We synchronize on the first GPS packet that doesn't have GSV sentences // then every 1000ms after, we will save DR data for error comparison if (!gps2Parse.gsv_ready() && !gpsSync) { syncTime = millis + 1000; gpsSync = true; } doGPS(gps2Parse, here2, gps2Status, gps2date, gps2time); if (gps2_hdop < GPS_MAX_HDOP) { // Correct here2 for offset (bias) here2.move(gps2_off_brg, gps2_off_dist); //pc.printf("GPS2 %d HDOP: %.1f Compass: %.1f Gyro: %d Gyro Temp: %d\n", millis, gps1Parse.f_hdop(), compassHdg, gyro, temp); gps2_bearing = wpt[wptCurrent].bearing(here2); gps2_distance = wpt[wptCurrent].distance(here2); gps2_heading = gps2Parse.f_course(); //pc.printf("GPS2: lat: %.6f lon: %.6f wpt[%d]: lat:%.6f lon:%.6f distance=%.2f\n", // here2.latitude(), here2.longitude(), wptCurrent, // wpt[wptCurrent].latitude(), wpt[wptCurrent].longitude(), distance); // TODO --> Estimate new DR position here // simple: take a weighted average midpoint // TODO --> gradually move dr position by using same err distance/bearing but move a fraction of the distance // over the next n samples between gps readings // We have to address GPS lag. The easy (but probably incorrect way) is to use the DR info at the last time // we received a GPS update. Because the GPS is spitting out CSV sentences every 5 seconds or so, that // throws off the timing. if (!firstGPS) { // 20110421 // Position error err_distance = here2.distance(dr_here_last); err_bearing = here2.bearing(dr_here_last); //////////////////////////////////////////////////////////////////////////////////////////////////////////// // Error Correction //////////////////////////////////////////////////////////////////////////////////////////////////////////// // The strategy is: rely on GPS when hdop < 2; corrected by distance from initial starting point. At // hdop > 2, gps position becomes unreliable, so fall back on compass and/or AHRS yaw and dead reckoning. // The latter is pretty reliable. But heading isn't. Compass err varies based on, apparently, mag field // from the motor/wires. We'll stick to a set speed and correct compass error while relying on gps. That // plus position correction should keep DR up to date with the last gps fix with hdop < GPS_MAX_HDOP // Meanwhile, AHRS yaw seems to be filtered to the point that bandwidth can't track even moderate turn // rates. The exact cause is yet undiscovered. The onboard compass has a distortion I've been unable // to resolve, so we're running only gyro+accelerometer // // At higher speeds, we can trust gps course, but at lower speeds we can't. Initially when we start, the // most reliable heading/DR information comes from encoders+AHRS. At speed = 0, yaw and compass are very // reliable, so yaw initial heading is set from compass. So we'll use dead reckoning until the vehicle // reaches a higher speed, at which point, position info comes from gps and is used for yaw/compass/position // correction. // // I wished I could figure out a mathematically fancy way to do this but I'm confounded by all the different // sensor errors. KF is the only thing I've sort-of learned so far and I don't yet see how I can use it // for the types of (apparently non-gaussian) noise/error I'm seeing. //////////////////////////////////////////////////////////////////////////////////////////////////////////// // 20110421 if (encSpeed >= GPS_MIN_SPEED) { // Heading error err_compass = compassHdg_last - gps2_heading; if (err_compass < -180.0) err_compass += 360.0; if (err_compass > 180.0) err_compass -= 360.0; // we'll use err_compass when computing compass heading from now on // fix compass by tweaking declination // again, no time to do anything elegant, just hacking here declination += err_compass * compassGain; clamp360(declination); // 20110421 fprintf(fp, "ERR: err_dist: %.5f err_brg: %.2f compassHdg_last: %.2f gps2_heading: %.2f err_compass: %.5f declination: %.2f\n", err_distance, err_bearing, compassHdg_last, gps2_heading, err_compass, declination); } } // if (!firstGPS) // 20110421 // Save currnt DR position / heading // change this so that after gpsSync == true, we only save this // when millis == syncTime, then syncTime += 1000 dr_here_last.set( dr_here ); compassHdg_last = compassHdg; firstGPS = false; // less simple: find distance between & bearing, mult dist by weight, plot new position with move() // harder: kalman filter // ALSO---> need to do some kind of sanity check. If distance between each exceeds threshold, then one is bad // how do we guess which? Will gps ever be way off with low hdop? // maybe compare to predicted location based on last heading/speed? // should we convert to UTM? } logStatus = 0; //WHERE(); fprintf(fp, "%d, %.1f, %.1f, %.1f, %.8f, %.8f, GPS2, %s, %s, %.2f, %.1f, %.1f, %.1f, %.1f, %.3f, , , , %d, \n", millis, gyro, yawHdg, compassHdg, here2.latitude(), here2.longitude(), gps2date, gps2time, gps2Parse.f_altitude(), // <-- was hanging here... but probably due to goof up somewhere else. Stack corruption?? gps2_heading, gps2Parse.f_speed_mph(), gps2Parse.f_hdop(), bearing, distance, temp); //WHERE(); } ////////////////////////////////////////////////////////////////////////////// // Update steering and throttle ////////////////////////////////////////////////////////////////////////////// if ((millis % 100) == 0) { // TODO --> Calculate bearing/distance from GPS and DR // Might be able to do kalman filter here but... meanwhile: // 1) Weighted averaging of heading using GPS and Gyro to account for drift // 2) Correct GPS position for bias (brg/dist) // 3) Take corrected GPS position, go back 2 seconds, and find error brg/dist with DR at that time // 4) calculate a weighted average position based on DR and GPS, hdop and SV change // 5) apportion some of the error back into the GPS position bias (brg/dist) weighted on hdop and SV changes ////////////////////////////////////////////////////////////////////////////// // Navigation -- see error correction above // We'll always navigate off DR but only because we're supposed to be // correcting it as we go via GPS ////////////////////////////////////////////////////////////////////////////// // 20110421 bearing = dr_bearing; distance = dr_distance; heading = dr_heading; relativeBrg = bearing - heading; // 0 is desired heading // limit steering angle based on object detection ? // or limit relative brg perhaps? // if correction angle is < -180, express as negative degree if (relativeBrg < -180.0) relativeBrg += 360.0; if (relativeBrg > 180.0) relativeBrg -= 360.0; float steerAngle = steerCalc.calcSA(relativeBrg); // Convert steerAngle to servo value // Testing determined near linear conversion between servo ms setting and steering angle // up to 20*. Assumes a particular servo library with range = 0.005 // In that case, f(SA) = servoPosition = 0.500 + SA/762.5 // between 20 and 24* the slop is approximately 475 // What if we ignore the linearity and just set to a max angle // also range is 0.535-0.460 --> slope = 800 //steering = 0.500 + (double) steerAngle / 762.5; if (encSpeed < GPS_MIN_SPEED) { steering = 0.495; } else { steering = 0.500 + (double) steerAngle / 808.0; } pc.printf("Wpt #%d: rel bearing: %.1f, bearing: %.1f, distance: %.5f\n", wptCurrent, relativeBrg, bearing, distance); // if within 3m move to next waypoint // PARAMETERIZE DISTANCE if (distance < 3.0) { pc.printf("Arrived at wpt %d\n", wptCurrent); wptCurrent++; } // Are we at the last waypoint? if (wptCurrent == wptCount) { pc.printf("Arrived at final destination. Done.\n"); done = true; } } /* if ((millis % 1000) == 0) { pc.printf("Wpt #%d: rel bearing: %.1f, bearing: %.1f, distance: %.5f\n", wptCurrent, relativeBrg, bearing, distance); pc.printf("dr_heading: %.1f encDistance: %.3f\n", dr_heading, encDistance); pc.printf("err_dist: %.5f\nerr_brg: %.1f\n\n", err_distance, err_bearing); } */ } } // while // shut 'er down! throttle = 0.500; steering = 0.500; if (fp) { fclose(fp); logStatus = 0; pc.printf("closed log file.\n"); } //ahrsStatus = 0; gps2Status = 0; confStatus = 0; flasher = 0; } // autonomousMode void idleMode() { while (1) { ahrsStatus = 0; wait(0.2); ahrsStatus = 1; wait(0.2); } } /////////////////////////////////////////////////////////////////////////////////////////////////////// // UTILITIY FUNCTIONS /////////////////////////////////////////////////////////////////////////////////////////////////////// // Takes n position readings and averages the gps position // in a pretty rudimentary fashion, returning bearing and distance // from the specified place to the average position void findGPSBias(double *brg, double *dist, int n, GeoPosition place) { // GPS Position averaging double div = (double) n; double latavg = 0; double lonavg = 0; double lat = 0; double lon = 0; gps2Parse.reset_ready(); pc.printf("Calculating GPS offset...\n"); while (n > 0) { while (gps2Parse.rmc_ready() == false) wait(0.2); gps2Parse.reset_ready(); gps2Parse.f_get_position(&lat, &lon, &age); if (gps2Parse.f_hdop() > 0 && gps2Parse.f_hdop() < 2.0) { pc.printf("GPS lat: %.7f lon %.7f\n", lat, lon); latavg += lat / div; lonavg += lon / div; n--; } else { pc.printf("Waiting for good GPS fix\n"); gps2Status = 1; wait(0.1); gps2Status = 0; } } pc.printf("AVG lat: %.7f lon %.7f\n", latavg, lonavg); GeoPosition avg(latavg, lonavg); *dist = place.distance( avg ); *brg = place.bearing( avg ); // Now that we've calculated a bearing and distance to a quickie offset/bias // we can subtract this from all future GPS readings by simply taking the // gps position and doing a move(gps2_off_brg, gps2_off_dist) // maybe do adjustments based on how far off predicted DR position is from // GPS reported pos. I'd love to be able to factor in SV and HDOP info too } void compass3dCalibrate() { int tick; FILE *fp; pc.printf("Entering calibration mode for 30 seconds\nRotate compass thru 360 degree sphere\n"); for (int i=10; i > 0; i--) { pc.printf("%d...", i); if (pc.readable()) { char cmd = pc.getc(); } wait(1.0); } pc.printf("Begin calibration\n"); fp = fopen("/log/compass.txt", "w"); if (fp == 0) { pc.printf("Error opening file\n"); } else { // printf(fp, "# magX, magY, magZ\n"); timer.reset(); timer.start(); tick = 30; while (tick > 0) { // run every 20ms // get all three axes // printf(fp, "%d, %d, %d\n" if (timer.read_ms() > 1000) { pc.printf("%d\n", tick--); timer.reset(); } wait(0.2); } fclose(fp); pc.printf("\nData saved to compass.txt\n\n"); } } // HMC6352 compass calibration mode void compassCalibrate() { int tick; pc.printf("Entering calibration mode for 60 seconds\nRotate compass at least once through 360 degrees\n"); for (int i=10; i > 0; i--) { pc.printf("%d...", i); if (pc.readable()) { char cmd = pc.getc(); } wait(1.0); } pc.printf("Begin calibration\n"); //compass.setCalibrationMode(HMC6352_ENTER_CALIB); timer.start(); tick = 60; while (tick > 0) { if (timer.read_ms() > 1000) { pc.printf("%d\n", tick--); timer.reset(); } wait(0.2); } //compass.setCalibrationMode(HMC6352_EXIT_CALIB); pc.printf("Calibration complete.\n"); } void servoCalibrate() { } void bridgeRecv() { while (dev && dev->readable()) { pc.putc(dev->getc()); } } void serialBridge(Serial &gps) { char x; int count = 0; bool done=false; pc.printf("\nEntering serial bridge in 2 seconds, +++ to escape\n\n"); wait(2.0); //dev = &gps; gps.attach(NULL, Serial::RxIrq); while (!done) { if (pc.readable()) { x = pc.getc(); // escape sequence if (x == '+') { if (++count >= 3) done=true; } else { count = 0; } gps.putc(x); } if (gps.readable()) { pc.putc(gps.getc()); } } } void instrumentCheck() { bool done = false; float compassHdg = 0; float initialHdg = 0; float ahrsHdg = 0; unsigned int adcResult[8] = {0,0,0,0,0,0,0,0}; //unsigned int sonar, gyro, temp; Timer timer; SimpleFilter ranger(8); initialHdg = compassHeading(); initAHRS(); timer.reset(); timer.start(); buttonReleased = buttonPressed = false; pc.printf("press e to exit\n"); while (!done) { if (buttonPressed) done=true; while (pc.readable()) { if (pc.getc() == 'e') { done = true; break; } } int millis = timer.read_ms(); if ((millis % 20) == 0) { if ((millis % 100) == 0) { sonarStart = 1; wait_us(25); sonarStart = 0; wait_us(50); } adc.setChannel(0); for (int i = 0; i < 8; i++) { adcResult[i] = adc.read(); } ranger.filter(adcResult[0]); if ((millis % 1000) == 0) { compassHdg = compassHeading(); ahrsHdg = initialHdg + ahrsParser.readYaw(); while (ahrsHdg >= 360.0) ahrsHdg -= 360.0; while (ahrsHdg < 0) ahrsHdg += 360.0; char data[4]; // CMUcam1 I2C code goes here pc.printf("-----e to exit-----\nCompass: %.1f\nAHRS: %.1f\nGyro: %.3f\nGyro Temp: %d\nSonar Left: %.2f\nSonar Right: %.2f\nIR Left: %.5f\nIR Right: %.5f\nEncLeft: %d\nEncRight: %d\n\n", compassHdg, ahrsHdg, gyroRate(adcResult[GYRO_CHAN]), adcResult[TEMP_CHAN], sonarDistance(adcResult[SONARLEFT_CHAN]), sonarDistance(adcResult[SONARRIGHT_CHAN]), irDistance(adcResult[IRLEFT_CHAN]), irDistance(adcResult[IRRIGHT_CHAN]), left.readTotal(), right.readTotal()); pc.printf("IR filter: %d %.1f\n", ranger.value(), irDistance(ranger.value())); pc.printf("\nCam Obj Box: (%d,%d) (%d,%d)\n\n", data[0], data[1], data[2], data[3]); for (int i = 0; i < 8; i++) { pc.printf("ADC(%d)=%d\n", i, adcResult[i]); } pc.printf("\n"); } wait_ms(2); } } // clear input buffer while (pc.readable()) pc.getc(); } /////////////////////////////////////////////////////////////////////////////////////////////////////// // ADC CONVERSION FUNCTIONS /////////////////////////////////////////////////////////////////////////////////////////////////////// float compassHeading() { //Timer t; //int usec1, usec2; //t.reset(); //t.start(); //float compassHdg = compass.sample() / 10.0; // read compass //float compassHdg = compass3d.heading2d(); ////WHERE(); //__disable_irq(); // Disable Interrupts //usec1 = t.read_us(); float compassHdg = compass3d.heading(); //usec2 = t.read_us(); //__enable_irq(); // Enable Interrupts //pc.printf("usec1: %d, usec2: %d, usec: %d\n", usec1, usec2, usec2-usec1); //WHERE(); compassHdg -= declination; // Correct for local declination clamp360(compassHdg); return compassHdg; } // returns rate in */sec // // Gyro is ratiometric and so is ADC, so we can cancel out // errors due to voltage supply by putting sensitivity in // terms of adc reading and calculate rate from that. // (adc - bias) / sens // UPDATE_PERIOD in ms, 5.91mV/degree/sec is scale factor // // TODO: use constants and defines // float gyroRate(unsigned int adc) { float rate = 0.0; rate = ((float) (adc - gyroBias)) / gyroSens; return rate; } // returns distance in m for LV-EZ1 sonar // float sonarDistance(unsigned int adc) { float distance = 9999.9; // EZ1 uses 9.8mV/inch @ 5V or scaling factor of Vcc / 512 // so we can eliminate Vcc changes by simply converting the 0-512 inch range // to the ADC's 0-4096 range distance = ((float) adc) * (512 * 0.0254) / 4096; // distance converted to inch then meter return distance; } // 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(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); }