I look forward to reading about your progress. My current favorite "all around" uBlox module is the NEO-M8N, as it supports most of the other systems in addition to GPS and has a fast update rate (18 Hz when using only GPS and 10 Hz when using
as it gives access to USB, I2C and Serial ports. Most uBlox modules directly support a USB interface but few breakout boards seem to bother adding the connector. Having a USB port makes it easier to test and configure the module with the uBlox U-Center application, which I find convenient to use as a way to test and characterize a module. This same CSG-made breakout PCB also comes with a decent antenna (taoglas) as opposed to some no-name ones you get out of China. In my experience, the quality of the antenna can make a significant different in how well a receiver performs. In fact,
However, having used many of the commercial GPS modules, the differences you're going to see in one module to the next as not that great, as all are limited to around 2.0-2.5 meters CEP accuracy under the best of conditions. So, you're idea of starting with something cheap, such as the NEO-6M, is not a bad option, either. I recommend using the uBlox binary protocol, but caution that it can be complicated and confusing in places, and uBlox has a habit of introducing new commands and deprecating old ones on a fairly frequent basis. So, you should also try to consult the correct programing manual for the module you're using. For this reason, I've avoided writing a general-purpose library for the uBLox modules, so far. However, I have a lot of codeI've written for use with the uBlox series and I'm happy to share it, or answer questions so you can write your own.
To get you started, I've attached a copy of one my test apps. The code is designed to run on an Arduino Leonardo (because it supports a 2nd serial port and allows the use of the USB port as a monitor.) This code can be configured to run using either a serial port to talk to the uBLox GPS, or vis the I2C port (Arduino Wire l;library.) Of the two, I recommend using I2C, as the serial port is harder to work with due to the fact that it's hard to sort out ACK/NAK responses to commands from the steam of position packets it want to constantly spit out.
If this code has a weakness it's that there are a few configuration commands, such as CFG_PRT (enableProtocols()) that are hard to implement because they require you to first read the current state of certain bits so you can include these in the config command packet. So, to keep my code simple, I've kludged around this by hard coding some of this data rather than first reading it from the module.
What this means, in practice, is that the module is really telling you where it thinks you are based on this model rather than where the raw data might indicate you are. This makes it difficult to integrate with the heading and position you might compute via odometry and other means.
// Designed to run on an Arduino Leonardo (ATMega32u4-based) board
#define USE_SERIAL 0 // If 1, talk to UBlox using Serial1, else use I2C
#define DEBUG 0 // If 1, enable debug messages
#if !USE_SERIAL
#include <Wire.h>
#endif
#include "Arduino.h"
#if USE_SERIAL
#define send(args) Serial1.write(args)
#define read(args) Serial1.read(args)
#else
#define send(args) Wire.write(args)
#define read(args) Wire.read(args)
#endif
#define output Serial
#define BUF_SIZE 400
int gpsAddress = 0x42; //GPS I2C Address
unsigned char state = 0;
unsigned char cls, id, chk1, chk2;
unsigned int pktLen, pktIdx;
bool acked;
typedef struct {
unsigned long iTOW; // GPS Millisecond Time of Week
long lon; // Longitude (1e-7)
long lat; // Latitude (1e-7)
long height; // Height above Ellipsoid (mm)
long hMSL; // Height above mean sea level (mm)
unsigned long hAcc; // Horizontal Accuracy Estimate (mm)
unsigned long vAcc; // Vertical Accuracy Estimate (mm)
} NAV_POSLLH;
typedef struct {
unsigned long iTOW; // GPS Millisecond Time of Week
unsigned int gDOP; // Geometric DOP
unsigned int pDOP; // Position DOP
unsigned int tDOP; // Time DOP
unsigned int vDOP; // Vertical DOP
unsigned int hDOP; // Horizontal DOP
unsigned int nDOP; // Northing DOP
unsigned int eDOP; // Easting DOP
} NAV_DOP;
typedef struct {
unsigned long iTOW; // GPS Millisecond Time of Week
unsigned char gpsFix; // Fix type: 0=none, 1=dead rek, 2=2D, 3=3D, etc.
unsigned char flags; // Bitfield
unsigned char fixStat;
unsigned char flags2; // Bitfield
unsigned long ttff;
unsigned long msss;
} NAV_STATUS;
typedef struct {
unsigned char rsvd; // Reserved
unsigned long cpMesL; // Carrier phase measurement [L1 cycles] (low 32 bits of IEEE 754 Double Precision)
unsigned long cpMesH; // Carrier phase measurement [L1 cycles] (high 32 bits of IEEE 754 Double Precision)
unsigned long prMesL; // Pseudorange measurement [m] (low 32 bits of IEEE 754 Double Precision)
unsigned long prMesH; // Pseudorange measurement [m] (high 32 bits of IEEE 754 Double Precision)
unsigned long doMes; // Doppler measurement [Hz] (IEEE 754 Single Precision)
unsigned char sv; // Space Vehicle Number
signed char mesQI; // Nav Measurements Quality Indicator
signed char cno; // Signal strength C/No. (dbHz)
unsigned char lli; // Loss of lock indicator (RINEX definition)
} RAW_ITEM;
typedef struct {
unsigned long iTOW; // GPS Millisecond Time of Week
unsigned int week; // Measurement GPS week number (Receiver Time).
unsigned char numSV; // # of satellites following
unsigned char rsvd; // Reserved
RAW_ITEM sat[]; // Array of RAW_ITEMs
} RXM_RAW;
typedef struct {
unsigned char chn; // Channel
unsigned char svid; // Satellite Id
unsigned char flags; // B0=Used for Nav, B7=smoothed (see manual for others)
unsigned char quality; // Bits 3-0 - 0-Idle, 1=Search, 2=Aquired, 3=Unusable, 4=Code Lock, 5,6,7=Code and Carrier locked
unsigned char cno; // Carrier to Noise Ratio (Signal Strength in dbHz)
signed char elev; // Elevation in integer degrees
int azim; // Azimuth in integer degrees
long prRes; // Pseudo range residual in centimetres
} SVINFO_ITEM;
typedef struct {
unsigned long iTOW; // GPS Millisecond Time of Week
unsigned char numCh; // Number of channels
unsigned char gFlags; // Bits 2-0 - 0 = Anataris, 1 = uBlox5, 2 = uBlox6
unsigned int rsvd; // Reserved
SVINFO_ITEM sItem[]; // Array of SVINFO_ITEM
} NAV_SVINFO;
typedef struct {
unsigned char msgClass;
unsigned char msgId;
unsigned char rate;
} CFG_MSG;
typedef struct {
unsigned int measRate;
unsigned int navRate; // Must equal 1
unsigned int timeRef;
} CFG_RATE;
typedef struct {
unsigned int mask;
unsigned char dynModel;
unsigned char fixMode;
long fixedAlt;
unsigned long fixedAltVar;
signed char minElev;
unsigned char drLimit;
unsigned int pDop;
unsigned int tDop;
unsigned int pAcc;
unsigned int tAcc;
unsigned char staticHoldThresh;
unsigned char dgpsTimeOut;
unsigned long reserved2;
unsigned long reserved3;
unsigned long reserved4;
} CFG_NAV5;
typedef struct {
unsigned char portId;
unsigned char reserved0;
unsigned int txReady;
unsigned long mode;
unsigned long baudRate;
unsigned int inProtoMask;
unsigned int outProtoMask;
unsigned int reserved4;
unsigned int reserved5;
} CFG_PRT;
typedef struct {
char swVersion[30];
char hwVersion[10];
char romVersion[30];
char extension[][30];
} MON_VER;
union {
unsigned char buf[BUF_SIZE];
NAV_POSLLH pos;
NAV_DOP dop;
NAV_STATUS stat;
RXM_RAW raw;
CFG_NAV5 nav5;
MON_VER ver;
NAV_SVINFO svInfo;
} UBX_PKTS;
void sendCmd (unsigned char id1, unsigned char id2, unsigned char *data, unsigned int len) {
unsigned char chk1 = 0, chk2 = 0, cc;
#if !USE_SERIAL
Wire.beginTransmission(gpsAddress);
#endif
send(0xB5);
send(0x62);
unsigned char hdr[] = {id1, id2, len & 0xFF, len >> 8};
for (int ii = 0; ii < sizeof(hdr); ii++) {
send(cc = hdr[ii]);
chk1 += cc;
chk2 += chk1;
}
for (int ii = 0; ii < len; ii++) {
send(cc = data[ii]);
chk1 += cc;
chk2 += chk1;
}
send(chk1);
send(chk2);
#if !USE_SERIAL
Wire.endTransmission();
#endif
// Give time to process shutting down messages
for (int ii = 0; ii < 20; ii++) {
process();
delay(10);
}
}
void sendRetry (unsigned char id1, unsigned char id2, unsigned char *data, unsigned int len) {
acked = false;
while (true) {
sendCmd(id1, id2, data, len);
if (acked)
break;
delay(100);
#if DEBUG
output.println(F("Retry"));
#endif
}
}
void enableProtocols (bool ubx, bool nmea) {
CFG_PRT prt;
unsigned int val = (ubx ? 1 : 0) | (nmea ? 2 : 0);
#if USE_SERIAL
prt.portId = 1;
prt.reserved0 = 0x0F;
prt.txReady = 0x42; // read from unit
prt.mode = 0x8C0; // read from unit
prt.baudRate = 9600; // read from unit
#else
prt.portId = 0;
prt.reserved0 = 0;
prt.txReady = 0x42; // read from unit
prt.mode = 0x84; // read from unit
prt.baudRate = 0; // read from unit
#endif
prt.inProtoMask = val;
prt.outProtoMask = val;
prt.reserved4 = 0;
prt.reserved5 = 0;
sendCmd(0x06, 0x00, (unsigned char *) &prt, sizeof(prt));
}
void enableMsg (unsigned char cls, unsigned char msg, unsigned char rate) {
#if DEBUG
output.print(F("enableMsg: cls: "));
output.print(cls, HEX);
output.print(F(", msg: "));
output.println(msg, HEX);
#endif
CFG_MSG enable;
enable.msgClass = cls;
enable.msgId = msg;
enable.rate = rate;
sendRetry(0x06, 0x01, (unsigned char *) &enable, sizeof(enable));
}
/*
* Set rate that measurements are taken
* msUpdate - milliseconds between updates (200 = 5Hz)
*/
void setUpdateRate (unsigned int msUpdate) {
CFG_RATE rate;
rate.measRate = msUpdate;
rate.navRate = 1; // Must be 1
rate.timeRef = 1; // Referenced to GPS time (?)
sendCmd(0x06, 0x08, (unsigned char *) &rate, sizeof(rate));
}
/*
* Set dynamic model, as follows:
* 0 - Portable
* 2 - Stationary
* 3 - Pedestrian
* 4 - Automotive
* 5 - Sea
* 6 - Airborne with <1g Acceleration
* 7 - Airborne with <2g Acceleration
* 8 - Airborne with <4g Acceleration
*/
void setDynamicModel (unsigned char model, signed char elev) {
CFG_NAV5 nav5;
nav5.mask = 3; // Set dynamic model and elev
nav5.dynModel = model;
nav5.minElev = elev;
#if 0
nav5.fixMode = 0;
nav5.fixedAlt = 0;
nav5.fixedAltVar = 0;
nav5.drLimit = 0;
nav5.pDop = 0;
nav5.tDop = 0;
nav5.pAcc = 0;
nav5.tAcc = 0;
nav5.staticHoldThresh = 0;
nav5.dgpsTimeOut = 0;
nav5.reserved2 = 0;
nav5.reserved3 = 0;
nav5.reserved4 = 0;
traceCmd(0x06, 0x24, (unsigned char *) &nav5, sizeof(nav5));
#endif
sendCmd(0x06, 0x24, (unsigned char *) &nav5, sizeof(nav5));
}
void setup () {
#if USE_SERIAL
Serial1.begin(9600);
#else
Wire.begin();
Wire.beginTransmission(gpsAddress);
send(0xFF); // Set regisaddress
Wire.endTransmission();
#endif
output.begin(115200);
delay(4000);
#if USE_SERIAL
output.println(F("U-Blox NEO-6P - Serial 1 Interface"));
#else
output.println(F("U-Blox NEO-6P - DDC (I2C) Interface"));
#endif
while (process())
;
// Disable unused messages
enableMsg(0x01, 0x02, 0); // NAV-POSLLH - Geodetic Position Solution
enableMsg(0x01, 0x06, 0); // NAV-SOL - Navigation Solution Information (ECEF)
enableMsg(0x01, 0x04, 0); // NAV_DOP - Dilution of precision
enableMsg(0x01, 0x01, 0); // NAV-POSECEF - Position Solution in ECEF
enableMsg(0x01, 0x32, 0); // NAV-SBAS - SBAS Status Data
enableMsg(0x01, 0x03, 0); // NAV-STATUS - Receiver Navigation Status
enableMsg(0x01, 0x30, 0); // NAV-SVINFO - Space Vehicle Information
enableMsg(0x02, 0x10, 0); // RXM-RAW - Raw Measurement Data
// Configure ports and other items
sendCmd(0x0A, 0x04, 0, 0); // Query MON-VER
enableProtocols(true, false); // CFG-PRT - Enable UBX, Disable NMEA
setDynamicModel(2, 15); // CFG-NAV5 - Navigation Engine Settings
setUpdateRate(1000); // CFG-RATE (NEO-6P limited to 1000 ms)
#if 1
// Verify that changes made by CFG-NAV5 have been applied
sendCmd(0x06, 0x23, 0, 0); // Query CFG-NAVX5 (responds with ACK?)
sendCmd(0x06, 0x24, 0, 0); // Query CFG-NAV5 (responds with ACK?)
#endif
// Enable used messages
enableMsg(0x01, 0x02, 1); // NAV-POSLLH - Geodetic Position Solution
enableMsg(0x01, 0x30, 1); // NAV-SVINFO - Space Vehicle Information
}
void loop () {
process();
}
#if DEBUG
int idx = 0;
#endif
bool process() {
bool processed = false;
#if USE_SERIAL
while (Serial1.available()) {
#else
Wire.beginTransmission(gpsAddress);
send(0xFD); // Select MSB of data available count
Wire.endTransmission();
Wire.requestFrom(gpsAddress, 2);
unsigned int count = (unsigned int) read() << 8;
count |= (unsigned int) read();
if (count == 0) {
return false;
}
Wire.requestFrom(gpsAddress, count);
while (Wire.available()) {
#endif
unsigned char cc = read();
#if DEBUG
if ((idx++ & 0x1F) == 0) {
output.println();
}
if (cc < 16) {
output.print('0');
}
output.print(cc, HEX);
output.print(F(", "));
#endif
switch (state) {
case 0: // Wait for sync 1 (0xB5)
chk1 = chk2 = 0;
if (cc == 0xB5) {
state++;
}
break;
case 1: // wait for sync 2 (0x62)
if (cc == 0x62) {
state++;
} else {
state = 0;
}
break;
case 2: // wait for class code
cls = cc;
chk1 += cc;
chk2 += chk1;
state++;
break;
case 3: // wait for Id
id = cc;
chk1 += cc;
chk2 += chk1;
state++;
break;
case 4: // wait for length byte 1
pktLen = cc;
chk1 += cc;
chk2 += chk1;
state++;
break;
case 5: // wait for length byte 2
pktLen |= (cc << 8);
pktIdx = 0;
chk1 += cc;
chk2 += chk1;
state++;
break;
case 6: // wait for <pktLen> payload bytes
UBX_PKTS.buf[pktIdx++] = cc;
chk1 += cc;
chk2 += chk1;
if (pktIdx == pktLen || pktIdx == sizeof(UBX_PKTS.buf)) {
state++;
}
break;
case 7: // wait for checksum 1
if (chk1 == cc) {
state++;
} else {
output.print(F("Chk1 fail: cls: "));
output.print(cls, HEX);
output.print(F(", id: "));
output.print(id, HEX);
output.print(F(", len: "));
output.print(pktLen, DEC);
output.print(F(" - computed "));
output.print(chk1, HEX);
output.print(F(" received "));
output.print(cc, HEX);
output.println();
state = 0;
}
break;
case 8: // wait for checksum 2
if (chk2 == cc) {
decodePacket();
} else {
output.print(F("Chk2 fail: cls: "));
output.print(cls, HEX);
output.print(F(", id: "));
output.print(id, HEX);
output.print(F(", len: "));
output.print(pktLen, DEC);
output.print(F(" - computed "));
output.print(chk1, HEX);
output.print(F(" received "));
output.print(cc, HEX);
output.println();
}
state = 0;
break;
}
processed = true;
}
return processed;
}
void decPrint3 (int val) {
if (val < 100)
output.print(' ');
if (val < 10)
output.print(' ');
output.print(val, DEC);
}
void decPrint5 (int val) {
int tmp = abs(val);
if (tmp < 1000)
output.print(' ');
if (tmp < 100)
output.print(' ');
if (tmp < 10)
output.print(' ');
if (val >= 0)
output.print(' ');
output.print(val, DEC);
}
void decodePacket () {
// Note: process <pktLen> bytes
#if DEBUG
output.println();
#endif
if (cls == 0x01 && id == 0x02 && pktLen == 28) {
// NAV_POSLLH packet
output.print(UBX_PKTS.pos.iTOW, DEC);
output.print(F(" - NAV_POSLLH - Lat: "));
output.print((float) UBX_PKTS.pos.lat / 10000000.0, DEC);
output.print(F(", Lon: "));
output.print((float) UBX_PKTS.pos.lon / 10000000.0, DEC);
output.print(F(", hAcc: "));
output.print(UBX_PKTS.pos.hAcc, DEC);
output.print(F(" mm"));
output.println();
} else if (cls == 0x01 && id == 0x30) {
// NAV_SVINFO packet
output.print(UBX_PKTS.svInfo.iTOW, DEC);
output.print(F(" - SVINFO - numCh: "));
output.print(UBX_PKTS.svInfo.numCh, DEC);
output.println();
for (int ii = 0; ii < UBX_PKTS.svInfo.numCh; ii++) {
output.print(F(" Id: "));
decPrint3(UBX_PKTS.svInfo.sItem[ii].chn);
output.print(F(", elev: "));
decPrint3(UBX_PKTS.svInfo.sItem[ii].elev);
output.print(F(", Azim: "));
decPrint3(UBX_PKTS.svInfo.sItem[ii].azim);
output.print(F(", cno: "));
decPrint3(UBX_PKTS.svInfo.sItem[ii].cno);
output.print(F(", prRes: "));
decPrint5(UBX_PKTS.svInfo.sItem[ii].prRes);
output.print(F(", used: "));
output.print(((UBX_PKTS.svInfo.sItem[ii].flags & 0x01) != 0 ? "Y" : "N"));
output.print(F(", smoothed: "));
output.print(((UBX_PKTS.svInfo.sItem[ii].flags & 0x80) != 0 ? "Y" : "N"));
output.println();
}
} else if (cls == 0x02 && id == 0x10) {
// RXM_RAW packet
output.print(UBX_PKTS.raw.iTOW, DEC);
output.print(F(" - RXM_RAW ("));
output.print(UBX_PKTS.raw.numSV, DEC);
output.println(F(" sats)"));
for (int ii = 0; ii < UBX_PKTS.raw.numSV; ii++) {
output.print(F(" "));
output.print(F("Sat: "));
int sat = UBX_PKTS.raw.sat[ii].sv;
if (sat < 100)
output.print(F(" "));
if (sat < 10)
output.print(F(" "));
output.print(sat, DEC);
output.print(F(", "));
output.print(UBX_PKTS.raw.sat[ii].cpMesH, HEX);
output.print(F(", "));
output.print(UBX_PKTS.raw.sat[ii].cpMesL, HEX);
output.println();
}
} else if (cls == 0x06 && id == 0x23) {
// CFG_NAVX5 Packet
output.print(F("CFG_NAVX5 - usePPP: "));
output.print((unsigned char) UBX_PKTS.buf[26], DEC);
output.println();
} else if (cls == 0x06 && id == 0x24) {
// CFG_NAV5 Packet
output.print(F("CFG_NAV5 - dynModel: "));
output.print(UBX_PKTS.nav5.dynModel, DEC);
output.print(F(", minElev: "));
output.print(UBX_PKTS.nav5.minElev, DEC);
output.println();
} else if (cls == 0x0A && id == 0x04) {
// MON_VER Packet (swVersion: 6.02 (36023), hwVersion: 00040007, romVersion: )
output.print(F("MON_VER - swVersion: "));
output.print((char *) &UBX_PKTS.ver.swVersion);
output.print(F(", hwVersion: "));
output.print((char *) &UBX_PKTS.ver.hwVersion);
output.print(F(", romVersion: "));
output.print((char *) &UBX_PKTS.ver.romVersion);
if (pktLen > 70) {
int exts = (pktLen - 70) / 30;
for (int ii = 0; ii < exts; ii++) {
output.print(F(", ext"));
output.print(ii + 1, DEC);
output.print(F(": "));
output.print((char *) &UBX_PKTS.ver.extension[ii]);
}
}
output.println();
} else if (cls == 0x05 && (id == 0x00 || id == 0x01) && pktLen == 2) {
// ACK, or NAK packets
acked = true;
output.print(id == 1 ? "ACK" : "NAK");
unsigned char aCls = UBX_PKTS.buf[0];
unsigned char aId = UBX_PKTS.buf[1];
if (aCls == 0x06 && aId == 0x01) {
output.println(F(" CFG_MSG"));
} else if (aCls == 0x06 && aId == 0x08) {
output.println(F(" CFG_RATE"));
} else if (aCls == 0x06 && aId == 0x00) {
output.println(F(" CFG_PRT"));
} else if (aCls == 0x06 && aId == 0x23) {
output.println(F(" CFG_NAVX5"));
} else if (aCls == 0x06 && aId == 0x24) {
output.println(F(" CFG_NAV5"));
} else {
output.print(F(" cls: "));
output.print(aCls, HEX);
output.print(F(", id: "));
output.print(aId, HEX);
output.println();
}
} else {
output.print(F("Pkt cls: "));
output.print(cls, HEX);
output.print(F(", id: "));
output.print(id, HEX);
output.print(F(", len: "));
output.print(pktLen, DEC);
output.println();
output.print(F(" "));
for (int ii = 0; ii < pktLen; ii++) {
unsigned jj = UBX_PKTS.buf[ii];
if (jj < 16)
output.print('0');
output.print(jj, HEX);
if ((ii & 0x0F) == 0x0F) {
output.println();
output.print(F(" "));
} else {
output.print(F(", "));
}
}
output.println();
}
}