Introduction: Arduino Mega GPS With LCD and SD Logging
Use your Audino Mega as a GPS logger with a clear LCD display and 5 function buttons. A bi-colour LED is used to provide status information.
The SDcard can be left in and read using a card reader sketch, with output to the Serial Monitor.
This instructable will show you how an Adafruit Ultimate GPS board, based on the MTK3339 chip, can be set up to log to a sdcard.
The lcd screen is configured to show:
- Latitude and Longitude position
- UK National Gid reference
- Altitude
- Speed
- Bearing
- Distance travelled from origin ( the origin can be reset )
- Range from origin (As the crow flies )
- Time
- Number of satellites
- Horizontal dilution of position (HDOP)
- Logging status
The LCD values can be displayed in metric or imperial units.
The original logging concept was developed from:
Ladyada's logger
with Bill Greiman's SdFat library
using MTK3339 chipset http://www.adafruit.com/products/1272
Step 1: Requirements
1) Arduino Mega 2560
The Sainsmart mega also works.
2) LCD Keypad Shield http://www.hobbytronics.co.uk/arduino-lcd-keypad-s...
The sainsmart keypad shield also works.
3) The adafruit ultimate gps logger shield http://proto-pic.co.uk/adafruit-ultimate-gps-logge...
4) Arduino Stackable (Shield) Header Kit - R3 http://www.hobbytronics.co.uk/cables-connectors/p...
Pull the pins out of single row header connectors and use the plastic to create lengths of spacers for the stackable shield header connectors.
5) Red / green bi-colour common cathode led with 3 / 5mm diameter
6) Micro SD Memory Card http://www.hobbytronics.co.uk/4gb-microsd?keyword=...
7) Resistor for LED current control (depends on the LED)
If the LED requires 10 ma @ 5V the resistor would be R= V / I = 5/ 0.010 = 500 ohms
8) Plastic BOX
I used 15cm * 10cm * 5 cm and would have gone smaller if I could have sourced it.
9) 5.5mm DC power plug for Arduino http://www.hobbytronics.co.uk/cables-connectors/dc...
9) Mono 3.5 mm phono socket
10) Panel Switch (on / off rated suitably for 5v 500mA )
11) Panel Fuse holder with 500mA quick blow fuse
12) Small Rubber Adhesive feet (8 needed)
13) 10mm nylon spacers with internal diameter 3.3mm http://www.hobbytronics.co.uk/hardware/spacers-was...
These fit perfectly onto the lcd buttons to make extensions. Use model makers superglue to attach. Apply using a micro nozzle with care!
14) 2-2.5mm bolts with nuts and washers (3 needed) to act as support pillars for the Arduino. One narrow screw 2cm long.
15) Small length of 5mm dowel. Small screw to fix. 3-4 small plastic washes.
16) Hardboard to fit the bottom of box. Wood to make two base supports the width of the box.
17) A battery - I used a 2 cell 500 mAH LIPO. You will need a LIPO charger for this.
18) Lead to match battery terminal type.
19) An Optional UFA to SMA arial adapter cable if you plan to add an external arial.
The gps is very sensitive - I have not bought an arial, yet.
Step 2: Getting Started
Read the adafruit article on the ultimate gps https://learn.adafruit.com/adafruit-ultimate-gps/
Another useful article https://learn.adafruit.com/adafruit-shield-compat...
.
Fit 4 small rubber feet to the base of Arduino Mega:
This will provide a stable mounting surface.
Avoid the mounting holes.
.
Fit the stackable connectors to the gps board:
Make sure you use the outer-most holes in the GPS board. Check how everything goes together!
I partially inserted the board into the arduino, then soldered the 2 end pins in each section with the board up-side down.
Care must be taken to not overheat the board. The rest of the pins were soldered with the board removed.
Use a length of straight single Row Header connectors and pull out the pins to make strips of spacers. Fit the spacers to each of the 4 sections of stackable connectors you now have on the GPS board.
Assuming you are using a Mega, cut the tracks to pins 7, 8 and 10 as shown in the sketch. Use a sharp scalpel and cut carefully. Check before you cut!
.
Fit the GPS battery and SD card:
Insert the battery, noting the polarity.
Carefully slide the pre-formated SD card into the slot until it is held in place. Observe the correct alignment.
.
Connect 3 leads to CCS, TX, and RX :
I bent the leads on a spare 6 way stackable connector through 90 degrees and soldered it into the row with CSS / TX / RX
This allows me to remove the leads easily, if required.
Alternatively solder the leads directly.
.
Add the Bi-colour LED:
Check the data sheet for your bi-colour LED and select a resistor to provide the correct current. Establish the orientation for the leads. The LED should be of the common cathode type.
Temporarily fit the LCD board onto the GPS board- match the holes with care!
Bend the led leads using pointy nose pliars as support, to achieve the profile in the sketch.
The led should clear the end of the lcd screen by 10-12mm and reach 4-5 mm above it so that it will just emerge from the case.
Remove the LCD when you have established the lead profile and length.
Solder the LED, resistor and 3 wires to the underside of the GPS board.
Red led to pin 2, green to pin 3 and cathode via resistor to 0V.
.
Plug the GPS board into the Arduino Mega:
If you are using a Sainsmart there will be 2 leads on each side not in socket holes- Before assembly I bent these slightly outwards and put heat shrink tubing on the exposed pins.
Connect the 3 leads to the Arduino- RX to TX1, TX to RX1 and SSC to pin 32 (Could be another if you want)
Set the GPS switch to SOFT SERIAL.
.
Plug the LCD board into the GPS board:
Find the mounting hole in the lcd board next to the buttons. Carefully cut a piece of 5mm wooden dowel to support the button end of the LCD below the mounting hole. It should rest on the bench. This will stop the lcd rocking when the buttons are pressed.
Put enough plastic washers in the slot to prevent it closing. Screw the wooden rod into place- use a good pilot hole. If necessary adjust the height with sand paper / washes. The rod should just clear the access to the Mega power socket.
Step 3: Making a Box
1) Cut a piece of hardboard to fit inside the base of the box - allow at least 2mm movement all round.
2) Place the Mega on the base- Carefully position to allow 15mm from the left edge of the lcd screen to the left of the box and 35mm from the front edge of the lcd to the front of the box. Mark and drill one mounting hole. Countersink the bottom of the base. Fit a 2mm nut and bolt from the bottom.
Reposition the Arduino and fit a second hole- ensure the lcd is parallel with the base.
Add a third nut and bolt. The final mount will be a screw from the top into the lower right mounting hole.
Cut and file the mounting bolts to length if appropriate.
3) Place the mega on its mounts. Carefully measure the height between the top of the box and the top of the LCD. Check the position of the underside of the lid with respect to the top of the LCD.
Make two rectangular runners the correct depth to place the top of the LCD 1-2 mm below the lid bottom.
The right hand runner must be centred on the screw hole for the lower right mounting position.
Screw through the top of the base to secure the runners. Avoid the mounting hole.
Check the height of the lcd again with respect to the lid- do not put the lid on- the led will bend!
4) Drill the screw hole into the right hand runner.slightly countersink around the hole for the screw. Cut a 10mm nylon spacer to support the lower right section of the board. Hold the spacer in place with the screw and glue the spacer in place.
5) Position the Mega with the lcd parallel to the front of the box. Mount the base with a countersunk screw into each runner from the outside of the box. Careful positioning must be observed.
6) Measure the location of the LED. Drill a small pilot hole in the lid. Check that the LED is aligned and open the hole to a clearance size for the LED. The lid will now go on.
7) Measure and cut the hole for the LCD screen - 1-2 mm less than the external size of the lcd screen mount.
8) Remove the base and cut two strips of corrugated card to match the base of the runners. Glue these to the runners. Then put 2 holes through the card to match the screw holes. Re-fit the base. Adjust the screw pressure until the lid fits nicely on the top of the LCD (Ah the 1-2mm gap!)
9) Drill the five button holes:
Carefully paint white tippex onto the tops of the buttons.
Measure the position for the lower left button. Drill a pilot hole in the lid. Check that the hole aligns- put the lid on, shine a torch from above -the white button top should show.
Adjust the pilot hole if required with a circular needle file - recheck position and drill the hole out to the clearance diameter for a 10mm nylon spacer.
Repeat for 4 more buttons. Clean the tippex off with white spirit.
10) Add the button Extensions to the LCD board:
Protect your work surface. Get some paper cloth ready. Remove all distracting children.
Check that the 10mm Nylon spacers rest nicely on the button pushes. If necessary, enlarge the holes until a close interference fit is achieved. You will need radio control model makers cyanoacrylate glue- medium to thin viscosity and some fine glue nozzles, also from a radio control model supplier. Fit the first spacer vertically. Then using the finest nozzle apply a hint of glue to the inside of the bottom of the tube. Invert the lcd board to prevent seepage into the switch. Wait a minute then do the other buttons following the same procedure. Leave the LCD inverted for 30 minutes to allow the glue to cure. In the interim remove and discard the fine nozzle. Clean the glue bottle with tissue and replace the glue top.
Check that the lid of the box fits!
11) Drill and fit the Charger socket, switch fuse and Arial lead (If required)
12) Remove the screw on plastic strain relief case from the 5.5mm DC power plug. Solder a pair of wires to the terminals- red for the tip. Use shrink tubing to insulate and arrange the wires in a curve before heating. Removing the strain relief case allows room for the wooden support.
13 Connect the wiring as shown in the internal view of the box.
The phono socket tip should be connected to the battery positive.
The switched connection on the phono socket provides the supply to the rest of the circuit.
14) Cut the hole for the USB plug and re-assemble the Arduino.
Include the UFL connection to the arial lead if required- handle with care.
Tighten the final board mounting screw gently.
Check the wiring carefully. I do not accept liablity for this or any other feature contained within this article!
15) Connect the battery and fix in place using velcro
Stick 4 small rubber feet to the base of the box and it is complete.
Step 4: Libraries and Software
You will need to install two libraries:
1) Download and install the adafruit gps library https://github.com/adafruit/Adafruit-GPS-Library
2) Install the adafruit sd card library:
The default installation will not work unless any existing sd library is removed from your Arduino\libraries folder. By all means do this and install the library from https://github.com/adafruit/SD
Or keep your existing sd library in place and download SDADA.zip which I have modified to accept calls to SDADA. Install it in \Arduino\libraries\SDADA This way you can still use the other SD library
If you are using the SDADA version it is included in the gps2.ino sketch with the line:
#include SDADA.h
If not, comment this line out and use: #include SD.h
Both libraries have example which you can test the GPS with.
.
The following libraries should already be installed by default:
LiquidCrystal.h
SoftwareSerial.h
SPI.h
avr/sleep.h
.
Download gps2.zip
Save gps2.ino and gps_card.ino to their own folders in your Arduino sketch directory.
Open gps2.ino
Alter the following line:
#include SDADA.h
to #include SD.h if you are not using my amended SD library
This change also applies to gps_card.ino
Check the setting for the make of LCD:
In the config section:
boolean sain = true; // Set to true if using the sainsmart lcd shield, false for another eg the DFROBOT shield.
Set the sain variable according to your choice of lcd.
Note that both of the stated lcd boards use pin 10 to set the backlight. The up button allows the backlight to be switched off, saving battery power.
.
gps2.ino should now run!
.
The Buttons:
a) Select changes the displayed function on the 1st line of the lcd:
Position and Altitude ( Decimal Longitude and Latitude , alternating with altitude )
Position (Decimal Longitude and Latitude )
Position in ordnance survey easting and northing co-ordinates plus OS sheet number
Altitude
Running total of distance from Origin
Range from the Origin
Time
b) Left toggles metric/ imperial.
c) Right toggles logging:
An L is shown in the lower right when logging
An ! mark in this position indicates no sd card, or card not available
d) Up toggles back display. ( Useful power saver. )
e) Down resets distance to zero:
This sets the current position as the origin for range. ( The crow flies distance to origin. )
.
To activate a button- hold it down until the led stops flashing. Then release.
Step 5: Using the SD Card in Gps2.ino
Setting up the sd card:
The default chip select pin and the actual chipSelect pin need to be set to output.
The SD card is accessed with a begin statement that includes the pins used by the card on the gps board.
The card speed is set at SPI_Full_SPEED.
In the event of an error the led flashes out an error signal- details in the sketch header.
If debug is set to true in config, Serial monitor messages are also sent.
If the card initialises ok the variable hascard is set to true.
.
// make sure that the default chip select pin is set to
// output, even if you don't use it:
pinMode(SS,OUTPUT); // default mega select pin
pinMode(chipSelect,OUTPUT);
digitalWrite(chipSelect,LOW);
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect, 11, 12, 13)) {
if(debug) Serial.println(F("Card init. failed!"));
error(1);
} else{
if (!card.init(SPI_FULL_SPEED, chipSelect, 11, 12, 13)) {
error(2);
if(debug) {
Serial.println(F("initialization failed. Things to check:"));
Serial.println(F("* is a card is inserted?"));
Serial.println(F("* Is your wiring correct?"));
Serial.println(F("* did you change the chipSelect pin to match your shield or module?"));
}
}else{
hascard = true;
if(debug) Serial.println(F("Card init. succeeded"));
}
}
Selecting the SD file for Logging:
When the right hand button is pressed subroutine openfile is called.
This inspects the gps folder looking for an unused file name in the sequence GPSLOGnn.TXT, where nn = 0 to 99.
If a file is found, set foundit to true.
if (!hascard){return;} // do not continue if a card does not exist or is un-available
byte i;
char filename[18];
strcpy(filename, "/gps/GPSLOG00.TXT");
filename[17] =char(0);
boolean foundit =false;
for (i = 0; i < 100; i++) {
filename[11] = '0' + i/10;
filename[12] = '0' + i%10;
if (! SD.exists(filename)){
foundit=true; break;
}
}
If all of the filenames are in use, foundit will be false and the user is asked if the first 50 should be erased. If this is not ok, hascard is set to false so that there can be no further attempts at logging.
if (!foundit){
int thisbutton = -1;
lcd.clear();
lcd.print("Dir full-Erase?");
lcd.setCursor(0,1);
lcd.print("Sel:Ok Right:No");
do{
thisbutton = read_LCD_buttons();
} while(thisbutton == btnNONE);
lcd.clear();
if (thisbutton == btnSELECT){
lcd.print("Deleting");
for(i=0; i<50; i++){
filename[11] = '0' + i/10;
filename[12] = '0' + i%10;
if (SD.exists(filename)) SD.remove(filename);
}
i=0;
filename[11] = '0' + i/10;
filename[12] = '0' + i%10;
}else{
error(4);
hascard = false;
return;
}
}
The file can be opened:
In the event of an error Serial information is output and the error code signalled via the status led.
If successful, the first line of the file is output. This is the column headings.
Note the use of logfile.flush(). This ensures that all output is sent to the SD file before continuing.
logfile = SD.open(filename, FILE_WRITE);
if( ! logfile ) {
if(debug) {
Serial.print("Couldn't create ");
Serial.println(filename);
}
error(3);
hascard = false;
}else{
logging = true;
myfile = filename;
logfile.println(pad("Time ",13) + lpad("Date ",9) + lpad("Longitude",10)
+ lpad("Latitude",10) + lpad("Altitude",9) + " m" + lpad("Geoid",9) + " m"+ lpad("Speed",7) + " mph Bearing");
logfile.flush();
if(debug) {Serial.print("Writing to ");Serial.println(filename);}
}
.
lpad and pad are text padding functions:
.
String lpad(String temp , byte L){
byte mylen = temp.length();
if(mylen > (L - 1))return temp.substring(0,L-1);
for (byte i=0; i< (L-mylen); i++) temp = " " + temp;
return temp;
}
.
String pad(String temp , byte L){
byte mylen = temp.length();
if(mylen > (L - 1))return
temp.substring(0,L-1);
for (byte i=0; i< (L-mylen); i++) temp = temp + " ";
return temp;
}
.
Writing to the file:
The main loop reads the gps and outputs the values to a file if logging is taking place. ( An "L" is displayed in the lower right of the LCD screen. )
.
The values are formatted to produce fixed width columns.
.
Function dtostrf(float,w,dp,buf) is an Arduino library function that takes a float variable and converts it to a string, of width w with dp decimal points. buf is a char buffer which must be big enough to handle the conversion.
I defined buf with "char buf[20];" in the sketch header.
The file is flushed before continuing.
.
ogfile.print(pad(mytime,13)); // includes milli seconds
logfile.print(pad(myyear,9));
logfile.print(dtostrf(longitude,10,5,buf));
logfile.print(dtostrf(latitude,10,5,buf));
logfile.print(dtostrf(altitudem,9,1,buf));logfile.print(" m");
logfile.print(dtostrf(geoid,9,1,buf)); logfile.print(" m");
logfile.print(dtostrf(speedmph,7,2,buf)); logfile.print(" mph");
logfile.print(dtostrf(heading,6,1,buf)); logfile.println(" deg");
logfile.flush();
.
Closing the file:
If the card is not available return, otherwise flush all remaining output to the card before closing the file.
void closefile(){
if (!hascard) return;
logfile.flush();
logfile.close();
logging = false;
if(debug) Serial.println("File " + myfile + " closed");
}
Step 6: Reading the Gps
Setup the libraries in the script header:
#include Adafruit_GPS.h
#include SoftwareSerial.h
HardwareSerial mySerial = Serial1;
Adafruit_GPS GPS (&mySerial);
Select the gps in startup:
pinMode(chipSelect,OUTPUT);
digitalWrite(chipSelect,LOW);
GPS.begin(9600);
// Set the update rate // 1Hz works well and leaves time to perform logging GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); // alternatively 0.1Hz, 5Hz and 10Hz update rate
// RMC (recommended minimum) : GGA (fix data) including altitude GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
// Turn off updates on antenna status, if the firmware permits it
GPS.sendCommand(PGCMD_NOANTENNA);
.
Read the GPS by interrupt for the purpose of echoing to the Serial monitor.
Note that the data is actually being interpreted in the Adafruit_GPS.h library code, not in this interrupt routine.
// 1ms Interrupt on timer0 to facilitate gps data echo-
if GPSECHO = true useInterrupt(true); // read input by interrupt if true
.
That's it! Loop and read:
GPS.newNMEAreceived() is set to true if new gps information has arrived.
GPS.parse(stringptr) is set to true if values have been successfully separated (parsed) from the raw GPS text strings.
GPS.fix is set to true if the GPS reports having a fix on it's position.
If the gps has a fix extract the values for position, altitude, speed, bearing, time, geoidheight, satellites and hdop.
Position is returned in decimal Longitude and Latitude, Speed in knots, distance in metres.
.
if (GPS.newNMEAreceived()) {
char *stringptr = GPS.lastNMEA();
.
if (!GPS.parse(stringptr)) return; // go around if false
.
if(GPS.fix){
digitalWrite(ledGreen, HIGH);
fixed = true; // remember state in case it changes mid process
mymillis = GPS.milliseconds;
myseconds = GPS.seconds;
mytime = String(GPS.hour) + ":" + String(GPS.minute)+":";
logtime = mytime;
logtime += String(int(myseconds + mymillis / 1000 + 0.1 ) ); // round up if within 0.1 S
mytime += String(myseconds + mymillis / 1000); // full recalled time
myyear = String(GPS.day) + "/" + String(GPS.month) + "/";
lcdyear = myyear; theyear = String(GPS.year);
myyear += theyear;
mylen = theyear.length();
lcdyear += theyear.substring(mylen-1,mylen); // last digit only to fit in
latitude = GPS.latitudeDegrees;
longitude = GPS.longitudeDegrees;
altitudem = GPS.altitude;
altitudef = altitudem * fconvert;
speedknots = GPS.speed;
speedmph = speedknots * 1.15077945;
speedkph = speedknots * 1.85200;
heading = GPS.angle;
satellites = GPS.satellites;
geoid = GPS.geoidheight;
hdop = 7-int(GPS.HDOP + 0.5);
Step 7: UK National Grid Position and Distance Travelled
1) Converting position in decimal Longitude and Latitude to UK ordnance survey metre units is detailed in a pdf supplied by the ordnance survey:
http://www.ordnancesurvey.co.uk/docs/support/guide...
Page 40 does not make light reading, but does provide the necessary maths.
My subroutine void ordnance(float phi, float lamda) implements the calculation.
The values returned are accurate to +- 0.1 for the easting and +- 0.02 for the northing. This lack of precision is due to the limited accuracy of the Arduino float arithmetic. The Arduino only supports "6-7" significant decimal places.
.
The ordnance survey sheet number can be extracted from the most significant digit in the longitude and latitude values. Function String NE2NGR(float &east, float &north) performs the extraction and is credited to:
Alex http://www.codeproject.com/Articles/13577/GPS-Der...
.
2) Distance travelled can be calculated from the difference in two latitude and longitude values.
For small differences the estimate must be suspect due to the lack of precision inherent in a gps fix. The restriction on the Arduino floating point decimal place support also affects overall accuracy. Despite this, cumulative distance travelled estimates are reasonable. The Range to the origin is quite accurate as it only involves the difference between 2 locations.
The origin for distance calculations can be re-set using the lower button.
At low speeds I sample the positions over a three second period to increase the likelihood that the difference in position is greater than the distance of uncertainty in the GPS fix. At higher speeds I reduce the time between the sampled positions. This time is controlled by the variable dlimit.
The subroutine float distance_between (float lat1, float long1, float lat2, float long2) returns the distance.
The original work is credited to Maarten Lamers.
Step 8: Reading the Sdcard Using Gps_card.ino
This sketch assumes you have connected a Serial monitor, preferably one with the ability to copy text.
Unfortunately the default Arduino Serial monitor appears not offer this feature. ( I wrote my own. )
CoolTerm by Roger Meier does the job http://freeware.the-meiers.org/
.
If you are using the default SD library setup make the change:
#include SD.h in place of #include SDADA.h
.
Set the Serial Monitor to a baud of 115200.
.
When the sketch runs it will test the SD card and display the card size and details of the gps directory.
Type in the name of a file and it will be opened and displayed.
Individual files can be deleted using #delete filename
The data is presented in fixed width columns with space delimiters.
Assuming you can copy the text, using the values in excel is very straightforward.
Step 9: Specifications
The LCD Display has a selectable top line and a lower line consisting of:
Number of satellites
Speed
HDOP- Horizontal dilution of position
Bearing
The Logging Status:
- "L" - Logging
- " " - Not Logging
- "!" - SD card not present or not available
The top line:
Latitude and Longitude position in degrees, alternating with altitude
Latitude and Longitude in degrees
UK National Gid reference
Altitude
Distance travelled from origin ( The origin can be reset )
Range from origin
Time
Notes:
LCD values in metric or imperial.
For the lcd display a speed of <= cutoff (0.65) mph is shown as 0
The number of satellites is shown as a vertical bar in lower left corner. The largest number that can be displayed is 8, after that the bar is full.
HDOP is indicated in the lower centre moving bar. HDOP indicates the spread of observed satellites- Increased spread increases accuracy and the HDOP number decreases. HDOP values of 1 are very good!
A HDOP precision of < 0.5 is shown by a full bar.
A HDOP of 0.5 to 1.5 (1) is shown with 7 bars.
A HDOP of 6 is shown with 1 bar.
A HDOP of >6 displays no bars.
Buttons:
a) Select changes lcd 1st line between
altitude/position (Longitude and Latitude)
position (Long & Lat)
position in ordnance survey x,y co-ordinates
altitude
time
b) Left toggles metric/ imperial
c) Right toggles logging
d) Up toggles back display (Useful power saver)
e) Down resets distance to zero and sets the current position as the origin for range (The crow flies distance to origin)
To activate a button- hold down until led stops flashing. Then release.
LED Indicator:
1 second interval Flashing red - No Fix
1 second interval Flashing green - Fix
Four short green flashes - A button has been pressed
1 red flash repeated 5 times - Error 1: SD Card init failed
2 red flashes repeated 5 times - Error 2: SD Card full speed init failed
3 red flashes repeated 5 times - Error 3: Can not create the log file
4 red flashes repeated 5 times - Error 4: 100 files in log folder and do not delete
.
The cumulative distance estimate will have cumulative rounding errors from multiple one second interval positions.
The range will be more accurate as it is determined by only two locations.
Note that the precision of the Ordnance survey value is limited- The arduino float values hold 6-7 significant decimal places. The conversion formula involves many calculations and the rounding errors have cumulative effects. Ultimately precision is restricted by the returned sin, cos and tan values.
The easting value appears to be accurate to +- 0.1
The northing to +- 0.02
By comparison the Longitude/latitude values appear accurate to +- 0.00001
.
Connecting a ufl to SMA lead does not affect the performance. Only an active arial (1575.42MHz) would be recognised by the GPS chip.