Another fantastic Coobro Geo Upgrade

I happened to be digging through Google searches a couple weeks back, and I stumbled upon an amazing looking hack of the Coobro Geo. A regular here at Adafruit, Stephanie, has done some nice work with a custom Chronodot library, and other hacks of Adafruit products. Using the open source schematic of the Coobro Geo, Stephanie managed to strap on an Adafruit OLED, and a lipo battery she had laying around to create her own GPS tracking device. Here is how Stephanie describes it:
In a nutshell – I was thinking of making an arduino/gps device to help me track walks. I wanted to know the time and distance I was walking. When I saw your Coobro Geo project I realized that would be a perfect platform to build on.
For the LEDs, I’m using them to indicate the GPS accuracy – if there’s no signal or it’s too poor to use, the three LEDs flash. As the signal gets better, it goes to two, then one, then if the signal is excellent, none of the LEDs blink. The OLED screen displays some basic stuff on the top, like lat, long, UTC time. The push-button toggles the ‘track’ mode, so once it’s on and has a fix, one push starts it tracking – so it starts counting time, and every 10 seconds it checks how far it’s travelled. This info is displayed on the lower half of the screen. A second push of the button stops the tracking, so you can see the total distance covered, the time, and the calculated average speed. And finally, a long-push of the button clears the data.
I haven’t finished working on it yet, I was also planning on having it save the readings to the EEPROM, like every 10 seconds (or once a minute or whatever) I’d have it save the lat lon and utc stamp. Then I could dump that info at home and play it back over a map or something.
I am using a LiPo for power, I had one I’d pulled out of a dead handheld gaming device which was almost exactly the same size as the Coobro PCB. I added a JST connector so I can just unplug the battery and plug it in to a charger when I need to top it up.
I left off one of the distance LEDs because I wanted to keep the I2C pins available “just in case” and left the other LED off because… it had to be symmetrical! hehe. To connect the OLED I used several of the directional LED pins, but I did wire three of the direction LEDs up – they’re just hidden beneath the screen. The screen is held on by the wires that I used to connect it, most of which are on the left-hand side. On the right hand side there’s two wires that are not used, other than to hold the screen down. So if I needed to access the uC for any reason, I only have to desolder two wires and the OLED will fold away to one side.
Finally, I wired the power switch for ‘always on’ and replaced the jumper with the power switch, so switching it to ‘battery’ turns it on and switching it to the ‘ftdi’ side turns it off (unless you have the ftdi plugged in of course). The downside is that this means the GPS does not get that backup power to keep its settings. But I realized it would lose that every time I unplugged the battery to recharge it, so I figured it wouldn’t matter if it had to coldstart each time. It only takes about 2 minutes to get a lock and solid signal even indoors.
I totally love that gps module by the way. It’s my first experience working with a gps and it blows me away how sensitive it is. I found it was ‘too talkative’ though so I figured out how to send it the NMEA control info to have it only send the two sentences I required.
I’ve pasted my sketch info below. Like I said, it’s a work in progress…
Cheers!
-Stephanie
p.s. Just remembered I had to modify the tinygps library. For ‘signal quality’ I am actualy using HDOP (horizontal degree of precision) but the tinygps library was ignoring that value. It’s a bit of an arbitrary thing, but the lower-the-better and I figured i’d want to know if it was accurately tracking my walk or not. I’ve included my modified library as well as the sketch… I also increased the buffer in the new soft serial library, as I found it was having trouble keeping up with the gps.p.p.s. I think there’s a bug in the speed calculations, but haven’t had time to finish analyzing that – work has been crazy busy the last few weeks.
// constants #define UPDATE_INTERVAL 1000 // update screen once per second #define LONGPRESS 1500 // hold button down 1.5 sec to clear data #define SHORTPRESS 500 // hold button down .5 sec to toggle tracking #define BLINK_DUR 10 // length to blink LEDs when signal is poor // pin definitions #define BUTTON 2 // button on d2 #define GPS_TX 3 // gps tx on d3 #define GPS_RX 4 // gps rx on d4 #define OLED_CLK 5 // oled clock on d5 #define OLED_MOSI 6 // oled mosi on d6 #define LED_A2 8 // top led, right side #define LED_A1 9 // top led, middle #define LED_A0 10 // top led, left side #define OLED_CS 11 // oled chip select on d11 #define OLED_RESET 12 // oled reset on d12 #define OLED_DC 13 // oled d-c on d13 #define LED_B2 A1 // bottom led, right side #define LED_B1 A2 // bottom led, middle #define LED_B0 A3 // bottom led, left side #include <stdlib.h> #include <SSD1306.h> #include <NewSoftSerial.h> #include "TinyGPS.h" #include <EEPROM.h> #include "EEPROMAnything.h" NewSoftSerial nss(GPS_RX, GPS_TX); SSD1306 oled(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS); TinyGPS gps; unsigned long lastUpdate; unsigned long elapsedTime; unsigned long trackingStarted = 0; unsigned long trackingStopped = 0; unsigned long buttonDown = 0; float last_flat=0.0; float last_flon=0.0; float velocity = 0.0; float totalDistance = 0.0; byte count = 0; boolean isTracking = false; boolean buttonPush = false; int availableMemory() { uint8_t * heapptr, * stackptr; stackptr = (uint8_t *)malloc(4); heapptr = stackptr; free(stackptr); stackptr = (uint8_t *)(SP); return int(stackptr - heapptr); } void feedGps() { while (nss.available()) { gps.encode(nss.read()); } } void screenSetup() { oled.clear(); oled.drawstring(0, 0, "Latitude"); oled.drawstring(0, 1, "Longitude"); oled.drawstring(0, 2, "UTC"); oled.drawstring(82, 2, "Sig"); oled.drawstring(0, 3, "Velocity"); oled.drawstring(102, 3, "Km/h"); } void setup() { pinMode(LED_A0, OUTPUT); pinMode(LED_A1, OUTPUT); pinMode(LED_A2, OUTPUT); pinMode(LED_B0, OUTPUT); pinMode(LED_B1, OUTPUT); pinMode(LED_B2, OUTPUT); pinMode(BUTTON, INPUT); digitalWrite(BUTTON, HIGH); // blinky LEDs make me happy digitalWrite(LED_A2, HIGH); digitalWrite(LED_B0, HIGH); delay(500); digitalWrite(LED_A2, LOW); digitalWrite(LED_A1, HIGH); digitalWrite(LED_B0, LOW); digitalWrite(LED_B1, HIGH); delay(500); digitalWrite(LED_A1, LOW); digitalWrite(LED_A0, HIGH); digitalWrite(LED_B1, LOW); digitalWrite(LED_B2, HIGH); delay(500); digitalWrite(LED_B2, LOW); digitalWrite(LED_A0, LOW); delay(500); oled.ssd1306_init(SSD1306_<wbr>SWITCHCAPVCC); screenSetup(); oled.display(); delay(250); nss.begin(9600); // some NMEA sentences to tinker with the gps parameters // nss.print("$PMTK220,5000*1B\r\<wbr>n"); // set update to 5 sec // nss.print("$PMTK220,2500*19\r\<wbr>n"); // set update to 2.5 sec // nss.print("$PMTK220,2000*1C\r\<wbr>n"); // set update to 2 sec // nss.print("$PMTK220,1000*1F\r\<wbr>n"); // set update to 1 Hz // delay(15); // nss.print("$PMTK314,0,1,1,1,0,<wbr>0,0,0,0,0,0,0,0,0,0,0,0,0,0*<wbr>29\r\n"); // only GGA, RMC, VTG nss.print("$PMTK314,0,1,0,1,0,<wbr>0,0,0,0,0,0,0,0,0,0,0,0,0,0*<wbr>28\r\n"); // only GGA, RMC delay(15); // nss.print("$PMTK605*31\r\n"); // report firmware version } void loop() { feedGps(); boolean testButton = !(digitalRead(BUTTON)); byte ledLevel = 0; if(testButton != buttonPush) { if(testButton) { buttonDown = millis(); digitalWrite(LED_A1, HIGH); } else { unsigned long buttonDur = millis() - buttonDown; buttonDown = 0; digitalWrite(LED_A1, LOW); if(buttonDur > LONGPRESS) { isTracking = false; trackingStarted = 0; trackingStopped = 0; elapsedTime = 0; totalDistance = 0.0; } else if(buttonDur > SHORTPRESS) { if(isTracking) { isTracking = false; trackingStopped = millis(); elapsedTime = trackingStopped - trackingStarted; lastUpdate = 0; } else { isTracking = true; trackingStarted = millis(); if(elapsedTime != 0) trackingStarted = millis() - elapsedTime; count=0; lastUpdate = 0; } } } buttonPush = testButton; } if((millis() - lastUpdate) > UPDATE_INTERVAL) { char buf[10]; float flat, flon; unsigned long age, hdop, time, date; if(isTracking) { count++; feedGps(); if((count>9) && (gps.data_good())) { gps.f_get_position(&flat, &flon); float dist = gps.distance_between(last_<wbr>flat, last_flon, flat, flon); feedGps(); totalDistance += dist; last_flat = flat; last_flon = flon; count=0; } } screenSetup(); hdop=gps.hdop(); gps.f_get_position(&flat, &flon, &age); if(!isTracking) { last_flat = flat; last_flon = flon; } if(!(gps.data_good())) { oled.drawstring(64, 0, "Unknown"); oled.drawstring(64, 1, "Unknown"); } else { bool north = true; bool east = true; if(flat < 0.0) { flat = flat * -1.0; north = false; } if(flon < 0.0) { flon = flon * -1.0; east = false; } dtostrf(flat, 7, 5, buf); oled.drawstring(66, 0, buf); dtostrf(flon, 7, 5, buf); if(flon < 100.0) { oled.drawstring(66, 1, buf); } else { oled.drawstring(60, 1, buf); } if(north) { oled.drawstring(121, 0, "N"); } else { oled.drawstring(121, 0, "S"); } if(east) { oled.drawstring(121, 1, "E"); } else { oled.drawstring(121, 1, "W"); } } velocity = gps.f_speed_kmph(); feedGps(); if((hdop == 0xFFFFFFFF) || !(gps.data_good())) { ledLevel = 4; velocity=0.0; oled.drawstring(103, 2, "Unkn"); } else if((hdop > 749) || (age > 5999)){ ledLevel = 3; velocity=0.0; oled.drawstring(103, 2, "BAD "); } else if((hdop > 499) || (age > 3999)) { ledLevel = 2; velocity=0.0; oled.drawstring(103, 2, "AVG"); } else if((hdop > 199) || (age > 2199)) { oled.drawstring(103, 2, "Good"); ledLevel = 1; } else { ledLevel = 0; oled.drawstring(103, 2, "Best"); } feedGps(); gps.get_datetime(&date, &time, &age); if(time == 0xFFFFFFFF) { oled.drawstring(22, 2, "Unknown"); } else { time = time / 100; byte seconds = time % 100; byte minutes = (time / 100) % 100; byte hours = (time / 10000); if(hours < 10) { buf[0] = '0'; } else { buf[0] = (48+(hours/10)); } buf[1] = (48+(hours % 10)); buf[2] = '.'; if(minutes < 10) { buf[3] = '0'; } else { buf[3] = (48+(minutes/10)); } buf[4]=(48+(minutes % 10)); buf[5]='.'; if(seconds < 10) { buf[6]='0'; } else { buf[6]=(48+(seconds/10)); } buf[7]=(48+(seconds % 10)); buf[8]='\0'; oled.drawstring(22, 2, buf); } feedGps(); dtostrf(velocity, 4, 2, buf); oled.drawstring(54, 3, buf); if(isTracking) { oled.drawstring(12, 4, "-- TRACKING ON --"); elapsedTime = millis() - trackingStarted; } else { oled.drawstring(8, 4, "-- Not Tracking --"); } if(elapsedTime != 0) { oled.drawstring(0, 5, "Elapsed"); oled.drawstring(0, 6, "Speed"); oled.drawstring(0, 7, "Tot Dst"); byte seconds = (elapsedTime / 1000) % 60; byte minutes = (elapsedTime / 1000) / 60; float lapTime = (float)minutes + ((float)seconds / 100.0); float distKM = totalDistance / 1000.0; float pace = distKM / (elapsedTime / 3600); dtostrf(lapTime, 4, 2, buf); oled.drawstring(50, 5, buf); oled.drawstring(96, 5, "mm.ss"); dtostrf(pace, 5, 3, buf); oled.drawstring(50, 6, buf); oled.drawstring(102, 6, "Km/h"); dtostrf(distKM, 5, 3, buf); oled.drawstring(50, 7, buf); oled.drawstring(114, 7, "Km"); } else { // display free memory for debugging // between 1kB for the OLED buffer and // 256B for the softserial buffer, // we're tight on ram! oled.drawstring(0, 7, "FreeMem"); itoa(availableMemory(), buf, 10); oled.drawstring(50, 7, buf); } oled.display(); feedGps(); if(ledLevel == 4) { digitalWrite(LED_B0, HIGH); digitalWrite(LED_B1, HIGH); digitalWrite(LED_B2, HIGH); delay(BLINK_DUR); digitalWrite(LED_B0, LOW); digitalWrite(LED_B1, LOW); digitalWrite(LED_B2, LOW); } else if(ledLevel == 3){ digitalWrite(LED_B0, HIGH); digitalWrite(LED_B1, HIGH); delay(BLINK_DUR); digitalWrite(LED_B0, LOW); digitalWrite(LED_B1, LOW); } else if(ledLevel == 2) { digitalWrite(LED_B1, HIGH); delay(BLINK_DUR); digitalWrite(LED_B1, LOW); } else if(ledLevel == 1) { digitalWrite(LED_B0, HIGH); delay(BLINK_DUR); digitalWrite(LED_B0, LOW); } lastUpdate = millis(); } feedGps(); if(buttonDown > 0) { unsigned long buttonDur = millis() - buttonDown; if(buttonDur > LONGPRESS) { if(!digitalRead(LED_A1)) digitalWrite(LED_A1, HIGH); } else if(buttonDur > SHORTPRESS) { if(digitalRead(LED_A1)) digitalWrite(LED_A1, LOW); } } }


Trackbacks/Pingbacks
[...] can download the updated tinygps library here, and view Stephanie’s code over at Coobro Labs. Don’t own a Coobro Geo? Pick one up here! Filed under: gps,open source hardware — [...]